diff --git a/.env.example b/.env.example index 2d0005d..5481469 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,5 @@ ETH_NODE_URL='https://eth.llamarpc.com' +SEPOLIA_NODE_URL='https://rpc.ankr.com/eth_sepolia' MANTLE_NODE_URL='https://rpc.mantle.xyz' ARBITRUM_NODE_URL='https://arb1.arbitrum.io/rpc' SCROLL_NODE_URL='https://rpc.scroll.io' diff --git a/abi/ICorkHook.json b/abi/ICorkHook.json new file mode 100644 index 0000000..efab0bf --- /dev/null +++ b/abi/ICorkHook.json @@ -0,0 +1,636 @@ +[ + { + "type": "function", + "name": "addLiquidity", + "inputs": [ + { + "name": "ra", + "type": "address", + "internalType": "address" + }, + { + "name": "ct", + "type": "address", + "internalType": "address" + }, + { + "name": "raAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ctAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "amountRamin", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "amountCtmin", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "amountRa", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "amountCt", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "mintedLp", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getAmountIn", + "inputs": [ + { + "name": "ra", + "type": "address", + "internalType": "address" + }, + { + "name": "ct", + "type": "address", + "internalType": "address" + }, + { + "name": "raForCt", + "type": "bool", + "internalType": "bool" + }, + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getAmountOut", + "inputs": [ + { + "name": "ra", + "type": "address", + "internalType": "address" + }, + { + "name": "ct", + "type": "address", + "internalType": "address" + }, + { + "name": "raForCt", + "type": "bool", + "internalType": "bool" + }, + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "amountOut", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getFee", + "inputs": [ + { + "name": "ra", + "type": "address", + "internalType": "address" + }, + { + "name": "ct", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "baseFeePercentage", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "actualFeePercentage", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getForwarder", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getLiquidityToken", + "inputs": [ + { + "name": "ra", + "type": "address", + "internalType": "address" + }, + { + "name": "ct", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getMarketSnapshot", + "inputs": [ + { + "name": "ra", + "type": "address", + "internalType": "address" + }, + { + "name": "ct", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct MarketSnapshot", + "components": [ + { + "name": "ra", + "type": "address", + "internalType": "address" + }, + { + "name": "ct", + "type": "address", + "internalType": "address" + }, + { + "name": "reserveRa", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "reserveCt", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "oneMinusT", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "baseFee", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "liquidityToken", + "type": "address", + "internalType": "address" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getPoolKey", + "inputs": [ + { + "name": "ra", + "type": "address", + "internalType": "address" + }, + { + "name": "ct", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct PoolKey", + "components": [ + { + "name": "currency0", + "type": "address", + "internalType": "Currency" + }, + { + "name": "currency1", + "type": "address", + "internalType": "Currency" + }, + { + "name": "fee", + "type": "uint24", + "internalType": "uint24" + }, + { + "name": "tickSpacing", + "type": "int24", + "internalType": "int24" + }, + { + "name": "hooks", + "type": "address", + "internalType": "contract IHooks" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getPoolManager", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getReserves", + "inputs": [ + { + "name": "ra", + "type": "address", + "internalType": "address" + }, + { + "name": "ct", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "removeLiquidity", + "inputs": [ + { + "name": "ra", + "type": "address", + "internalType": "address" + }, + { + "name": "ct", + "type": "address", + "internalType": "address" + }, + { + "name": "liquidityAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "amountRamin", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "amountCtmin", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "amountRa", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "amountCt", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "swap", + "inputs": [ + { + "name": "ra", + "type": "address", + "internalType": "address" + }, + { + "name": "ct", + "type": "address", + "internalType": "address" + }, + { + "name": "amountRaOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "amountCtOut", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "amountIn", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "AddedLiquidity", + "inputs": [ + { + "name": "ra", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "ct", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "raAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "ctAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "mintedLp", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "who", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RemovedLiquidity", + "inputs": [ + { + "name": "ra", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "ct", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "raAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "ctAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "who", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Swapped", + "inputs": [ + { + "name": "input", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "output", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "amountIn", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "amountOut", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "who", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "baseFeePercentage", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "realizedFeePercentage", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AlreadyInitialized", + "inputs": [] + }, + { + "type": "error", + "name": "Deadline", + "inputs": [] + }, + { + "type": "error", + "name": "DisableNativeLiquidityModification", + "inputs": [] + }, + { + "type": "error", + "name": "Insufficient0Amount", + "inputs": [] + }, + { + "type": "error", + "name": "Insufficient1Amount", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientOutputAmout", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidAmount", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidFee", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidToken", + "inputs": [] + }, + { + "type": "error", + "name": "NoExactIn", + "inputs": [] + }, + { + "type": "error", + "name": "NoSender", + "inputs": [] + }, + { + "type": "error", + "name": "NotEnoughLiquidity", + "inputs": [] + }, + { + "type": "error", + "name": "NotInitialized", + "inputs": [] + }, + { + "type": "error", + "name": "OnlySelfCall", + "inputs": [] + } +] \ No newline at end of file diff --git a/abi/cork_module_core.json b/abi/cork_module_core.json new file mode 100644 index 0000000..d2c20a5 --- /dev/null +++ b/abi/cork_module_core.json @@ -0,0 +1,2251 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [], + "name": "AlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "ERC1967InvalidImplementation", + "type": "error" + }, + { + "inputs": [], + "name": "ERC1967NonPayable", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requested", + "type": "uint256" + } + ], + "name": "InsufficientLiquidity", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "InvalidAsset", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidFees", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "LVDepositPaused", + "type": "error" + }, + { + "inputs": [], + "name": "LVWithdrawalPaused", + "type": "error" + }, + { + "inputs": [], + "name": "NotExpired", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyConfigAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyFlashSwapRouterAllowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "PSMDepositPaused", + "type": "error" + }, + { + "inputs": [], + "name": "PSMWithdrawalPaused", + "type": "error" + }, + { + "inputs": [], + "name": "StateLocked", + "type": "error" + }, + { + "inputs": [], + "name": "UUPSUnauthorizedCallContext", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "name": "UUPSUnsupportedProxiableUUID", + "type": "error" + }, + { + "inputs": [], + "name": "Uinitialized", + "type": "error" + }, + { + "inputs": [], + "name": "Uinitialized", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroDeposit", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "Id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "raAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "swapAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "dSexchangeRates", + "type": "uint256" + } + ], + "name": "Cancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "Id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paReceived", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "raReceived", + "type": "uint256" + } + ], + "name": "CtRedeemed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "Id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "received", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "dsExchangeRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feePrecentage", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "DsRedeemed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "Id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "earlyRedemptionFeeRate", + "type": "uint256" + } + ], + "name": "EarlyRedemptionFeeRateUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "Id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "newEarlyRedemptionFee", + "type": "uint256" + } + ], + "name": "EarlyRedemptionFeeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "pa", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "ra", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "lv", + "type": "address" + } + ], + "name": "InitializedModuleCore", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "Id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "expiry", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "ds", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "ct", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "raCtUniPair", + "type": "address" + } + ], + "name": "Issued", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "LvDeposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "Id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feePrecentage", + "type": "uint256" + } + ], + "name": "LvRedeemEarly", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "Id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isPSMDepositPaused", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isPSMWithdrawalPaused", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isLVDepositPaused", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isLVWithdrawalPaused", + "type": "bool" + } + ], + "name": "PoolsStatusUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "Id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "received", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "exchangeRate", + "type": "uint256" + } + ], + "name": "PsmDeposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "Id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "repurchaseFeeRate", + "type": "uint256" + } + ], + "name": "RepurchaseFeeRateUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "buyer", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "raUsed", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "received", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feePrecentage", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "exchangeRates", + "type": "uint256" + } + ], + "name": "Repurchased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "Id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "currentDsId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "prevDsId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountCtRolledOver", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "dsReceived", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ctReceived", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paReceived", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "exchangeRate", + "type": "uint256" + } + ], + "name": "RolledOver", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "Id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "profit", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "remainingDs", + "type": "uint256" + } + ], + "name": "RolloverProfitClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [], + "name": "UPGRADE_INTERFACE_VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + } + ], + "name": "availableForRepurchase", + "outputs": [ + { + "internalType": "uint256", + "name": "pa", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "ds", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "baseRedemptionFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "claimAutoSellProfit", + "outputs": [ + { + "internalType": "uint256", + "name": "profit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dsReceived", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "depositLv", + "outputs": [ + { + "internalType": "uint256", + "name": "received", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "depositPsm", + "outputs": [ + { + "internalType": "uint256", + "name": "received", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_exchangeRate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + } + ], + "name": "earlyRedemptionFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + } + ], + "name": "exchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "rates", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pa", + "type": "address" + }, + { + "internalType": "address", + "name": "ra", + "type": "address" + } + ], + "name": "getId", + "outputs": [ + { + "internalType": "Id", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_swapAssetFactory", + "type": "address" + }, + { + "internalType": "address", + "name": "_ammFactory", + "type": "address" + }, + { + "internalType": "address", + "name": "_flashSwapRouter", + "type": "address" + }, + { + "internalType": "address", + "name": "_ammRouter", + "type": "address" + }, + { + "internalType": "address", + "name": "_config", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_psmBaseRedemptionFeePrecentage", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pa", + "type": "address" + }, + { + "internalType": "address", + "name": "ra", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lvFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "initialDsPrice", + "type": "uint256" + } + ], + "name": "initializeModuleCore", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "expiry", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exchangeRates", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "repurchaseFeePrecentage", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "decayDiscountRateInDays", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rolloverPeriodInblocks", + "type": "uint256" + } + ], + "name": "issueNewDs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + } + ], + "name": "lastDsId", + "outputs": [ + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "lvAcceptRolloverProfit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "previewDepositPsm", + "outputs": [ + { + "internalType": "uint256", + "name": "ctReceived", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dsReceived", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "previewLvDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "lv", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "previewRedeemEarlyLv", + "outputs": [ + { + "internalType": "uint256", + "name": "received", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feePrecentage", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "previewRedeemRaWithCtDs", + "outputs": [ + { + "internalType": "uint256", + "name": "ra", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rates", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "previewRedeemRaWithDs", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "previewRedeemWithCt", + "outputs": [ + { + "internalType": "uint256", + "name": "paReceived", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "raReceived", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "previewRepurchase", + "outputs": [ + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "received", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feePrecentage", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exchangeRates", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "provideLiquidityWithFlashSwapFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "proxiableUUID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "profit", + "type": "uint256" + } + ], + "name": "psmAcceptFlashSwapProfit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "rawLvPermitSig", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + } + ], + "name": "redeemEarlyLv", + "outputs": [ + { + "internalType": "uint256", + "name": "received", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feePrecentage", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + } + ], + "name": "redeemEarlyLv", + "outputs": [ + { + "internalType": "uint256", + "name": "received", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feePrecentage", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "redeemRaWithCtDs", + "outputs": [ + { + "internalType": "uint256", + "name": "ra", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rates", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "rawDsPermitSig", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "dsDeadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "rawCtPermitSig", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "ctDeadline", + "type": "uint256" + } + ], + "name": "redeemRaWithCtDs", + "outputs": [ + { + "internalType": "uint256", + "name": "ra", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rates", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "redeemRaWithDs", + "outputs": [ + { + "internalType": "uint256", + "name": "received", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_exchangeRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "rawDsPermitSig", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "redeemRaWithDs", + "outputs": [ + { + "internalType": "uint256", + "name": "received", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_exchangeRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "redeemWithCT", + "outputs": [ + { + "internalType": "uint256", + "name": "accruedPa", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "accruedRa", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "rawCtPermitSig", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "redeemWithCT", + "outputs": [ + { + "internalType": "uint256", + "name": "accruedPa", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "accruedRa", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "repurchase", + "outputs": [ + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "received", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feePrecentage", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exchangeRates", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + } + ], + "name": "repurchaseFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + } + ], + "name": "repurchaseRates", + "outputs": [ + { + "internalType": "uint256", + "name": "rates", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + } + ], + "name": "rolloverCt", + "outputs": [ + { + "internalType": "uint256", + "name": "ctReceived", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dsReceived", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_exchangeRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "paReceived", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "rawCtPermitSig", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "ctDeadline", + "type": "uint256" + } + ], + "name": "rolloverCt", + "outputs": [ + { + "internalType": "uint256", + "name": "ctReceived", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dsReceived", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_exchangeRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "paReceived", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + } + ], + "name": "rolloverProfitRemaining", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "dsId", + "type": "uint256" + } + ], + "name": "swapAsset", + "outputs": [ + { + "internalType": "address", + "name": "ct", + "type": "address" + }, + { + "internalType": "address", + "name": "ds", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + } + ], + "name": "underlyingAsset", + "outputs": [ + { + "internalType": "address", + "name": "ra", + "type": "address" + }, + { + "internalType": "address", + "name": "pa", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "newEarlyRedemptionFeeRate", + "type": "uint256" + } + ], + "name": "updateEarlyRedemptionFeeRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "bool", + "name": "isPSMDepositPaused", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isPSMWithdrawalPaused", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isLVDepositPaused", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isLVWithdrawalPaused", + "type": "bool" + } + ], + "name": "updatePoolsStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "bool", + "name": "status", + "type": "bool" + } + ], + "name": "updatePsmAutoSellStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newPsmBaseRedemptionFeePrecentage", + "type": "uint256" + } + ], + "name": "updatePsmBaseRedemptionFeePrecentage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "newRepurchaseFeePrecentage", + "type": "uint256" + } + ], + "name": "updateRepurchaseFeeRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + } + ], + "name": "valueLocked", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "id", + "type": "bytes32" + } + ], + "name": "vaultLp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/constants/chains.py b/constants/chains.py index f33beef..75defd8 100644 --- a/constants/chains.py +++ b/constants/chains.py @@ -14,3 +14,4 @@ class Chain(Enum): SWELL = "Swell" SOLANA = "Solana" BASE = "Base" + SEPOLIA = "Sepolia" diff --git a/constants/cork.py b/constants/cork.py new file mode 100644 index 0000000..e1618b7 --- /dev/null +++ b/constants/cork.py @@ -0,0 +1,85 @@ +import json +import pathlib +from enum import Enum +from collections import defaultdict +from web3 import Web3 +from constants.chains import Chain +from utils.web3_utils import ( + w3, + w3_sepolia, +) + +class TokenType(Enum): + RA = 1 + PA = 2 + +PAGINATION_SIZE = 2000 + +ZERO_ADDRESS = Web3.to_checksum_address("0x0000000000000000000000000000000000000000") + +ABI_PATH = pathlib.Path(__file__).parent.parent / "abi" +with open(ABI_PATH / "ERC20_abi.json") as f: + ERC20_ABI = json.load(f) +with open(ABI_PATH / "cork_module_core.json") as f: + MODULE_CORE_ABI = json.load(f) +with open(ABI_PATH / "ICorkHook.json") as f: + ICORK_HOOK_ABI = json.load(f) + +PSM_ADDRESS_BY_CHAIN = { + Chain.ETHEREUM: Web3.to_checksum_address("0xF6a5b7319DfBc84EB94872478be98462aA9Aab99"), + Chain.SEPOLIA: Web3.to_checksum_address("0xF6a5b7319DfBc84EB94872478be98462aA9Aab99"), +} + +LV_ADDRESS_BY_CHAIN = PSM_ADDRESS_BY_CHAIN + +AMM_ADDRESS_BY_CHAIN = { + Chain.ETHEREUM: Web3.to_checksum_address("0xf190c07670Db093962814393daCbF833CE02ea88"), + Chain.SEPOLIA: Web3.to_checksum_address("0xf190c07670Db093962814393daCbF833CE02ea88"), +} + +AMM_CONTRACT_BY_CHAIN = { + Chain.ETHEREUM: w3.eth.contract( + address=AMM_ADDRESS_BY_CHAIN[Chain.ETHEREUM], + abi=ICORK_HOOK_ABI, + ), + Chain.SEPOLIA: w3_sepolia.eth.contract( + address=AMM_ADDRESS_BY_CHAIN[Chain.SEPOLIA], + abi=ICORK_HOOK_ABI, + ), +} + +PSM_CONTRACT_BY_CHAIN = { + Chain.ETHEREUM: w3.eth.contract( + address=PSM_ADDRESS_BY_CHAIN[Chain.ETHEREUM], + abi=MODULE_CORE_ABI, + ), + Chain.SEPOLIA: w3_sepolia.eth.contract( + address=PSM_ADDRESS_BY_CHAIN[Chain.SEPOLIA], + abi=MODULE_CORE_ABI, + ), +} + +# block height of first deployment of contract or pair +PSM_USDE_START_BLOCK_BY_CHAIN = { + Chain.ETHEREUM: 21725765, + Chain.SEPOLIA: 7214863, +} + +# block height of first deployment of contract or pair +PSM_SUSDE_START_BLOCK_BY_CHAIN = { + Chain.ETHEREUM: 21725765, + Chain.SEPOLIA: 7214863, +} + +# See: https://docs.ethena.fi/solution-design/key-addresses +# See: https://docs.ethena.fi/resources/testnet +USDE_TOKEN_ADDRESS_FOR_L2 = Web3.to_checksum_address("0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34") +USDE_TOKEN_ADDRESS_BY_CHAIN = defaultdict(lambda: USDE_TOKEN_ADDRESS_FOR_L2, { + Chain.ETHEREUM: Web3.to_checksum_address("0x4c9EDD5852cd905f086C759E8383e09bff1E68B3"), + Chain.SEPOLIA: Web3.to_checksum_address("0x9458caaca74249abbe9e964b3ce155b98ec88ef2"), +}) + +SUSDE_TOKEN_ADDRESS_FOR_L2 = Web3.to_checksum_address("0x211Cc4DD073734dA055fbF44a2b4667d5E5fE5d2") +SUSDE_TOKEN_ADDRESS_BY_CHAIN = defaultdict(lambda: SUSDE_TOKEN_ADDRESS_FOR_L2, { + Chain.ETHEREUM: Web3.to_checksum_address("0x9d39a5de30e57443bff2a8307a4256c8797a3497"), +}) diff --git a/constants/summary_columns.py b/constants/summary_columns.py index f1e6dd8..5242719 100644 --- a/constants/summary_columns.py +++ b/constants/summary_columns.py @@ -33,6 +33,8 @@ class SummaryColumn(Enum): CURVE_LLAMALEND_SHARDS = ("curve_llamalend_shards", SummaryColumnType.ETHENA_PTS) + CORK_PSM_PTS = ("cork_psm_shards", SummaryColumnType.ETHENA_PTS) + CLAIMED_ENA_PTS_EXAMPLE = ("claimed_ena_example", SummaryColumnType.ETHENA_PTS) BEEFY_CACHED_BALANCE_EXAMPLE = ( diff --git a/integrations/cork_susde.py b/integrations/cork_susde.py new file mode 100644 index 0000000..4bf17b6 --- /dev/null +++ b/integrations/cork_susde.py @@ -0,0 +1,736 @@ +import logging +from copy import deepcopy +from decimal import Decimal +from dataclasses import dataclass, field +from typing import Callable, Dict, List, NewType, Optional, Set, NamedTuple + +from web3 import Web3 +from eth_typing import ChecksumAddress + +from constants.chains import Chain +from constants.summary_columns import SummaryColumn +from constants.cork import ( + # AMM_ADDRESS_BY_CHAIN, + AMM_CONTRACT_BY_CHAIN, + SUSDE_TOKEN_ADDRESS_BY_CHAIN, + PSM_SUSDE_START_BLOCK_BY_CHAIN, + PSM_CONTRACT_BY_CHAIN, + LV_ADDRESS_BY_CHAIN, + PAGINATION_SIZE, + ZERO_ADDRESS, + ERC20_ABI, + TokenType, +) + +from integrations.cached_balances_integration import CachedBalancesIntegration +from integrations.integration_ids import IntegrationID +from utils.web3_utils import fetch_events_logs_with_retry, multicall, W3_BY_CHAIN + +######################################################################## +# Terminologies +######################################################################## +# Cork PSM (PSM - Peg Stability Model/Module): +# - Fixed-term PSMs with pools for peg stability between a pair of assets +# Cork AMM (AMM - Automated Market Maker): +# - Fixed-term AMMs with pools to enable swaps between the tokens of each Cork Market +# Cork Vaults (LV - Liquidity Vault): +# - Long-term Vaults that deploy liquidity into Cork PSM pools and Cork AMM pools + +# Typings +TermId = NewType("TermId", int) +PsmShareTokenAddress = NewType("PsmShareTokenAddress", ChecksumAddress) +LpTokenAddress = NewType("LpTokenAddress", ChecksumAddress) +QuoteTokenAddress = NewType("QuoteTokenAddress", ChecksumAddress) +VaultShareTokenAddress = NewType("VaultShareTokenAddress", ChecksumAddress) + +class TermConfig(NamedTuple): + share_token_addr: PsmShareTokenAddress + amm_lp_token_addr: LpTokenAddress + amm_pool_addr: ChecksumAddress + start_block: int + +class PairConfig(NamedTuple): + eligible_asset: TokenType + amm_quote_token_addr: QuoteTokenAddress + vault_share_token_addr: VaultShareTokenAddress + vault_addr: ChecksumAddress + start_block: int + terms: Dict[TermId, TermConfig] + +@dataclass +class PooledBalance: + ''' + PooledBalance is a class that represents the balances of a pool or ERC4626 vault. + It contains the following attributes: + - total_assets: the total amount of the eligible asset in the pool + - total_assets_by_token: a dictionary of the total amount of the other assets in the pool + - total_supply: the total supply of the shares + - shares_by_account: a dictionary that maps each account address to its share of the pool + ''' + pair_config: PairConfig + term_config: Optional[TermConfig] = None + total_assets: int = 0 + # total_assets_by_token: Dict[ChecksumAddress, int | Decimal] = {} + total_supply: int = 0 + shares_by_account: Dict[ChecksumAddress, int] = field(default_factory=dict) + + +class CorkIntegration( + CachedBalancesIntegration +): + def __init__( + self, + integration_id: IntegrationID, + start_block: int, + chain: Chain = Chain.ETHEREUM, + summary_cols: Optional[List[SummaryColumn]] = None, + reward_multiplier: int = 1, + balance_multiplier: int = 1, + excluded_addresses: Optional[Set[ChecksumAddress]] = None, + end_block: Optional[int] = None, + ethereal_multiplier: int = 0, + ethereal_multiplier_func: Optional[Callable[[int, str], int]] = None, + ): + super().__init__( + integration_id, + start_block, + chain, + summary_cols, + reward_multiplier, + balance_multiplier, + excluded_addresses, + end_block, + ethereal_multiplier, + ethereal_multiplier_func, + ) + + self.w3 = W3_BY_CHAIN[self.chain]["w3"] + self.eligible_token_addr = SUSDE_TOKEN_ADDRESS_BY_CHAIN[self.chain] + + self.pair_config_by_id: Dict[bytes, PairConfig] = None + self.psm_contract = PSM_CONTRACT_BY_CHAIN[self.chain] + self.psm_balances_by_share_token: Dict[PsmShareTokenAddress, PooledBalance] = None + + self.amm_contract = AMM_CONTRACT_BY_CHAIN[self.chain] + self.amm_balances_by_lp_token: Dict[LpTokenAddress, PooledBalance] = None + + self.vault_balances_by_vault_share_token: Dict[VaultShareTokenAddress, PooledBalance] = None + + + def update_pair_config( + self, + pair_config_by_id: Dict[bytes, PairConfig], + from_block: int = 0, + to_block: int | str = "latest" + ) -> Dict[bytes, PairConfig]: + # Fetch events that indicates new pair was created + new_pair_events_with_eligible_pa = fetch_events_logs_with_retry( + "Pairs initialized with USDe as PA", + self.psm_contract.events.InitializedModuleCore(), + from_block or self.start_block, + to_block, + filter = { + "pa": self.eligible_token_addr, + }, + ) + new_pair_events_with_eligible_ra = fetch_events_logs_with_retry( + "Pairs initialized with USDe as RA", + self.psm_contract.events.InitializedModuleCore(), + from_block or self.start_block, + to_block, + filter = { + "ra": self.eligible_token_addr, + }, + ) + + # /// @notice Emitted when a new LV and PSM is initialized with a given pair + # /// @param id The PSM id + # /// @param pa The address of the pegged asset + # /// @param ra The address of the redemption asset + # /// @param lv The address of the LV + # /// @param expiry The expiry interval of the DS + # event InitializedModuleCore( + # Id indexed id, address indexed pa, address indexed ra, address lv, uint256 expiry); + # ) + + # Set new pairs to config + pair_config_by_id.update({ + event["args"]["id"]: PairConfig( + eligible_asset=TokenType.PA, + amm_quote_token_addr=Web3.to_checksum_address(event["args"]["ra"]), + vault_share_token_addr=Web3.to_checksum_address(event["args"]["lv"]), + vault_addr=LV_ADDRESS_BY_CHAIN[self.chain], + start_block=event["blockNumber"], + terms={}, + ) + for event in new_pair_events_with_eligible_pa + # if event["args"]["pa"] == self.eligible_token_addr + } | { + event["args"]["id"]: PairConfig( + eligible_asset=TokenType.RA, + amm_quote_token_addr=Web3.to_checksum_address(event["args"]["ra"]), + vault_share_token_addr=Web3.to_checksum_address(event["args"]["lv"]), + vault_addr=LV_ADDRESS_BY_CHAIN[self.chain], + start_block=event["blockNumber"], + terms={}, + ) + for event in new_pair_events_with_eligible_ra + # if event["args"]["ra"] == self.eligible_token_addr + }) + + # For each pair, update term config... + for pair_id, pair_config in pair_config_by_id.items(): + # Fetch events that indicate a new term was issued/started + new_term_events_of_pair = fetch_events_logs_with_retry( + "Issuance on pairs with USDe", + self.psm_contract.events.Issued(), + pair_config.start_block or self.start_block, + to_block, + filter = { + "id": pair_id, + }, + ) + + # Get LP token address + # WORKAROUND: until LP token address is available from Issued event + amm_contract_function = self.amm_contract.functions.getLiquidityToken + amm_calls = [ + ( + self.amm_contract, + amm_contract_function.fn_name, + [pair_config.amm_quote_token_addr, event["args"]["ct"]], + ) + for event in new_term_events_of_pair + ] + multicall_results = multicall(self.w3, amm_calls, to_block) + for event, result in zip(new_term_events_of_pair, multicall_results): + event["args"]["lpt"] = result[0] + + # For each term, update config... + for event in new_term_events_of_pair: + # pylint: disable=line-too-long + # /// @notice Emitted when a new DS is issued for a given PSM + # /// @param id The PSM id + # /// @param dsId The DS id + # /// @param expiry The expiry of the DS + # /// @param ds The address of the DS token + # /// @param ct The address of the CT token + # /// @param raCtUniPairId The id of the uniswap-v4 pair between RA and CT + # event Issued( + # Id indexed id, uint256 indexed dsId, uint256 indexed expiry, address ds, address ct, bytes32 raCtUniPairId + # ) + # pylint: enable=line-too-long + + # the `ds_id` identifies each term, but is not unique globally (across pairs) + term_id: TermId = TermId(event["args"]["dsId"]) + + # the `share_token_addr` is unique and only valid for each term (or issuance) + # the `lp_token_addr` is also unique and only valid for each term (or issuance) + term_config = TermConfig( + share_token_addr=Web3.to_checksum_address(event["args"]["ct"]), + amm_lp_token_addr=Web3.to_checksum_address(event["args"]["lpt"]), + amm_pool_addr=self.amm_contract.address, + start_block=event["blockNumber"], + ) + + # Add new term to config + pair_config.terms[term_id] = term_config + return pair_config_by_id + + + def update_psm_pool_balances( + self, + pool_balances: Dict[PsmShareTokenAddress, PooledBalance], + from_block: int = 0, + to_block: int | str = "latest" + ) -> Dict[PsmShareTokenAddress, PooledBalance]: + # For each pair... + for pair_id, pair_config in self.pair_config_by_id.items(): + # For each term, accumulate all changes on amount of assets locked in PSM... + for term_id, term_config in pair_config.terms.items(): + start_block = max(from_block, term_config.start_block) + + # Fetch various events when assets are going IN or OUT of the PSM pool: + # pylint: disable=line-too-long + + # event PsmDeposited(Id indexed id, uint256 indexed dsId, address indexed depositor, uint256 amount, uint256 received, uint256 exchangeRate) + # IN: RA + # OUT: DS + CT + deposit_events = fetch_events_logs_with_retry( + "Deposit on pairs with USDe", + self.psm_contract.events.PsmDeposited(), + start_block, + to_block, + filter = { + "id": pair_id, + "dsId": int(term_id), + }, + ) + + # event CtRedeemed(Id indexed id, uint256 indexed dsId, address indexed redeemer, uint256 amount, uint256 paReceived, uint256 raReceived) + # IN: CT + # OUT: PA + RA + withdraw_events = fetch_events_logs_with_retry( + "Withdraw using CT on pairs with USDe", + self.psm_contract.events.CtRedeemed(), + start_block, + to_block, + filter = { + "id": pair_id, + "dsId": int(term_id), + }, + ) + + # event DsRedeemed(Id indexed id, uint256 indexed dsId, address indexed redeemer, uint256 paUsed, uint256 dsUsed, uint256 raReceived, uint256 dsExchangeRate, uint256 feePercentage, uint256 fee) + # IN: DS + PA + # OUT: RA + redeem_events = fetch_events_logs_with_retry( + "Redeem using DS on pairs with USDe", + self.psm_contract.events.DsRedeemed(), + start_block, + to_block, + filter = { + "id": pair_id, + "dsId": int(term_id), + }, + ) + + # event Repurchased(Id indexed id, address indexed buyer, uint256 indexed dsId, uint256 raUsed, uint256 receivedPa, uint256 receivedDs, uint256 feePercentage, uint256 fee, uint256 exchangeRates) + # IN: RA + # OUT: PA + DS + repurchase_events = fetch_events_logs_with_retry( + "Repurchase on pairs with USDe", + self.psm_contract.events.Repurchased(), + start_block, + to_block, + filter = { + "id": pair_id, + "dsId": int(term_id), + }, + ) + # pylint: enable=line-too-long + + # TODO: Rollover Events + if pair_config.eligible_asset == TokenType.RA: + balance_in = sum( + [event["args"]["amount"] for event in deposit_events] + + [event["args"]["raUsed"] for event in repurchase_events] + ) + balance_out = sum( + [event["args"]["raReceived"] for event in withdraw_events] + + [event["args"]["raReceived"] for event in redeem_events] + ) + elif pair_config.eligible_asset == TokenType.PA: + balance_in = sum( + [event["args"]["paUsed"] for event in redeem_events] + ) + balance_out = sum( + [event["args"]["paReceived"] for event in withdraw_events] + + [event["args"]["receivedPa"] for event in repurchase_events] + ) + else: + raise NotImplementedError("Token type not yet implemented") + + # Get pooled balances of each PSM + share_token_addr = term_config.share_token_addr + pool_balances.setdefault(share_token_addr, PooledBalance(pair_config, term_config)) + pool = pool_balances[share_token_addr] + + # Update asset balance of PSM pool + pool.total_assets += balance_in - balance_out + + # For each token, accumulate all balance changes from Transfer events... + token_contract = self.w3.eth.contract( + address=share_token_addr, + abi=ERC20_ABI, + ) + + # event Transfer(address indexed from, address indexed to, uint256 value) + token_transfers = fetch_events_logs_with_retry( + "Token transfers of PSM CT token", + token_contract.events.Transfer(), + start_block, + to_block, + ) + + # Update token balance of accounts involved in transfer + for transfer in token_transfers: + value = int(transfer["args"]["value"]) + + sender = Web3.to_checksum_address(transfer["args"]["from"]) + if sender not in self.excluded_addresses: + pool.shares_by_account.setdefault(sender, 0) + pool.shares_by_account[sender] -= value + elif sender == ZERO_ADDRESS: + # token was minted, update total supply + pool.total_supply += value + + recipient = Web3.to_checksum_address(transfer["args"]["to"]) + if recipient not in self.excluded_addresses: + pool.shares_by_account.setdefault(recipient, 0) + pool.shares_by_account[recipient] += value + elif sender == ZERO_ADDRESS: + # token was burned, update total supply + pool.total_supply -= value + return pool_balances + + + def update_amm_pool_balances( + self, + pool_balances: Dict[LpTokenAddress, PooledBalance], + from_block: int = 0, + to_block: int | str = "latest" + ) -> Dict[LpTokenAddress, PooledBalance]: + # For each pair... + for _pair_id, pair_config in self.pair_config_by_id.items(): + # For each term... + for _term_id, term_config in pair_config.terms.items(): + start_block = max(from_block, term_config.start_block) + + # Get pooled balances of each AMM pair + lp_token_addr = term_config.amm_lp_token_addr + pool_balances.setdefault(lp_token_addr, PooledBalance(pair_config, term_config)) + pool = pool_balances[lp_token_addr] + + # For each token, accumulate all balance changes from Transfer events... + token_contract = self.w3.eth.contract( + address=lp_token_addr, + abi=ERC20_ABI, + ) + + # event Transfer(address indexed from, address indexed to, uint256 value) + token_transfers = fetch_events_logs_with_retry( + "Token transfers of AMM LP token", + token_contract.events.Transfer(), + start_block, + to_block, + ) + + # Update token balance of accounts involved in transfer + for transfer in token_transfers: + value = int(transfer["args"]["value"]) + + sender = Web3.to_checksum_address(transfer["args"]["from"]) + if sender not in self.excluded_addresses: + pool.shares_by_account.setdefault(sender, 0) + pool.shares_by_account[sender] -= value + elif sender == ZERO_ADDRESS: + # token was minted, update total supply + pool.total_supply += value + + recipient = Web3.to_checksum_address(transfer["args"]["to"]) + if recipient not in self.excluded_addresses: + pool.shares_by_account.setdefault(recipient, 0) + pool.shares_by_account[recipient] += value + elif sender == ZERO_ADDRESS: + # token was burned, update total supply + pool.total_supply -= value + return pool_balances + + + def update_vault_pool_balances( + self, + pool_balances: Dict[VaultShareTokenAddress, PooledBalance], + from_block: int = 0, + to_block: int | str = "latest" + ) -> Dict[VaultShareTokenAddress, PooledBalance]: + # For each pair... + for _pair_id, pair_config in self.pair_config_by_id.items(): + start_block = max(from_block, pair_config.start_block) + + vault_share_token_addr = pair_config.vault_share_token_addr + pool_balances.setdefault(vault_share_token_addr, PooledBalance(pair_config)) + pool = pool_balances[pool_balances] + + # For each token, accumulate all balance changes from Transfer events... + token_contract = self.w3.eth.contract( + address=vault_share_token_addr, + abi=ERC20_ABI, + ) + + # event Transfer(address indexed from, address indexed to, uint256 value) + token_transfers = fetch_events_logs_with_retry( + "Token transfers of Vault LV token", + token_contract.events.Transfer(), + start_block, + to_block, + ) + + # Update token balance of accounts involved in transfer + for transfer in token_transfers: + value = int(transfer["args"]["value"]) + + sender = Web3.to_checksum_address(transfer["args"]["from"]) + if sender not in self.excluded_addresses: + pool.shares_by_account.setdefault(sender, 0) + pool.shares_by_account[sender] -= value + elif sender == ZERO_ADDRESS: + # token was minted, update total supply + pool.total_supply += value + + recipient = Web3.to_checksum_address(transfer["args"]["to"]) + if recipient not in self.excluded_addresses: + pool.shares_by_account.setdefault(recipient, 0) + pool.shares_by_account[recipient] += value + elif sender == ZERO_ADDRESS: + # token was burned, update total supply + pool.total_supply -= value + return pool_balances + + + def get_block_balances( + self, cached_data: Dict[int, Dict[ChecksumAddress, float]], blocks: List[int] + ) -> Dict[int, Dict[ChecksumAddress, float]]: + # pylint: disable=line-too-long + """Get user balances for specified blocks, using cached data when available. + + Args: + cached_data (Dict[int, Dict[ChecksumAddress, float]]): Dictionary mapping block numbers + to user balances at that block. Used to avoid recomputing known balances. + The inner dictionary maps user addresses to their token balance. + blocks (List[int]): List of block numbers to get balances for. + + Returns: + Dict[int, Dict[ChecksumAddress, float]]: Dictionary mapping block numbers to user balances, + where each inner dictionary maps user addresses to their token balance + at that block. + """ + # pylint: enable=line-too-long + logging.info("Getting block data for claimed USDe") + new_block_data: Dict[int, Dict[ChecksumAddress, float]] = {} + if not blocks: + logging.error("No blocks provided to get_block_balances") + return new_block_data + + sorted_blocks = sorted(blocks) + cache_copy_of_account_bals: Dict[int, Dict[ChecksumAddress, float]] = deepcopy(cached_data) + + for block in sorted_blocks: + # find the closest prev block in the data + # list keys parsed as ints and in descending order + sorted_existing_blocks = sorted( + cache_copy_of_account_bals, + reverse=True, + ) + + # loop through the sorted blocks and find the closest previous block + prev_block = self.start_block - 1 + start = prev_block + 1 + account_bals: Dict[ChecksumAddress, Decimal | float] = {} + + for existing_block in sorted_existing_blocks: + if existing_block < block: + prev_block = existing_block + start = existing_block + 1 + account_bals = deepcopy(cache_copy_of_account_bals[prev_block]) + break + + # Fetch pair config at prev_block if not already done + if self.pair_config_by_id is None: + self.pair_config_by_id = self.update_pair_config({}, to_block=prev_block) + + # Fetch Peg Stability term balances at prev_block if not already done + if self.psm_balances_by_share_token is None: + self.psm_balances_by_share_token = self.update_psm_pool_balances( + {}, to_block=prev_block) + + # Fetch AMM Liquidity Pool term balances at prev_block if not already done + if self.amm_balances_by_lp_token is None: + self.amm_balances_by_lp_token = self.update_amm_pool_balances( + {}, to_block=prev_block) + + # Fetch Vault term balances at prev_block if not already done + if self.vault_balances_by_vault_share_token is None: + self.vault_balances_by_vault_share_token = self.update_vault_pool_balances( + {}, to_block=prev_block) + + # parse events since and update bals + while start <= block: + to_block = min(start + PAGINATION_SIZE, block) + # print(f"Fetching events from {start} to {to_block}") + + # Add new pairs to config + self.update_pair_config(self.pair_config_by_id, start, to_block) + + # Update PSM Pool term balances + self.update_psm_pool_balances(self.psm_balances_by_share_token, start, to_block) + + # Update AMM Pool term balances + self.update_amm_pool_balances(self.amm_balances_by_lp_token, start, to_block) + + # Update Vault Liquidity Pool term balances + self.update_vault_pool_balances( + self.vault_balances_by_vault_share_token, + start, + to_block + ) + + start = to_block + 1 + # continue pagination + + # finished pagination loop, block height reached... + # Filter out non-eligible AMM pools + eligible_amm_balances = { + lp_token_addr: amm_pool + for lp_token_addr, amm_pool in self.amm_balances_by_lp_token.items() + if amm_pool.pair_config.amm_quote_token_addr == self.eligible_token_addr + } + + # Fetch the reserve asset balances of each eligible AMM pool + # Note: We cannot assume that all LP token holders have withdrawn + # remaining reserves after end of epoch/term. + amm_contract_function = self.amm_contract.functions.getReserves + amm_calls = [ + ( + self.amm_contract, + amm_contract_function.fn_name, + [ + amm_pool.pair_config.amm_quote_token_addr, + amm_pool.term_config.share_token_addr + ], + ) + for amm_pool in eligible_amm_balances.values() + ] + multicall_results = multicall(self.w3, amm_calls, block) + + # The results contain the following: + # - The `result[0]` is the total balance of the asset token in the AMM pool + # - The `result[1]` is the total balance of the share token in the AMM pool + for amm_pool, result in zip(eligible_amm_balances.values(), multicall_results): + # Update the total asset balance of each AMM pool + amm_pool.total_assets = result[0] + + # Update the total shares of PSM pool of each AMM pool + # amm_pool.total_assets_by_token[amm_pool.term_config.share_token_addr] = result[1] + + # Attribute Ethena asset balances on AMM pools to their respective LP token holders + for amm_pool in self.amm_balances_by_lp_token.values(): + for account_addr, account_shares in amm_pool.shares_by_account.items(): + amount = ( + Decimal(amm_pool.total_assets) + * Decimal(account_shares) + / Decimal(amm_pool.total_supply) + ) + # If the account_addr is the vault_addr, then we need to attribute the + # Ethena asset balances to the respective LV token holders + if account_addr == amm_pool.pair_config.vault_addr: + vault_share_token_addr = amm_pool.pair_config.vault_share_token_addr + vault = self.vault_balances_by_vault_share_token[vault_share_token_addr] + for account_addr, account_shares in vault.shares_by_account.items(): + account_bals.setdefault(account_addr, Decimal(0)) + account_bals[account_addr] = Decimal(account_bals[account_addr]) + ( + amount + * Decimal(account_shares) + / Decimal(vault.total_supply) + ) + else: + account_bals.setdefault(account_addr, Decimal(0)) + account_bals[account_addr] = Decimal(account_bals[account_addr]) + amount + + # # Attribute PSM share token balances to their respective LP token holders + # if amm_pool.term_config is not None: + # share_token_addr = amm_pool.term_config.share_token_addr + # psm_pool = self.psm_balances_by_share_token[share_token_addr] + # account_share_token_bals = psm_pool.shares_by_account + # share_amount = ( + # Decimal(amm_pool.total_assets_by_token[share_token_addr]) + # * Decimal(account_shares) + # / Decimal(amm_pool.total_supply) + # ) + # account_share_token_bals.setdefault(account_addr, Decimal(0)) + # bal = Decimal(account_share_token_bals[account_addr]) + # account_share_token_bals[account_addr] = bal + share_amount + + # Attribute Ethena asset balances on PSM pools to their respective CT token holders + for psm_pool in self.psm_balances_by_share_token.values(): + for account_addr, account_shares in psm_pool.shares_by_account.items(): + amount = ( + Decimal(psm_pool.total_assets) + * Decimal(account_shares) + / Decimal(psm_pool.total_supply) + ) + # If the account_addr is the vault_addr, then we need to attribute the + # Ethena asset balances to the respective LV token holders + if account_addr == psm_pool.pair_config.vault_addr: + vault_share_token_addr = psm_pool.pair_config.vault_share_token_addr + vault = self.vault_balances_by_vault_share_token[vault_share_token_addr] + for account_addr, account_shares in vault.shares_by_account.items(): + account_bals.setdefault(account_addr, Decimal(0)) + account_bals[account_addr] = Decimal(account_bals[account_addr]) + ( + amount + * Decimal(account_shares) + / Decimal(vault.total_supply) + ) + # If the account_addr is the amm_contract, then we need to attribute the + # Ethena asset balances to the respective LP token holders + elif account_addr == psm_pool.term_config.amm_pool_addr: + lp_token_addr = psm_pool.term_config.amm_lp_token_addr + amm_pool = self.amm_balances_by_lp_token[lp_token_addr] + for account_addr, account_shares in amm_pool.shares_by_account.items(): + account_bals.setdefault(account_addr, Decimal(0)) + account_bals[account_addr] = Decimal(account_bals[account_addr]) + ( + amount + * Decimal(account_shares) + / Decimal(amm_pool.total_supply) + ) + else: + account_bals.setdefault(account_addr, Decimal(0)) + account_bals[account_addr] = Decimal(account_bals[account_addr]) + amount + + # Round off to 4 decimals + for account_addr, account_bal in account_bals.items(): + account_bals[account_addr] = round(account_bal / Decimal(1e18), 4) + + new_block_data[block] = account_bals + cache_copy_of_account_bals[block] = account_bals + return new_block_data + + +if __name__ == "__main__": + # simple tests for the integration + cork_integration = CorkIntegration( + integration_id=IntegrationID.CORK_SUSDE, + start_block=PSM_SUSDE_START_BLOCK_BY_CHAIN[Chain.SEPOLIA], + summary_cols=[SummaryColumn.CORK_PSM_PTS], + chain=Chain.SEPOLIA, + reward_multiplier=50, + excluded_addresses={ + Web3.to_checksum_address("0x0000000000000000000000000000000000000000"), + # AMM_ADDRESS_BY_CHAIN[Chain.SEPOLIA], # exclude Cork AMMs from being counted + }, + ) + + print("=" * 120) + print("Run without cached data", + cork_integration.get_block_balances( + cached_data={}, blocks=[20000000, 20000001, 20000002] + ) + ) + # Example output: + # { + # 20000000: {"0x123": 100, "0x456": 200}, + # 20000001: {"0x123": 101, "0x456": 201}, + # 20000002: {"0x123": 102, "0x456": 202}, + # } + + print("=" * 120, "\n" * 5) + print("Run with cached data", + cork_integration.get_block_balances( + cached_data={ + 20000000: { + Web3.to_checksum_address("0x123"): 100, + Web3.to_checksum_address("0x456"): 200, + }, + 20000001: { + Web3.to_checksum_address("0x123"): 101, + Web3.to_checksum_address("0x456"): 201, + }, + }, + blocks=[20000002], + ) + ) + print("=" * 120) + # Example output: + # { + # 20000002: {"0x123": 102, "0x456": 202}, + # } diff --git a/integrations/cork_usde.py b/integrations/cork_usde.py new file mode 100644 index 0000000..70ca44c --- /dev/null +++ b/integrations/cork_usde.py @@ -0,0 +1,736 @@ +import logging +from copy import deepcopy +from decimal import Decimal +from dataclasses import dataclass, field +from typing import Callable, Dict, List, NewType, Optional, Set, NamedTuple + +from web3 import Web3 +from eth_typing import ChecksumAddress + +from constants.chains import Chain +from constants.summary_columns import SummaryColumn +from constants.cork import ( + # AMM_ADDRESS_BY_CHAIN, + AMM_CONTRACT_BY_CHAIN, + USDE_TOKEN_ADDRESS_BY_CHAIN, + PSM_USDE_START_BLOCK_BY_CHAIN, + PSM_CONTRACT_BY_CHAIN, + LV_ADDRESS_BY_CHAIN, + PAGINATION_SIZE, + ZERO_ADDRESS, + ERC20_ABI, + TokenType, +) + +from integrations.cached_balances_integration import CachedBalancesIntegration +from integrations.integration_ids import IntegrationID +from utils.web3_utils import fetch_events_logs_with_retry, multicall, W3_BY_CHAIN + +######################################################################## +# Terminologies +######################################################################## +# Cork PSM (PSM - Peg Stability Model/Module): +# - Fixed-term PSMs with pools for peg stability between a pair of assets +# Cork AMM (AMM - Automated Market Maker): +# - Fixed-term AMMs with pools to enable swaps between the tokens of each Cork Market +# Cork Vaults (LV - Liquidity Vault): +# - Long-term Vaults that deploy liquidity into Cork PSM pools and Cork AMM pools + +# Typings +TermId = NewType("TermId", int) +PsmShareTokenAddress = NewType("PsmShareTokenAddress", ChecksumAddress) +LpTokenAddress = NewType("LpTokenAddress", ChecksumAddress) +QuoteTokenAddress = NewType("QuoteTokenAddress", ChecksumAddress) +VaultShareTokenAddress = NewType("VaultShareTokenAddress", ChecksumAddress) + +class TermConfig(NamedTuple): + share_token_addr: PsmShareTokenAddress + amm_lp_token_addr: LpTokenAddress + amm_pool_addr: ChecksumAddress + start_block: int + +class PairConfig(NamedTuple): + eligible_asset: TokenType + amm_quote_token_addr: QuoteTokenAddress + vault_share_token_addr: VaultShareTokenAddress + vault_addr: ChecksumAddress + start_block: int + terms: Dict[TermId, TermConfig] + +@dataclass +class PooledBalance: + ''' + PooledBalance is a class that represents the balances of a pool or ERC4626 vault. + It contains the following attributes: + - total_assets: the total amount of the eligible asset in the pool + - total_assets_by_token: a dictionary of the total amount of the other assets in the pool + - total_supply: the total supply of the shares + - shares_by_account: a dictionary that maps each account address to its share of the pool + ''' + pair_config: PairConfig + term_config: Optional[TermConfig] = None + total_assets: int = 0 + # total_assets_by_token: Dict[ChecksumAddress, int | Decimal] = {} + total_supply: int = 0 + shares_by_account: Dict[ChecksumAddress, int] = field(default_factory=dict) + + +class CorkIntegration( + CachedBalancesIntegration +): + def __init__( + self, + integration_id: IntegrationID, + start_block: int, + chain: Chain = Chain.ETHEREUM, + summary_cols: Optional[List[SummaryColumn]] = None, + reward_multiplier: int = 1, + balance_multiplier: int = 1, + excluded_addresses: Optional[Set[ChecksumAddress]] = None, + end_block: Optional[int] = None, + ethereal_multiplier: int = 0, + ethereal_multiplier_func: Optional[Callable[[int, str], int]] = None, + ): + super().__init__( + integration_id, + start_block, + chain, + summary_cols, + reward_multiplier, + balance_multiplier, + excluded_addresses, + end_block, + ethereal_multiplier, + ethereal_multiplier_func, + ) + + self.w3 = W3_BY_CHAIN[self.chain]["w3"] + self.eligible_token_addr = USDE_TOKEN_ADDRESS_BY_CHAIN[self.chain] + + self.pair_config_by_id: Dict[bytes, PairConfig] = None + self.psm_contract = PSM_CONTRACT_BY_CHAIN[self.chain] + self.psm_balances_by_share_token: Dict[PsmShareTokenAddress, PooledBalance] = None + + self.amm_contract = AMM_CONTRACT_BY_CHAIN[self.chain] + self.amm_balances_by_lp_token: Dict[LpTokenAddress, PooledBalance] = None + + self.vault_balances_by_vault_share_token: Dict[VaultShareTokenAddress, PooledBalance] = None + + + def update_pair_config( + self, + pair_config_by_id: Dict[bytes, PairConfig], + from_block: int = 0, + to_block: int | str = "latest" + ) -> Dict[bytes, PairConfig]: + # Fetch events that indicates new pair was created + new_pair_events_with_eligible_pa = fetch_events_logs_with_retry( + "Pairs initialized with USDe as PA", + self.psm_contract.events.InitializedModuleCore(), + from_block or self.start_block, + to_block, + filter = { + "pa": self.eligible_token_addr, + }, + ) + new_pair_events_with_eligible_ra = fetch_events_logs_with_retry( + "Pairs initialized with USDe as RA", + self.psm_contract.events.InitializedModuleCore(), + from_block or self.start_block, + to_block, + filter = { + "ra": self.eligible_token_addr, + }, + ) + + # /// @notice Emitted when a new LV and PSM is initialized with a given pair + # /// @param id The PSM id + # /// @param pa The address of the pegged asset + # /// @param ra The address of the redemption asset + # /// @param lv The address of the LV + # /// @param expiry The expiry interval of the DS + # event InitializedModuleCore( + # Id indexed id, address indexed pa, address indexed ra, address lv, uint256 expiry); + # ) + + # Set new pairs to config + pair_config_by_id.update({ + event["args"]["id"]: PairConfig( + eligible_asset=TokenType.PA, + amm_quote_token_addr=Web3.to_checksum_address(event["args"]["ra"]), + vault_share_token_addr=Web3.to_checksum_address(event["args"]["lv"]), + vault_addr=LV_ADDRESS_BY_CHAIN[self.chain], + start_block=event["blockNumber"], + terms={}, + ) + for event in new_pair_events_with_eligible_pa + # if event["args"]["pa"] == self.eligible_token_addr + } | { + event["args"]["id"]: PairConfig( + eligible_asset=TokenType.RA, + amm_quote_token_addr=Web3.to_checksum_address(event["args"]["ra"]), + vault_share_token_addr=Web3.to_checksum_address(event["args"]["lv"]), + vault_addr=LV_ADDRESS_BY_CHAIN[self.chain], + start_block=event["blockNumber"], + terms={}, + ) + for event in new_pair_events_with_eligible_ra + # if event["args"]["ra"] == self.eligible_token_addr + }) + + # For each pair, update term config... + for pair_id, pair_config in pair_config_by_id.items(): + # Fetch events that indicate a new term was issued/started + new_term_events_of_pair = fetch_events_logs_with_retry( + "Issuance on pairs with USDe", + self.psm_contract.events.Issued(), + pair_config.start_block or self.start_block, + to_block, + filter = { + "id": pair_id, + }, + ) + + # Get LP token address + # WORKAROUND: until LP token address is available from Issued event + amm_contract_function = self.amm_contract.functions.getLiquidityToken + amm_calls = [ + ( + self.amm_contract, + amm_contract_function.fn_name, + [pair_config.amm_quote_token_addr, event["args"]["ct"]], + ) + for event in new_term_events_of_pair + ] + multicall_results = multicall(self.w3, amm_calls, to_block) + for event, result in zip(new_term_events_of_pair, multicall_results): + event["args"]["lpt"] = result[0] + + # For each term, update config... + for event in new_term_events_of_pair: + # pylint: disable=line-too-long + # /// @notice Emitted when a new DS is issued for a given PSM + # /// @param id The PSM id + # /// @param dsId The DS id + # /// @param expiry The expiry of the DS + # /// @param ds The address of the DS token + # /// @param ct The address of the CT token + # /// @param raCtUniPairId The id of the uniswap-v4 pair between RA and CT + # event Issued( + # Id indexed id, uint256 indexed dsId, uint256 indexed expiry, address ds, address ct, bytes32 raCtUniPairId + # ) + # pylint: enable=line-too-long + + # the `ds_id` identifies each term, but is not unique globally (across pairs) + term_id: TermId = TermId(event["args"]["dsId"]) + + # the `share_token_addr` is unique and only valid for each term (or issuance) + # the `lp_token_addr` is also unique and only valid for each term (or issuance) + term_config = TermConfig( + share_token_addr=Web3.to_checksum_address(event["args"]["ct"]), + amm_lp_token_addr=Web3.to_checksum_address(event["args"]["lpt"]), + amm_pool_addr=self.amm_contract.address, + start_block=event["blockNumber"], + ) + + # Add new term to config + pair_config.terms[term_id] = term_config + return pair_config_by_id + + + def update_psm_pool_balances( + self, + pool_balances: Dict[PsmShareTokenAddress, PooledBalance], + from_block: int = 0, + to_block: int | str = "latest" + ) -> Dict[PsmShareTokenAddress, PooledBalance]: + # For each pair... + for pair_id, pair_config in self.pair_config_by_id.items(): + # For each term, accumulate all changes on amount of assets locked in PSM... + for term_id, term_config in pair_config.terms.items(): + start_block = max(from_block, term_config.start_block) + + # Fetch various events when assets are going IN or OUT of the PSM pool: + # pylint: disable=line-too-long + + # event PsmDeposited(Id indexed id, uint256 indexed dsId, address indexed depositor, uint256 amount, uint256 received, uint256 exchangeRate) + # IN: RA + # OUT: DS + CT + deposit_events = fetch_events_logs_with_retry( + "Deposit on pairs with USDe", + self.psm_contract.events.PsmDeposited(), + start_block, + to_block, + filter = { + "id": pair_id, + "dsId": int(term_id), + }, + ) + + # event CtRedeemed(Id indexed id, uint256 indexed dsId, address indexed redeemer, uint256 amount, uint256 paReceived, uint256 raReceived) + # IN: CT + # OUT: PA + RA + withdraw_events = fetch_events_logs_with_retry( + "Withdraw using CT on pairs with USDe", + self.psm_contract.events.CtRedeemed(), + start_block, + to_block, + filter = { + "id": pair_id, + "dsId": int(term_id), + }, + ) + + # event DsRedeemed(Id indexed id, uint256 indexed dsId, address indexed redeemer, uint256 paUsed, uint256 dsUsed, uint256 raReceived, uint256 dsExchangeRate, uint256 feePercentage, uint256 fee) + # IN: DS + PA + # OUT: RA + redeem_events = fetch_events_logs_with_retry( + "Redeem using DS on pairs with USDe", + self.psm_contract.events.DsRedeemed(), + start_block, + to_block, + filter = { + "id": pair_id, + "dsId": int(term_id), + }, + ) + + # event Repurchased(Id indexed id, address indexed buyer, uint256 indexed dsId, uint256 raUsed, uint256 receivedPa, uint256 receivedDs, uint256 feePercentage, uint256 fee, uint256 exchangeRates) + # IN: RA + # OUT: PA + DS + repurchase_events = fetch_events_logs_with_retry( + "Repurchase on pairs with USDe", + self.psm_contract.events.Repurchased(), + start_block, + to_block, + filter = { + "id": pair_id, + "dsId": int(term_id), + }, + ) + # pylint: enable=line-too-long + + # TODO: Rollover Events + if pair_config.eligible_asset == TokenType.RA: + balance_in = sum( + [event["args"]["amount"] for event in deposit_events] + + [event["args"]["raUsed"] for event in repurchase_events] + ) + balance_out = sum( + [event["args"]["raReceived"] for event in withdraw_events] + + [event["args"]["raReceived"] for event in redeem_events] + ) + elif pair_config.eligible_asset == TokenType.PA: + balance_in = sum( + [event["args"]["paUsed"] for event in redeem_events] + ) + balance_out = sum( + [event["args"]["paReceived"] for event in withdraw_events] + + [event["args"]["receivedPa"] for event in repurchase_events] + ) + else: + raise NotImplementedError("Token type not yet implemented") + + # Get pooled balances of each PSM + share_token_addr = term_config.share_token_addr + pool_balances.setdefault(share_token_addr, PooledBalance(pair_config, term_config)) + pool = pool_balances[share_token_addr] + + # Update asset balance of PSM pool + pool.total_assets += balance_in - balance_out + + # For each token, accumulate all balance changes from Transfer events... + token_contract = self.w3.eth.contract( + address=share_token_addr, + abi=ERC20_ABI, + ) + + # event Transfer(address indexed from, address indexed to, uint256 value) + token_transfers = fetch_events_logs_with_retry( + "Token transfers of PSM CT token", + token_contract.events.Transfer(), + start_block, + to_block, + ) + + # Update token balance of accounts involved in transfer + for transfer in token_transfers: + value = int(transfer["args"]["value"]) + + sender = Web3.to_checksum_address(transfer["args"]["from"]) + if sender not in self.excluded_addresses: + pool.shares_by_account.setdefault(sender, 0) + pool.shares_by_account[sender] -= value + elif sender == ZERO_ADDRESS: + # token was minted, update total supply + pool.total_supply += value + + recipient = Web3.to_checksum_address(transfer["args"]["to"]) + if recipient not in self.excluded_addresses: + pool.shares_by_account.setdefault(recipient, 0) + pool.shares_by_account[recipient] += value + elif sender == ZERO_ADDRESS: + # token was burned, update total supply + pool.total_supply -= value + return pool_balances + + + def update_amm_pool_balances( + self, + pool_balances: Dict[LpTokenAddress, PooledBalance], + from_block: int = 0, + to_block: int | str = "latest" + ) -> Dict[LpTokenAddress, PooledBalance]: + # For each pair... + for _pair_id, pair_config in self.pair_config_by_id.items(): + # For each term... + for _term_id, term_config in pair_config.terms.items(): + start_block = max(from_block, term_config.start_block) + + # Get pooled balances of each AMM pair + lp_token_addr = term_config.amm_lp_token_addr + pool_balances.setdefault(lp_token_addr, PooledBalance(pair_config, term_config)) + pool = pool_balances[lp_token_addr] + + # For each token, accumulate all balance changes from Transfer events... + token_contract = self.w3.eth.contract( + address=lp_token_addr, + abi=ERC20_ABI, + ) + + # event Transfer(address indexed from, address indexed to, uint256 value) + token_transfers = fetch_events_logs_with_retry( + "Token transfers of AMM LP token", + token_contract.events.Transfer(), + start_block, + to_block, + ) + + # Update token balance of accounts involved in transfer + for transfer in token_transfers: + value = int(transfer["args"]["value"]) + + sender = Web3.to_checksum_address(transfer["args"]["from"]) + if sender not in self.excluded_addresses: + pool.shares_by_account.setdefault(sender, 0) + pool.shares_by_account[sender] -= value + elif sender == ZERO_ADDRESS: + # token was minted, update total supply + pool.total_supply += value + + recipient = Web3.to_checksum_address(transfer["args"]["to"]) + if recipient not in self.excluded_addresses: + pool.shares_by_account.setdefault(recipient, 0) + pool.shares_by_account[recipient] += value + elif sender == ZERO_ADDRESS: + # token was burned, update total supply + pool.total_supply -= value + return pool_balances + + + def update_vault_pool_balances( + self, + pool_balances: Dict[VaultShareTokenAddress, PooledBalance], + from_block: int = 0, + to_block: int | str = "latest" + ) -> Dict[VaultShareTokenAddress, PooledBalance]: + # For each pair... + for _pair_id, pair_config in self.pair_config_by_id.items(): + start_block = max(from_block, pair_config.start_block) + + vault_share_token_addr = pair_config.vault_share_token_addr + pool_balances.setdefault(vault_share_token_addr, PooledBalance(pair_config)) + pool = pool_balances[pool_balances] + + # For each token, accumulate all balance changes from Transfer events... + token_contract = self.w3.eth.contract( + address=vault_share_token_addr, + abi=ERC20_ABI, + ) + + # event Transfer(address indexed from, address indexed to, uint256 value) + token_transfers = fetch_events_logs_with_retry( + "Token transfers of Vault LV token", + token_contract.events.Transfer(), + start_block, + to_block, + ) + + # Update token balance of accounts involved in transfer + for transfer in token_transfers: + value = int(transfer["args"]["value"]) + + sender = Web3.to_checksum_address(transfer["args"]["from"]) + if sender not in self.excluded_addresses: + pool.shares_by_account.setdefault(sender, 0) + pool.shares_by_account[sender] -= value + elif sender == ZERO_ADDRESS: + # token was minted, update total supply + pool.total_supply += value + + recipient = Web3.to_checksum_address(transfer["args"]["to"]) + if recipient not in self.excluded_addresses: + pool.shares_by_account.setdefault(recipient, 0) + pool.shares_by_account[recipient] += value + elif sender == ZERO_ADDRESS: + # token was burned, update total supply + pool.total_supply -= value + return pool_balances + + + def get_block_balances( + self, cached_data: Dict[int, Dict[ChecksumAddress, float]], blocks: List[int] + ) -> Dict[int, Dict[ChecksumAddress, float]]: + # pylint: disable=line-too-long + """Get user balances for specified blocks, using cached data when available. + + Args: + cached_data (Dict[int, Dict[ChecksumAddress, float]]): Dictionary mapping block numbers + to user balances at that block. Used to avoid recomputing known balances. + The inner dictionary maps user addresses to their token balance. + blocks (List[int]): List of block numbers to get balances for. + + Returns: + Dict[int, Dict[ChecksumAddress, float]]: Dictionary mapping block numbers to user balances, + where each inner dictionary maps user addresses to their token balance + at that block. + """ + # pylint: enable=line-too-long + logging.info("Getting block data for claimed USDe") + new_block_data: Dict[int, Dict[ChecksumAddress, float]] = {} + if not blocks: + logging.error("No blocks provided to get_block_balances") + return new_block_data + + sorted_blocks = sorted(blocks) + cache_copy_of_account_bals: Dict[int, Dict[ChecksumAddress, float]] = deepcopy(cached_data) + + for block in sorted_blocks: + # find the closest prev block in the data + # list keys parsed as ints and in descending order + sorted_existing_blocks = sorted( + cache_copy_of_account_bals, + reverse=True, + ) + + # loop through the sorted blocks and find the closest previous block + prev_block = self.start_block - 1 + start = prev_block + 1 + account_bals: Dict[ChecksumAddress, Decimal | float] = {} + + for existing_block in sorted_existing_blocks: + if existing_block < block: + prev_block = existing_block + start = existing_block + 1 + account_bals = deepcopy(cache_copy_of_account_bals[prev_block]) + break + + # Fetch pair config at prev_block if not already done + if self.pair_config_by_id is None: + self.pair_config_by_id = self.update_pair_config({}, to_block=prev_block) + + # Fetch Peg Stability term balances at prev_block if not already done + if self.psm_balances_by_share_token is None: + self.psm_balances_by_share_token = self.update_psm_pool_balances( + {}, to_block=prev_block) + + # Fetch AMM Liquidity Pool term balances at prev_block if not already done + if self.amm_balances_by_lp_token is None: + self.amm_balances_by_lp_token = self.update_amm_pool_balances( + {}, to_block=prev_block) + + # Fetch Vault term balances at prev_block if not already done + if self.vault_balances_by_vault_share_token is None: + self.vault_balances_by_vault_share_token = self.update_vault_pool_balances( + {}, to_block=prev_block) + + # parse events since and update bals + while start <= block: + to_block = min(start + PAGINATION_SIZE, block) + # print(f"Fetching events from {start} to {to_block}") + + # Add new pairs to config + self.update_pair_config(self.pair_config_by_id, start, to_block) + + # Update PSM Pool term balances + self.update_psm_pool_balances(self.psm_balances_by_share_token, start, to_block) + + # Update AMM Pool term balances + self.update_amm_pool_balances(self.amm_balances_by_lp_token, start, to_block) + + # Update Vault Liquidity Pool term balances + self.update_vault_pool_balances( + self.vault_balances_by_vault_share_token, + start, + to_block + ) + + start = to_block + 1 + # continue pagination + + # finished pagination loop, block height reached... + # Filter out non-eligible AMM pools + eligible_amm_balances = { + lp_token_addr: amm_pool + for lp_token_addr, amm_pool in self.amm_balances_by_lp_token.items() + if amm_pool.pair_config.amm_quote_token_addr == self.eligible_token_addr + } + + # Fetch the reserve asset balances of each eligible AMM pool + # Note: We cannot assume that all LP token holders have withdrawn + # remaining reserves after end of epoch/term. + amm_contract_function = self.amm_contract.functions.getReserves + amm_calls = [ + ( + self.amm_contract, + amm_contract_function.fn_name, + [ + amm_pool.pair_config.amm_quote_token_addr, + amm_pool.term_config.share_token_addr + ], + ) + for amm_pool in eligible_amm_balances.values() + ] + multicall_results = multicall(self.w3, amm_calls, block) + + # The results contain the following: + # - The `result[0]` is the total balance of the asset token in the AMM pool + # - The `result[1]` is the total balance of the share token in the AMM pool + for amm_pool, result in zip(eligible_amm_balances.values(), multicall_results): + # Update the total asset balance of each AMM pool + amm_pool.total_assets = result[0] + + # Update the total shares of PSM pool of each AMM pool + # amm_pool.total_assets_by_token[amm_pool.term_config.share_token_addr] = result[1] + + # Attribute Ethena asset balances on AMM pools to their respective LP token holders + for amm_pool in self.amm_balances_by_lp_token.values(): + for account_addr, account_shares in amm_pool.shares_by_account.items(): + amount = ( + Decimal(amm_pool.total_assets) + * Decimal(account_shares) + / Decimal(amm_pool.total_supply) + ) + # If the account_addr is the vault_addr, then we need to attribute the + # Ethena asset balances to the respective LV token holders + if account_addr == amm_pool.pair_config.vault_addr: + vault_share_token_addr = amm_pool.pair_config.vault_share_token_addr + vault = self.vault_balances_by_vault_share_token[vault_share_token_addr] + for account_addr, account_shares in vault.shares_by_account.items(): + account_bals.setdefault(account_addr, Decimal(0)) + account_bals[account_addr] = Decimal(account_bals[account_addr]) + ( + amount + * Decimal(account_shares) + / Decimal(vault.total_supply) + ) + else: + account_bals.setdefault(account_addr, Decimal(0)) + account_bals[account_addr] = Decimal(account_bals[account_addr]) + amount + + # # Attribute PSM share token balances to their respective LP token holders + # if amm_pool.term_config is not None: + # share_token_addr = amm_pool.term_config.share_token_addr + # psm_pool = self.psm_balances_by_share_token[share_token_addr] + # account_share_token_bals = psm_pool.shares_by_account + # share_amount = ( + # Decimal(amm_pool.total_assets_by_token[share_token_addr]) + # * Decimal(account_shares) + # / Decimal(amm_pool.total_supply) + # ) + # account_share_token_bals.setdefault(account_addr, Decimal(0)) + # bal = Decimal(account_share_token_bals[account_addr]) + # account_share_token_bals[account_addr] = bal + share_amount + + # Attribute Ethena asset balances on PSM pools to their respective CT token holders + for psm_pool in self.psm_balances_by_share_token.values(): + for account_addr, account_shares in psm_pool.shares_by_account.items(): + amount = ( + Decimal(psm_pool.total_assets) + * Decimal(account_shares) + / Decimal(psm_pool.total_supply) + ) + # If the account_addr is the vault_addr, then we need to attribute the + # Ethena asset balances to the respective LV token holders + if account_addr == psm_pool.pair_config.vault_addr: + vault_share_token_addr = psm_pool.pair_config.vault_share_token_addr + vault = self.vault_balances_by_vault_share_token[vault_share_token_addr] + for account_addr, account_shares in vault.shares_by_account.items(): + account_bals.setdefault(account_addr, Decimal(0)) + account_bals[account_addr] = Decimal(account_bals[account_addr]) + ( + amount + * Decimal(account_shares) + / Decimal(vault.total_supply) + ) + # If the account_addr is the amm_contract, then we need to attribute the + # Ethena asset balances to the respective LP token holders + elif account_addr == psm_pool.term_config.amm_pool_addr: + lp_token_addr = psm_pool.term_config.amm_lp_token_addr + amm_pool = self.amm_balances_by_lp_token[lp_token_addr] + for account_addr, account_shares in amm_pool.shares_by_account.items(): + account_bals.setdefault(account_addr, Decimal(0)) + account_bals[account_addr] = Decimal(account_bals[account_addr]) + ( + amount + * Decimal(account_shares) + / Decimal(amm_pool.total_supply) + ) + else: + account_bals.setdefault(account_addr, Decimal(0)) + account_bals[account_addr] = Decimal(account_bals[account_addr]) + amount + + # Round off to 4 decimals + for account_addr, account_bal in account_bals.items(): + account_bals[account_addr] = round(account_bal / Decimal(1e18), 4) + + new_block_data[block] = account_bals + cache_copy_of_account_bals[block] = account_bals + return new_block_data + + +if __name__ == "__main__": + # simple tests for the integration + cork_integration = CorkIntegration( + integration_id=IntegrationID.CORK_USDE, + start_block=PSM_USDE_START_BLOCK_BY_CHAIN[Chain.SEPOLIA], + summary_cols=[SummaryColumn.CORK_PSM_PTS], + chain=Chain.SEPOLIA, + reward_multiplier=50, + excluded_addresses={ + Web3.to_checksum_address("0x0000000000000000000000000000000000000000"), + # AMM_ADDRESS_BY_CHAIN[Chain.SEPOLIA], # exclude Cork AMMs from being counted + }, + ) + + print("=" * 120) + print("Run without cached data", + cork_integration.get_block_balances( + cached_data={}, blocks=[20000000, 20000001, 20000002] + ) + ) + # Example output: + # { + # 20000000: {"0x123": 100, "0x456": 200}, + # 20000001: {"0x123": 101, "0x456": 201}, + # 20000002: {"0x123": 102, "0x456": 202}, + # } + + print("=" * 120, "\n" * 5) + print("Run with cached data", + cork_integration.get_block_balances( + cached_data={ + 20000000: { + Web3.to_checksum_address("0x123"): 100, + Web3.to_checksum_address("0x456"): 200, + }, + 20000001: { + Web3.to_checksum_address("0x123"): 101, + Web3.to_checksum_address("0x456"): 201, + }, + }, + blocks=[20000002], + ) + ) + print("=" * 120) + # Example output: + # { + # 20000002: {"0x123": 102, "0x456": 202}, + # } diff --git a/integrations/integration_ids.py b/integrations/integration_ids.py index 77e01ea..9661639 100644 --- a/integrations/integration_ids.py +++ b/integrations/integration_ids.py @@ -395,6 +395,10 @@ class IntegrationID(Enum): # Fluid FLUID = ("Fluid_susde", "Fluid sUSDe", Token.SUSDE) + # Cork + CORK_USDE = ("cork_usde", "Cork USDe", Token.USDE) + CORK_SUSDE = ("cork_susde", "Cork SUSDe", Token.SUSDE) + # Claimed ENA CLAIMED_ENA_EXAMPLE = ("claimed_ena_example", "Claimed ENA Example", Token.ENA) BEEFY_CACHED_BALANCE_EXAMPLE = ( diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..3225c2f --- /dev/null +++ b/mise.toml @@ -0,0 +1,5 @@ +[tools] +python = "latest" + +[env] +_.python.venv = { path = ".venv", create = true } # create the venv if it doesn't exist diff --git a/utils/web3_utils.py b/utils/web3_utils.py index d7d8c27..c5208ae 100644 --- a/utils/web3_utils.py +++ b/utils/web3_utils.py @@ -2,13 +2,14 @@ import os import time import traceback +from typing import Iterable from dotenv import load_dotenv from eth_abi.abi import decode from datetime import datetime from web3 import Web3 -from web3.types import BlockIdentifier +from web3.types import BlockIdentifier, EventData from utils.slack import slack_message from constants.chains import Chain @@ -34,6 +35,8 @@ w3_swell = Web3(Web3.HTTPProvider(SWELL_NODE_URL)) BASE_NODE_URL = os.getenv("BASE_NODE_URL") w3_base = Web3(Web3.HTTPProvider(BASE_NODE_URL)) +SEPOLIA_NODE_URL = os.getenv("SEPOLIA_NODE_URL") +w3_sepolia = Web3(Web3.HTTPProvider(SEPOLIA_NODE_URL)) W3_BY_CHAIN = { Chain.ETHEREUM: { @@ -69,6 +72,9 @@ Chain.BASE: { "w3": w3_base, }, + Chain.SEPOLIA: { + "w3": w3_sepolia, + }, } @@ -109,13 +115,13 @@ def fetch_events_logs_with_retry( retries: int = 3, delay: int = 2, filter: dict | None = None, -) -> dict: +) -> Iterable[EventData]: for attempt in range(retries): try: if filter is None: return contract_event.get_logs(fromBlock=from_block, toBlock=to_block) else: - return contract_event.get_logs(filter) + return contract_event.get_logs(argument_filters=filter, fromBlock=from_block, toBlock=to_block) except Exception as e: if attempt < retries - 1: time.sleep(delay)