From 0fae04252ed2aa2824478d42c54c5d95054771ab Mon Sep 17 00:00:00 2001 From: James Tuckett Date: Thu, 25 Apr 2024 11:49:22 +0100 Subject: [PATCH 1/2] feat: MorphoBlue strategies --- .../morphoblue/borrow/deposit-borrow.ts | 127 ++++ .../src/strategies/morphoblue/borrow/open.ts | 136 ++++ .../morphoblue/borrow/payback-withdraw.ts | 117 ++++ .../morphoblue/common/claim-rewards.ts | 42 ++ .../src/strategies/morphoblue/index.ts | 43 ++ .../strategies/morphoblue/multiply/adjust.ts | 335 ++++++++++ .../strategies/morphoblue/multiply/close.ts | 315 +++++++++ .../strategies/morphoblue/multiply/index.ts | 1 + .../strategies/morphoblue/multiply/open.ts | 612 ++++++++++++++++++ .../morphoblue/validation/getMarketRate.ts | 12 + .../strategies/morphoblue/validation/index.ts | 3 + .../validateBorrowUndercollateralized.ts | 33 + .../validation/validateLiquidity.ts | 24 + .../validateWithdrawUndercollateralized.ts | 30 + 14 files changed, 1830 insertions(+) create mode 100644 packages/dma-library/src/strategies/morphoblue/borrow/deposit-borrow.ts create mode 100644 packages/dma-library/src/strategies/morphoblue/borrow/open.ts create mode 100644 packages/dma-library/src/strategies/morphoblue/borrow/payback-withdraw.ts create mode 100644 packages/dma-library/src/strategies/morphoblue/common/claim-rewards.ts create mode 100644 packages/dma-library/src/strategies/morphoblue/index.ts create mode 100644 packages/dma-library/src/strategies/morphoblue/multiply/adjust.ts create mode 100644 packages/dma-library/src/strategies/morphoblue/multiply/close.ts create mode 100644 packages/dma-library/src/strategies/morphoblue/multiply/index.ts create mode 100644 packages/dma-library/src/strategies/morphoblue/multiply/open.ts create mode 100644 packages/dma-library/src/strategies/morphoblue/validation/getMarketRate.ts create mode 100644 packages/dma-library/src/strategies/morphoblue/validation/index.ts create mode 100644 packages/dma-library/src/strategies/morphoblue/validation/validateBorrowUndercollateralized.ts create mode 100644 packages/dma-library/src/strategies/morphoblue/validation/validateLiquidity.ts create mode 100644 packages/dma-library/src/strategies/morphoblue/validation/validateWithdrawUndercollateralized.ts diff --git a/packages/dma-library/src/strategies/morphoblue/borrow/deposit-borrow.ts b/packages/dma-library/src/strategies/morphoblue/borrow/deposit-borrow.ts new file mode 100644 index 00000000..74980084 --- /dev/null +++ b/packages/dma-library/src/strategies/morphoblue/borrow/deposit-borrow.ts @@ -0,0 +1,127 @@ +import { Network } from '@deploy-configurations/types/network' +import { Address } from '@dma-common/types' +import { amountToWei } from '@dma-common/utils/common' +import { operations } from '@dma-library/operations' +import { MorphoBlueStrategyAddresses } from '@dma-library/operations/morphoblue/addresses' +import { validateGenerateCloseToMaxLtv } from '@dma-library/strategies/validation/closeToMaxLtv' +import { MorphoBluePosition, SummerStrategy } from '@dma-library/types' +import { encodeOperation } from '@dma-library/utils/operation' +import { GetCumulativesData, views } from '@dma-library/views' +import { MorphoCumulativesData } from '@dma-library/views/morpho' +import BigNumber from 'bignumber.js' +import { ethers } from 'ethers' + +import { TEN } from '../../../../../dma-common/constants/numbers' +import { validateBorrowUndercollateralized } from '../validation/validateBorrowUndercollateralized' +import { validateLiquidity } from '../validation/validateLiquidity' + +export interface MorphoblueDepositBorrowPayload { + quoteAmount: BigNumber + collateralAmount: BigNumber + collateralPrecision: number + collateralPrice: BigNumber + quotePrice: BigNumber + quotePrecision: number + morphoBlueMarket: string + proxyAddress: Address + user: Address +} + +export interface MorphoBlueCommonDependencies { + provider: ethers.providers.Provider + getCumulatives: GetCumulativesData + network: Network + addresses: MorphoBlueStrategyAddresses + operationExecutor: Address +} + +export type MorphoDepositBorrowStrategy = ( + args: MorphoblueDepositBorrowPayload, + dependencies: MorphoBlueCommonDependencies, +) => Promise> + +export const depositBorrow: MorphoDepositBorrowStrategy = async (args, dependencies) => { + const getPosition = views.morpho.getPosition + const position = await getPosition( + { + collateralPriceUSD: args.collateralPrice, + quotePriceUSD: args.quotePrice, + proxyAddress: args.proxyAddress, + collateralPrecision: args.collateralPrecision, + quotePrecision: args.quotePrecision, + marketId: args.morphoBlueMarket, + }, + { + provider: dependencies.provider, + getCumulatives: dependencies.getCumulatives, + morphoAddress: dependencies.addresses.morphoblue, + }, + ) + + const isDepositingEth = + position.marketParams.collateralToken.toLowerCase() === + dependencies.addresses.tokens.WETH.toLowerCase() + const isBorrowingEth = + position.marketParams.loanToken.toLowerCase() === + dependencies.addresses.tokens.WETH.toLowerCase() + + const operation = await operations.morphoblue.borrow.depositBorrow( + args.collateralAmount.gt(0) + ? { + userFundsTokenAddress: isDepositingEth + ? dependencies.addresses.tokens.ETH + : position.marketParams.collateralToken, + userFundsTokenAmount: amountToWei(args.collateralAmount, args.collateralPrecision), + depositorAddress: args.user, + morphoBlueMarket: { + loanToken: position.marketParams.loanToken, + collateralToken: position.marketParams.collateralToken, + oracle: position.marketParams.oracle, + irm: position.marketParams.irm, + lltv: position.marketParams.lltv.times(TEN.pow(18)), + }, + } + : undefined, + args.quoteAmount.gt(0) + ? { + morphoBlueMarket: { + loanToken: position.marketParams.loanToken, + collateralToken: position.marketParams.collateralToken, + oracle: position.marketParams.oracle, + irm: position.marketParams.irm, + lltv: position.marketParams.lltv.times(TEN.pow(18)), + }, + amountToBorrow: amountToWei(args.quoteAmount, args.quotePrecision), + isEthToken: isBorrowingEth, + } + : undefined, + dependencies.addresses, + dependencies.network, + ) + + const targetPosition = position.deposit(args.collateralAmount).borrow(args.quoteAmount) + + const warnings = [...validateGenerateCloseToMaxLtv(targetPosition, position)] + + const errors = [ + ...validateLiquidity(position, targetPosition, args.quoteAmount), + ...validateBorrowUndercollateralized(targetPosition, position, args.quoteAmount), + ] + + return { + simulation: { + swaps: [], + errors, + warnings, + notices: [], + successes: [], + targetPosition, + position: targetPosition, + }, + tx: { + to: dependencies.operationExecutor, + data: encodeOperation(operation, dependencies), + value: isDepositingEth ? amountToWei(args.collateralAmount, 18).toString() : '0', + }, + } +} diff --git a/packages/dma-library/src/strategies/morphoblue/borrow/open.ts b/packages/dma-library/src/strategies/morphoblue/borrow/open.ts new file mode 100644 index 00000000..053a7a50 --- /dev/null +++ b/packages/dma-library/src/strategies/morphoblue/borrow/open.ts @@ -0,0 +1,136 @@ +import { Network } from '@deploy-configurations/types/network' +import { Address } from '@dma-common/types' +import { amountToWei } from '@dma-common/utils/common' +import { operations } from '@dma-library/operations' +import { MorphoBlueStrategyAddresses } from '@dma-library/operations/morphoblue/addresses' +import { validateGenerateCloseToMaxLtv } from '@dma-library/strategies/validation/closeToMaxLtv' +// import { +// validateBorrowUndercollateralized, +// validateDustLimit, +// validateLiquidity, +// } from '../../validation' +import { MorphoBluePosition, SummerStrategy } from '@dma-library/types' +import { encodeOperation } from '@dma-library/utils/operation' +import { GetCumulativesData, views } from '@dma-library/views' +import { MorphoCumulativesData } from '@dma-library/views/morpho' +import BigNumber from 'bignumber.js' +import { ethers } from 'ethers' + +import { TEN } from '../../../../../dma-common/constants/numbers' +import { validateBorrowUndercollateralized } from '../validation/validateBorrowUndercollateralized' +import { validateLiquidity } from '../validation/validateLiquidity' + +export interface MorphoblueOpenBorrowPayload { + quoteAmount: BigNumber + collateralAmount: BigNumber + collateralPrecision: number + collateralPrice: BigNumber + quotePrice: BigNumber + quotePrecision: number + morphoBlueMarket: string + proxyAddress: Address + user: Address +} + +export interface MorphoBlueCommonDependencies { + provider: ethers.providers.Provider + getCumulatives: GetCumulativesData + network: Network + addresses: MorphoBlueStrategyAddresses + operationExecutor: Address +} + +export type MorphoOpenBorrowStrategy = ( + args: MorphoblueOpenBorrowPayload, + dependencies: MorphoBlueCommonDependencies, +) => Promise> + +export const open: MorphoOpenBorrowStrategy = async (args, dependencies) => { + const getPosition = views.morpho.getPosition + const position = await getPosition( + { + collateralPriceUSD: args.collateralPrice, + quotePriceUSD: args.quotePrice, + proxyAddress: args.proxyAddress, + collateralPrecision: args.collateralPrecision, + quotePrecision: args.quotePrecision, + marketId: args.morphoBlueMarket, + }, + { + provider: dependencies.provider, + getCumulatives: dependencies.getCumulatives, + morphoAddress: dependencies.addresses.morphoblue, + }, + ) + + if (position.collateralAmount.gt(0)) { + throw new Error('Position already exists') + } + + const isDepositingEth = + position.marketParams.collateralToken.toLowerCase() === + dependencies.addresses.tokens.WETH.toLowerCase() + const isBorrowingEth = + position.marketParams.loanToken.toLowerCase() === + dependencies.addresses.tokens.WETH.toLowerCase() + + const operation = await operations.morphoblue.borrow.openDepositBorrow( + { + userFundsTokenAddress: isDepositingEth + ? dependencies.addresses.tokens.ETH + : position.marketParams.collateralToken, + userFundsTokenAmount: amountToWei(args.collateralAmount, args.collateralPrecision), + depositorAddress: args.user, + morphoBlueMarket: { + loanToken: position.marketParams.loanToken, + collateralToken: position.marketParams.collateralToken, + oracle: position.marketParams.oracle, + irm: position.marketParams.irm, + lltv: position.marketParams.lltv.times(TEN.pow(18)), + }, + }, + { + morphoBlueMarket: { + loanToken: position.marketParams.loanToken, + collateralToken: position.marketParams.collateralToken, + oracle: position.marketParams.oracle, + irm: position.marketParams.irm, + lltv: position.marketParams.lltv.times(TEN.pow(18)), + }, + amountToBorrow: amountToWei(args.quoteAmount, args.quotePrecision), + isEthToken: isBorrowingEth, + }, + { + protocol: 'MorphoBlue', + positionType: 'Borrow', + }, + dependencies.addresses, + dependencies.network, + ) + + const targetPosition = position.deposit(args.collateralAmount).borrow(args.quoteAmount) + + const warnings = [...validateGenerateCloseToMaxLtv(targetPosition, position)] + + const errors = [ + ...validateLiquidity(position, targetPosition, args.quoteAmount), + ...validateBorrowUndercollateralized(targetPosition, position, args.quoteAmount), + ] + + return { + simulation: { + swaps: [], + errors, + warnings, + notices: [], + successes: [], + targetPosition, + position: targetPosition, + }, + tx: { + to: dependencies.operationExecutor, + data: encodeOperation(operation, dependencies), + value: isDepositingEth ? amountToWei(args.collateralAmount, 18).toString() : '0', + }, + } +} diff --git a/packages/dma-library/src/strategies/morphoblue/borrow/payback-withdraw.ts b/packages/dma-library/src/strategies/morphoblue/borrow/payback-withdraw.ts new file mode 100644 index 00000000..99386c1b --- /dev/null +++ b/packages/dma-library/src/strategies/morphoblue/borrow/payback-withdraw.ts @@ -0,0 +1,117 @@ +import { Network } from '@deploy-configurations/types/network' +import { Address } from '@dma-common/types' +import { amountToWei } from '@dma-common/utils/common' +import { operations } from '@dma-library/operations' +import { MorphoBlueStrategyAddresses } from '@dma-library/operations/morphoblue/addresses' +import { validateWithdrawCloseToMaxLtv } from '@dma-library/strategies/validation/closeToMaxLtv' +import { MorphoBluePosition, SummerStrategy } from '@dma-library/types' +import { encodeOperation } from '@dma-library/utils/operation' +import { GetCumulativesData, views } from '@dma-library/views' +import { MorphoCumulativesData } from '@dma-library/views/morpho' +import BigNumber from 'bignumber.js' +import { ethers } from 'ethers' + +import { TEN } from '../../../../../dma-common/constants/numbers' +import { validateWithdrawUndercollateralized } from '../validation' + +export interface MorphobluePaybackWithdrawPayload { + quoteAmount: BigNumber + collateralAmount: BigNumber + collateralPrecision: number + collateralPrice: BigNumber + quotePrice: BigNumber + quotePrecision: number + morphoBlueMarket: string + proxyAddress: Address + user: Address +} + +export interface MorphoBlueCommonDependencies { + provider: ethers.providers.Provider + getCumulatives: GetCumulativesData + network: Network + addresses: MorphoBlueStrategyAddresses + operationExecutor: Address +} + +export type MorphoPaybackWithdrawStrategy = ( + args: MorphobluePaybackWithdrawPayload, + dependencies: MorphoBlueCommonDependencies, +) => Promise> + +export const paybackWithdraw: MorphoPaybackWithdrawStrategy = async (args, dependencies) => { + const getPosition = views.morpho.getPosition + const position = await getPosition( + { + collateralPriceUSD: args.collateralPrice, + quotePriceUSD: args.quotePrice, + proxyAddress: args.proxyAddress, + collateralPrecision: args.collateralPrecision, + quotePrecision: args.quotePrecision, + marketId: args.morphoBlueMarket, + }, + { + provider: dependencies.provider, + getCumulatives: dependencies.getCumulatives, + morphoAddress: dependencies.addresses.morphoblue, + }, + ) + + const isPaybackingEth = + position.marketParams.loanToken.toLowerCase() === + dependencies.addresses.tokens.WETH.toLowerCase() + + const amountDebtToPaybackInBaseUnit = amountToWei(args.quoteAmount, args.quotePrecision) + + const operation = await operations.morphoblue.borrow.paybackWithdraw( + { + amountDebtToPaybackInBaseUnit, + proxy: args.proxyAddress, + amountCollateralToWithdrawInBaseUnit: amountToWei( + args.collateralAmount, + args.collateralPrecision, + ), + user: args.user, + morphoBlueMarket: { + loanToken: position.marketParams.loanToken, + collateralToken: position.marketParams.collateralToken, + oracle: position.marketParams.oracle, + irm: position.marketParams.irm, + lltv: position.marketParams.lltv.times(TEN.pow(18)), + }, + isPaybackAll: args.quoteAmount.gte(position.debtAmount), + }, + dependencies.addresses, + dependencies.network, + ) + + const targetPosition = position.payback(args.quoteAmount).withdraw(args.collateralAmount) + + const warnings = [...validateWithdrawCloseToMaxLtv(targetPosition, position)] + + const errors = [ + ...validateWithdrawUndercollateralized( + targetPosition, + position, + args.collateralPrecision, + args.collateralAmount, + ), + ] + + return { + simulation: { + swaps: [], + errors, + warnings, + notices: [], + successes: [], + targetPosition, + position: targetPosition, + }, + tx: { + to: dependencies.operationExecutor, + data: encodeOperation(operation, dependencies), + value: isPaybackingEth ? amountToWei(args.quoteAmount, 18).toString() : '0', + }, + } +} diff --git a/packages/dma-library/src/strategies/morphoblue/common/claim-rewards.ts b/packages/dma-library/src/strategies/morphoblue/common/claim-rewards.ts new file mode 100644 index 00000000..2a857ad4 --- /dev/null +++ b/packages/dma-library/src/strategies/morphoblue/common/claim-rewards.ts @@ -0,0 +1,42 @@ +import { Address, Tx } from '@dma-common/types' +import { Network } from '@dma-library/index' +import { operations } from '@dma-library/operations' +import { CommonDMADependencies } from '@dma-library/types' +import { encodeOperation } from '@dma-library/utils/operation' +import BigNumber from 'bignumber.js' + +import { ZERO } from '../../../../../dma-common/constants/numbers' + +export interface MorphoClaimRewardsDependencies extends CommonDMADependencies { + network: Network +} + +export interface MorphoCloseClaimRewardsPayload { + urds: Address[] + rewards: Address[] + claimable: BigNumber[] + proofs: string[][] +} + +export type MorphoClaimRewardsStrategy = ( + args: MorphoCloseClaimRewardsPayload, + dependencies: MorphoClaimRewardsDependencies, +) => Promise + +export const claimRewards: MorphoClaimRewardsStrategy = async (args, dependencies) => { + const operation = await operations.morphoblue.common.claimRewards( + { + urds: args.urds, + rewards: args.rewards, + claimable: args.claimable.map(item => item.toString()), + proofs: args.proofs, + }, + dependencies.network, + ) + + return { + to: dependencies.operationExecutor, + data: encodeOperation(operation, dependencies), + value: ZERO.toString(), + } +} diff --git a/packages/dma-library/src/strategies/morphoblue/index.ts b/packages/dma-library/src/strategies/morphoblue/index.ts new file mode 100644 index 00000000..c9d0e269 --- /dev/null +++ b/packages/dma-library/src/strategies/morphoblue/index.ts @@ -0,0 +1,43 @@ +import { + depositBorrow as morphoDepositBorrow, + MorphoDepositBorrowStrategy, +} from './borrow/deposit-borrow' +import { MorphoOpenBorrowStrategy, open as morphoblueOpenDepositBorrow } from './borrow/open' +import { + MorphoPaybackWithdrawStrategy, + paybackWithdraw as morphoPaybackWithdraw, +} from './borrow/payback-withdraw' +import { claimRewards, MorphoClaimRewardsStrategy } from './common/claim-rewards' +import { adjustMultiply, MorphoAdjustRiskStrategy } from './multiply/adjust' +import { closeMultiply, MorphoCloseStrategy } from './multiply/close' +import { MorphoOpenMultiplyStrategy, openMultiply } from './multiply/open' + +export const morphoblue: { + borrow: { + depositBorrow: MorphoDepositBorrowStrategy + openDepositBorrow: MorphoOpenBorrowStrategy + paybackWithdraw: MorphoPaybackWithdrawStrategy + } + multiply: { + open: MorphoOpenMultiplyStrategy + close: MorphoCloseStrategy + adjust: MorphoAdjustRiskStrategy + } + common: { + claimRewards: MorphoClaimRewardsStrategy + } +} = { + borrow: { + openDepositBorrow: morphoblueOpenDepositBorrow, + depositBorrow: morphoDepositBorrow, + paybackWithdraw: morphoPaybackWithdraw, + }, + multiply: { + open: openMultiply, + adjust: adjustMultiply, + close: closeMultiply, + }, + common: { + claimRewards: claimRewards, + }, +} diff --git a/packages/dma-library/src/strategies/morphoblue/multiply/adjust.ts b/packages/dma-library/src/strategies/morphoblue/multiply/adjust.ts new file mode 100644 index 00000000..c93b4803 --- /dev/null +++ b/packages/dma-library/src/strategies/morphoblue/multiply/adjust.ts @@ -0,0 +1,335 @@ +import { getNetwork } from '@deploy-configurations/utils/network/index' +import { TEN, ZERO } from '@dma-common/constants' +import { areAddressesEqual } from '@dma-common/utils/addresses' +import { operations } from '@dma-library/operations' +import { MorphoBlueAdjustRiskDownArgs } from '@dma-library/operations/morphoblue/multiply/adjust-risk-down' +import { MorphoBlueAdjustRiskUpArgs } from '@dma-library/operations/morphoblue/multiply/adjust-risk-up' +import { + FlashloanProvider, + IOperation, + MorphoBluePosition, + PositionType, + SwapData, +} from '@dma-library/types' +import { SummerStrategy } from '@dma-library/types/ajna/ajna-strategy' +import * as SwapUtils from '@dma-library/utils/swap' +import * as Domain from '@domain' +import { isRiskIncreasing } from '@domain/utils' +import BigNumber from 'bignumber.js' + +import { + getSwapData, + getTokenSymbol, + MinimalPosition, + MorphoMultiplyDependencies, + prepareMorphoMultiplyDMAPayload, + simulateAdjustment, +} from './open' + +export interface MorphoAdjustMultiplyPayload { + riskRatio: Domain.IRiskRatio + collateralAmount: BigNumber + slippage: BigNumber + position: MorphoBluePosition + quoteTokenPrecision: number + collateralTokenPrecision: number + user: string + dpmProxyAddress: string +} + +export type MorphoAdjustRiskStrategy = ( + args: MorphoAdjustMultiplyPayload, + dependencies: MorphoMultiplyDependencies, +) => Promise> + +const positionType: PositionType = 'Multiply' + +export const adjustMultiply: MorphoAdjustRiskStrategy = ( + args: MorphoAdjustMultiplyPayload, + dependencies: MorphoMultiplyDependencies, +) => { + if (isRiskIncreasing(args.riskRatio.loanToValue, args.position.riskRatio.loanToValue)) { + return adjustRiskUp(args, dependencies) + } else { + return adjustRiskDown(args, dependencies) + } +} + +const adjustRiskUp: MorphoAdjustRiskStrategy = async (args, dependencies) => { + const oraclePrice = args.position.marketPrice + const collateralTokenSymbol = await getTokenSymbol( + args.position.marketParams.collateralToken, + dependencies.provider, + ) + const debtTokenSymbol = await getTokenSymbol( + args.position.marketParams.loanToken, + dependencies.provider, + ) + + const mappedArgs = { + ...args, + collateralTokenSymbol, + quoteTokenSymbol: debtTokenSymbol, + collateralAmount: args.collateralAmount.shiftedBy(args.collateralTokenPrecision), + } + + const mappedPosition: MinimalPosition = { + collateralAmount: args.position.collateralAmount.shiftedBy(args.collateralTokenPrecision), + debtAmount: args.position.debtAmount.shiftedBy(args.quoteTokenPrecision), + riskRatio: args.position.riskRatio, + marketParams: { + loanToken: args.position.marketParams.loanToken, + collateralToken: args.position.marketParams.collateralToken, + }, + } + + // Simulate adjust + const riskIsIncreasing = true + const simulatedAdjustment = await simulateAdjustment( + mappedArgs, + dependencies, + mappedPosition, + riskIsIncreasing, + oraclePrice, + collateralTokenSymbol, + debtTokenSymbol, + ) + + // Get swap data + const { swapData, collectFeeFrom, preSwapFee } = await getSwapData( + mappedArgs, + args.position, + dependencies, + simulatedAdjustment, + riskIsIncreasing, + positionType, + collateralTokenSymbol, + debtTokenSymbol, + ) + + // Build operation + const operation = await buildOperation( + args, + dependencies, + simulatedAdjustment, + swapData, + riskIsIncreasing, + ) + + // Prepare payload + return prepareMorphoMultiplyDMAPayload( + args, + dependencies, + simulatedAdjustment, + operation, + swapData, + collectFeeFrom, + preSwapFee, + riskIsIncreasing, + args.position, + collateralTokenSymbol, + debtTokenSymbol, + ) +} + +const adjustRiskDown: MorphoAdjustRiskStrategy = async (args, dependencies) => { + const oraclePrice = args.position.marketPrice + + const collateralTokenSymbol = await getTokenSymbol( + args.position.marketParams.collateralToken, + dependencies.provider, + ) + const debtTokenSymbol = await getTokenSymbol( + args.position.marketParams.loanToken, + dependencies.provider, + ) + const mappedArgs = { + ...args, + collateralTokenSymbol, + quoteTokenSymbol: debtTokenSymbol, + collateralAmount: args.collateralAmount.shiftedBy(args.collateralTokenPrecision), + } + + const mappedPosition: MinimalPosition = { + collateralAmount: args.position.collateralAmount.shiftedBy(args.collateralTokenPrecision), + debtAmount: args.position.debtAmount.shiftedBy(args.quoteTokenPrecision), + riskRatio: args.position.riskRatio, + marketParams: { + loanToken: args.position.marketParams.loanToken, + collateralToken: args.position.marketParams.collateralToken, + }, + } + + // Simulate adjust + const riskIsIncreasing = false + const simulatedAdjustment = await simulateAdjustment( + mappedArgs, + dependencies, + mappedPosition, + riskIsIncreasing, + oraclePrice, + collateralTokenSymbol, + debtTokenSymbol, + ) + + // Get swap data + const { swapData, collectFeeFrom, preSwapFee } = await getSwapData( + mappedArgs, + args.position, + dependencies, + simulatedAdjustment, + riskIsIncreasing, + positionType, + collateralTokenSymbol, + debtTokenSymbol, + ) + + // Build operation + const operation = await buildOperation( + args, + dependencies, + simulatedAdjustment, + swapData, + riskIsIncreasing, + ) + + // Prepare payload + return prepareMorphoMultiplyDMAPayload( + args, + dependencies, + simulatedAdjustment, + operation, + swapData, + collectFeeFrom, + preSwapFee, + riskIsIncreasing, + args.position, + collateralTokenSymbol, + debtTokenSymbol, + ) +} + +async function buildOperation( + args: MorphoAdjustMultiplyPayload, + dependencies: MorphoMultiplyDependencies, + simulatedAdjust: Domain.ISimulationV2 & Domain.WithSwap, + swapData: SwapData, + riskIsIncreasing: boolean, +): Promise { + /** Not relevant for Ajna */ + const debtTokensDeposited = ZERO + const borrowAmount = simulatedAdjust.delta.debt.minus(debtTokensDeposited) + const collateralTokenSymbol = simulatedAdjust.position.collateral.symbol.toUpperCase() + const debtTokenSymbol = simulatedAdjust.position.debt.symbol.toUpperCase() + const fee = SwapUtils.feeResolver(collateralTokenSymbol, debtTokenSymbol, { + isIncreasingRisk: riskIsIncreasing, + isEarnPosition: SwapUtils.isCorrelatedPosition(collateralTokenSymbol, debtTokenSymbol), + }) + const swapAmountBeforeFees = simulatedAdjust.swap.fromTokenAmount + const collectFeeFrom = SwapUtils.acceptedFeeTokenBySymbol({ + fromTokenSymbol: riskIsIncreasing + ? simulatedAdjust.position.debt.symbol + : simulatedAdjust.position.collateral.symbol, + toTokenSymbol: riskIsIncreasing + ? simulatedAdjust.position.collateral.symbol + : simulatedAdjust.position.debt.symbol, + }) + + const network = await getNetwork(dependencies.provider) + + const morphoBlueMarket = { + loanToken: args.position.marketParams.loanToken, + collateralToken: args.position.marketParams.collateralToken, + oracle: args.position.marketParams.oracle, + irm: args.position.marketParams.irm, + lltv: args.position.marketParams.lltv.times(TEN.pow(18)), + } + + const collateral = { + address: args.position.marketParams.collateralToken, + isEth: areAddressesEqual( + args.position.marketParams.collateralToken, + dependencies.addresses.WETH, + ), + } + + const debt = { + address: args.position.marketParams.loanToken, + isEth: areAddressesEqual(args.position.marketParams.loanToken, dependencies.addresses.WETH), + } + + const swap = { + fee: fee.toNumber(), + data: swapData.exchangeCalldata, + amount: swapAmountBeforeFees, + collectFeeFrom, + receiveAtLeast: swapData.minToTokenAmount, + } + + const addresses = { + morphoblue: dependencies.morphoAddress, + operationExecutor: dependencies.operationExecutor, + tokens: dependencies.addresses, + } + + const proxy = { + address: args.dpmProxyAddress, + isDPMProxy: true, + owner: args.user, + } + + if (riskIsIncreasing) { + const riskUpMultiplyArgs: MorphoBlueAdjustRiskUpArgs = { + morphoBlueMarket, + collateral, + addresses, + proxy, + network, + swap, + debt: { + ...debt, + borrow: { + amount: borrowAmount, + }, + }, + deposit: { + address: args.position.marketParams.collateralToken, + amount: args.collateralAmount.times(TEN.pow(args.collateralTokenPrecision)).integerValue(), + }, + flashloan: { + token: { + amount: Domain.debtToCollateralSwapFlashloan(swapAmountBeforeFees), + address: args.position.marketParams.loanToken, + }, + amount: Domain.debtToCollateralSwapFlashloan(swapAmountBeforeFees), + provider: FlashloanProvider.Balancer, + }, + } + + return await operations.morphoblue.multiply.adjustRiskUp(riskUpMultiplyArgs) + } + const riskDownMultiplyArgs: MorphoBlueAdjustRiskDownArgs = { + morphoBlueMarket, + debt, + swap, + addresses, + proxy, + network, + collateral: { + ...collateral, + withdrawal: { + amount: simulatedAdjust.delta.collateral.abs(), + }, + }, + flashloan: { + token: { + amount: Domain.collateralToDebtSwapFlashloan(swapData.minToTokenAmount), + address: args.position.marketParams.loanToken, + }, + amount: Domain.collateralToDebtSwapFlashloan(swapData.minToTokenAmount), + provider: FlashloanProvider.Balancer, + }, + } + + return await operations.morphoblue.multiply.adjustRiskDown(riskDownMultiplyArgs) +} diff --git a/packages/dma-library/src/strategies/morphoblue/multiply/close.ts b/packages/dma-library/src/strategies/morphoblue/multiply/close.ts new file mode 100644 index 00000000..b7864ef4 --- /dev/null +++ b/packages/dma-library/src/strategies/morphoblue/multiply/close.ts @@ -0,0 +1,315 @@ +import { FEE_ESTIMATE_INFLATOR, ONE, TEN, ZERO } from '@dma-common/constants' +import { CollectFeeFrom } from '@dma-common/types' +import { areAddressesEqual } from '@dma-common/utils/addresses' +import { amountToWei } from '@dma-common/utils/common' +import { calculateFee } from '@dma-common/utils/swap' +import { operations } from '@dma-library/operations' +import { resolveTxValue } from '@dma-library/protocols/ajna' +import * as StrategiesCommon from '@dma-library/strategies/common' +import { + FlashloanProvider, + IOperation, + MorphoBluePosition, + PositionType, + SummerStrategy, + SwapData, +} from '@dma-library/types' +import { StrategyError, StrategyWarning } from '@dma-library/types/ajna/ajna-validations' +import { encodeOperation } from '@dma-library/utils/operation' +import * as SwapUtils from '@dma-library/utils/swap' +import * as Domain from '@domain' +import BigNumber from 'bignumber.js' + +import { getTokenSymbol, MorphoMultiplyDependencies } from './open' + +export interface MorphoCloseMultiplyPayload { + slippage: BigNumber + position: MorphoBluePosition + quoteTokenPrecision: number + collateralTokenPrecision: number + user: string + dpmProxyAddress: string + shouldCloseToCollateral: boolean +} + +export type MorphoCloseStrategy = ( + args: MorphoCloseMultiplyPayload, + dependencies: MorphoMultiplyDependencies, +) => Promise> + +const positionType: PositionType = 'Multiply' + +export const closeMultiply: MorphoCloseStrategy = async (args, dependencies) => { + const position = args.position + + const getSwapData = args.shouldCloseToCollateral + ? getMorphoSwapDataToCloseToCollateral + : getMorphoSwapDataToCloseToDebt + const collateralTokenSymbol = await getTokenSymbol( + args.position.marketParams.collateralToken, + dependencies.provider, + ) + const debtTokenSymbol = await getTokenSymbol( + args.position.marketParams.loanToken, + dependencies.provider, + ) + + const { swapData, collectFeeFrom, preSwapFee } = await getSwapData( + args, + dependencies, + position, + collateralTokenSymbol, + debtTokenSymbol, + ) + + // Build operation + const operation = await buildOperation( + args, + dependencies, + position, + swapData, + preSwapFee, + collectFeeFrom, + collateralTokenSymbol, + debtTokenSymbol, + ) + + // Prepare Payload + const isDepositingEth = + args.position.marketParams.collateralToken.toLowerCase() === + dependencies.addresses.WETH.toLowerCase() + + const targetPosition = args.position.close() + + const fee = SwapUtils.feeResolver(collateralTokenSymbol, debtTokenSymbol, { + isEarnPosition: SwapUtils.isCorrelatedPosition(collateralTokenSymbol, debtTokenSymbol), + isIncreasingRisk: false, + }) + + const postSwapFee = + collectFeeFrom === 'targetToken' ? calculateFee(swapData.toTokenAmount, fee.toNumber()) : ZERO + + const tokenFee = preSwapFee.plus( + postSwapFee.times(ONE.plus(FEE_ESTIMATE_INFLATOR)).integerValue(BigNumber.ROUND_DOWN), + ) + + // Validation + const errors = [ + // Add as required... + ] + + const warnings = [ + // Add as required.. + ] + + return prepareMorphoDMAPayload({ + swaps: [{ ...swapData, collectFeeFrom, tokenFee }], + dependencies, + targetPosition, + data: encodeOperation(operation, dependencies), + errors: errors, + warnings: warnings, + successes: [], + notices: [], + txValue: resolveTxValue(isDepositingEth, ZERO), + }) +} + +async function getMorphoSwapDataToCloseToDebt( + args: MorphoCloseMultiplyPayload, + dependencies: MorphoMultiplyDependencies, + position: MorphoBluePosition, + collateralTokenSymbol: string, + debtTokenSymbol: string, +) { + const swapAmountBeforeFees = amountToWei( + position.collateralAmount, + args.collateralTokenPrecision, + ).integerValue(BigNumber.ROUND_DOWN) + + const fromToken = { + symbol: collateralTokenSymbol, + precision: args.collateralTokenPrecision, + address: position.marketParams.collateralToken, + } + const toToken = { + symbol: debtTokenSymbol, + precision: args.quoteTokenPrecision, + address: position.marketParams.loanToken, + } + + return StrategiesCommon.getSwapDataForCloseToDebt({ + fromToken, + toToken, + slippage: args.slippage, + swapAmountBeforeFees: swapAmountBeforeFees, + getSwapData: dependencies.getSwapData, + }) +} + +async function getMorphoSwapDataToCloseToCollateral( + args: MorphoCloseMultiplyPayload, + dependencies: MorphoMultiplyDependencies, + position: MorphoBluePosition, + collateralTokenSymbol: string, + debtTokenSymbol: string, +) { + const outstandingDebt = amountToWei(position.debtAmount, args.quoteTokenPrecision).integerValue( + BigNumber.ROUND_DOWN, + ) + + const collateralToken = { + symbol: collateralTokenSymbol, + precision: args.collateralTokenPrecision, + address: position.marketParams.collateralToken, + } + const debtToken = { + symbol: debtTokenSymbol, + precision: args.quoteTokenPrecision, + address: position.marketParams.loanToken, + } + + const colPrice = args.position.collateralPrice + const debtPrice = args.position.debtPrice + + return StrategiesCommon.getSwapDataForCloseToCollateral({ + collateralToken, + debtToken, + colPrice, + debtPrice, + slippage: args.slippage, + outstandingDebt, + getSwapData: dependencies.getSwapData, + }) +} + +async function buildOperation( + args: MorphoCloseMultiplyPayload, + dependencies: MorphoMultiplyDependencies, + position: MorphoBluePosition, + swapData: SwapData, + preSwapFee: BigNumber, + collectFeeFrom: CollectFeeFrom, + collateralTokenSymbol: string, + debtTokenSymbol: string, +): Promise { + const DEBT_OFFSET = new BigNumber(1.01) + const amountToFlashloan = amountToWei( + position.debtAmount.times(DEBT_OFFSET), + args.quoteTokenPrecision, + ).integerValue(BigNumber.ROUND_DOWN) + + const lockedCollateralAmount = amountToWei( + position.collateralAmount, + args.collateralTokenPrecision, + ).integerValue(BigNumber.ROUND_DOWN) + + const collateralToken = { + symbol: collateralTokenSymbol, + precision: args.collateralTokenPrecision, + address: position.marketParams.collateralToken, + } + const debtToken = { + symbol: debtTokenSymbol, + precision: args.quoteTokenPrecision, + address: position.marketParams.loanToken, + } + + const fee = SwapUtils.feeResolver(collateralTokenSymbol, debtTokenSymbol, { + isEarnPosition: SwapUtils.isCorrelatedPosition(collateralTokenSymbol, debtTokenSymbol), + isIncreasingRisk: false, + }) + + const collateralAmountToBeSwapped = args.shouldCloseToCollateral + ? swapData.fromTokenAmount.plus(preSwapFee) + : lockedCollateralAmount + + const closeArgs = { + collateral: { + address: collateralToken.address, + isEth: areAddressesEqual(collateralToken.address, dependencies.addresses.WETH), + }, + debt: { + address: debtToken.address, + isEth: areAddressesEqual(debtToken.address, dependencies.addresses.WETH), + }, + swap: { + fee: fee.toNumber(), + data: swapData.exchangeCalldata, + amount: collateralAmountToBeSwapped, + collectFeeFrom, + receiveAtLeast: swapData.minToTokenAmount, + }, + flashloan: { + token: { + amount: Domain.debtToCollateralSwapFlashloan(amountToFlashloan), + address: position.marketParams.loanToken, + }, + provider: FlashloanProvider.Balancer, + amount: Domain.debtToCollateralSwapFlashloan(amountToFlashloan), + }, + position: { + type: positionType, + collateral: { amount: lockedCollateralAmount }, + }, + proxy: { + address: args.dpmProxyAddress, + isDPMProxy: true, + owner: args.user, + }, + addresses: { + morphoblue: dependencies.morphoAddress, + operationExecutor: dependencies.operationExecutor, + tokens: dependencies.addresses, + }, + network: dependencies.network, + amountDebtToPaybackInBaseUnit: amountToFlashloan, + amountCollateralToWithdrawInBaseUnit: lockedCollateralAmount, + morphoBlueMarket: { + loanToken: args.position.marketParams.loanToken, + collateralToken: args.position.marketParams.collateralToken, + oracle: args.position.marketParams.oracle, + irm: args.position.marketParams.irm, + lltv: args.position.marketParams.lltv.times(TEN.pow(18)), + }, + } + + return await operations.morphoblue.multiply.close(closeArgs) +} + +export const prepareMorphoDMAPayload = ({ + dependencies, + targetPosition, + errors, + warnings, + data, + txValue, + swaps, +}: { + dependencies: MorphoMultiplyDependencies + targetPosition: MorphoBluePosition + errors: StrategyError[] + warnings: StrategyWarning[] + notices: [] + successes: [] + data: string + txValue: string + swaps: (SwapData & { collectFeeFrom: 'sourceToken' | 'targetToken'; tokenFee: BigNumber })[] +}): SummerStrategy => { + return { + simulation: { + swaps, + errors, + warnings, + notices: [], + successes: [], + targetPosition, + position: targetPosition, + }, + tx: { + to: dependencies.operationExecutor, + data, + value: txValue, + }, + } +} diff --git a/packages/dma-library/src/strategies/morphoblue/multiply/index.ts b/packages/dma-library/src/strategies/morphoblue/multiply/index.ts new file mode 100644 index 00000000..cd0307cb --- /dev/null +++ b/packages/dma-library/src/strategies/morphoblue/multiply/index.ts @@ -0,0 +1 @@ +export { MorphoOpenMultiplyStrategy, openMultiply } from './open' diff --git a/packages/dma-library/src/strategies/morphoblue/multiply/open.ts b/packages/dma-library/src/strategies/morphoblue/multiply/open.ts new file mode 100644 index 00000000..185b6c70 --- /dev/null +++ b/packages/dma-library/src/strategies/morphoblue/multiply/open.ts @@ -0,0 +1,612 @@ +import { Network } from '@deploy-configurations/types/network' +import { getNetwork } from '@deploy-configurations/utils/network/index' +import { ONE, TEN, ZERO } from '@dma-common/constants' +import { Address, CollectFeeFrom } from '@dma-common/types' +import { areAddressesEqual } from '@dma-common/utils/addresses' +import { amountFromWei, amountToWei } from '@dma-common/utils/common' +import { calculateFee } from '@dma-common/utils/swap' +import { BALANCER_FEE } from '@dma-library/config/flashloan-fees' +import { operations } from '@dma-library/operations' +import { TokenAddresses } from '@dma-library/operations/morphoblue/addresses' +import { MorphoBlueOpenOperationArgs } from '@dma-library/operations/morphoblue/multiply/open' +import { resolveTxValue } from '@dma-library/protocols/ajna' +import { + validateBorrowUndercollateralized, + validateWithdrawUndercollateralized, +} from '@dma-library/strategies/morphoblue/validation' +import { validateLiquidity } from '@dma-library/strategies/morphoblue/validation/validateLiquidity' +import { validateGenerateCloseToMaxLtv } from '@dma-library/strategies/validation/closeToMaxLtv' +import { + FlashloanProvider, + IOperation, + MorphoBluePosition, + PositionType, + SwapData, +} from '@dma-library/types' +import { + AjnaError, + AjnaNotice, + AjnaSuccess, + AjnaWarning, + SummerStrategy, +} from '@dma-library/types/ajna' +import { CommonDMADependencies, GetSwapData } from '@dma-library/types/common' +import { encodeOperation } from '@dma-library/utils/operation' +import * as SwapUtils from '@dma-library/utils/swap' +import { GetCumulativesData, views } from '@dma-library/views' +import { MorphoCumulativesData } from '@dma-library/views/morpho' +import * as Domain from '@domain' +import * as DomainUtils from '@domain/utils' +import BigNumber from 'bignumber.js' +import { ethers, providers } from 'ethers' + +interface MorphoOpenMultiplyPayload { + collateralPriceUSD: BigNumber + quotePriceUSD: BigNumber + marketId: string + dpmProxyAddress: string + collateralTokenPrecision: number + quoteTokenPrecision: number + riskRatio: Domain.IRiskRatio + collateralAmount: BigNumber + slippage: BigNumber + user: string +} + +export interface MorphoMultiplyDependencies extends CommonDMADependencies { + getCumulatives: GetCumulativesData + getSwapData: GetSwapData + morphoAddress: string + network: Network + addresses: TokenAddresses +} + +export type MorphoOpenMultiplyStrategy = ( + args: MorphoOpenMultiplyPayload, + dependencies: MorphoMultiplyDependencies, +) => Promise> + +const positionType: PositionType = 'Multiply' + +export const openMultiply: MorphoOpenMultiplyStrategy = async (args, dependencies) => { + const position = await getPosition(args, dependencies) + const riskIsIncreasing = verifyRiskDirection(args, position) + const oraclePrice = position.marketPrice + const collateralTokenSymbol = await getTokenSymbol( + position.marketParams.collateralToken, + dependencies.provider, + ) + const debtTokenSymbol = await getTokenSymbol( + position.marketParams.loanToken, + dependencies.provider, + ) + const mappedArgs = { + ...args, + collateralAmount: args.collateralAmount.shiftedBy(args.collateralTokenPrecision), + } + const simulatedAdjustment = await simulateAdjustment( + mappedArgs, + dependencies, + position, + riskIsIncreasing, + oraclePrice, + collateralTokenSymbol, + debtTokenSymbol, + ) + + const { swapData, collectFeeFrom, preSwapFee } = await getSwapData( + mappedArgs, + position, + dependencies, + simulatedAdjustment, + riskIsIncreasing, + positionType, + collateralTokenSymbol, + debtTokenSymbol, + ) + const operation = await buildOperation( + args, + dependencies, + position, + simulatedAdjustment, + swapData, + riskIsIncreasing, + ) + + return prepareMorphoMultiplyDMAPayload( + args, + dependencies, + simulatedAdjustment, + operation, + swapData, + collectFeeFrom, + preSwapFee, + riskIsIncreasing, + position, + collateralTokenSymbol, + debtTokenSymbol, + ) +} + +async function getPosition( + args: MorphoOpenMultiplyPayload, + dependencies: MorphoMultiplyDependencies, +) { + const getPosition = views.morpho.getPosition + const position = await getPosition( + { + collateralPriceUSD: args.collateralPriceUSD, + collateralPrecision: args.collateralTokenPrecision, + quotePriceUSD: args.quotePriceUSD, + quotePrecision: args.quoteTokenPrecision, + proxyAddress: args.dpmProxyAddress, + marketId: args.marketId, + }, + dependencies, + ) + + if (position.collateralAmount.gt(0)) { + throw new Error('Position already exists') + } + + return position +} + +function verifyRiskDirection(args: MorphoOpenMultiplyPayload, position: MorphoBluePosition): true { + const riskIsIncreasing = DomainUtils.isRiskIncreasing( + args.riskRatio.loanToValue, + position.riskRatio.loanToValue, + ) + if (!riskIsIncreasing) { + throw new Error('Risk must increase on openMultiply') + } + + return riskIsIncreasing +} + +export interface AdjustArgs { + quoteTokenPrecision: number + collateralTokenPrecision: number + slippage: BigNumber + collateralAmount: BigNumber + riskRatio: Domain.IRiskRatio + dpmProxyAddress: string + user: string +} + +export function buildFromToken( + args: AdjustArgs, + position: MinimalPosition, + isIncreasingRisk: boolean, + collateralTokenSymbol: string, + debtTokenSymbol: string, +) { + if (isIncreasingRisk) { + return { + symbol: debtTokenSymbol, + address: position.marketParams.loanToken, + precision: args.quoteTokenPrecision, + } + } else { + return { + symbol: collateralTokenSymbol, + address: position.marketParams.collateralToken, + precision: args.collateralTokenPrecision, + } + } +} + +export function buildToToken( + args: AdjustArgs, + position: MinimalPosition, + isIncreasingRisk: boolean, + collateralTokenSymbol: string, + debtTokenSymbol: string, +) { + if (isIncreasingRisk) { + return { + symbol: collateralTokenSymbol, + address: position.marketParams.collateralToken, + precision: args.collateralTokenPrecision, + } + } else { + return { + symbol: debtTokenSymbol, + address: position.marketParams.loanToken, + precision: args.quoteTokenPrecision, + } + } +} + +export interface MinimalPosition { + collateralAmount: BigNumber + debtAmount: BigNumber + riskRatio: Domain.IRiskRatio + marketParams: { + loanToken: string + collateralToken: string + } +} + +export async function simulateAdjustment( + args: AdjustArgs, + dependencies: MorphoMultiplyDependencies, + position: MinimalPosition, + riskIsIncreasing: boolean, + oraclePrice: BigNumber, + collateralTokenSymbol: string, + debtTokenSymbol: string, +) { + const fromToken = buildFromToken( + args, + position, + riskIsIncreasing, + collateralTokenSymbol, + debtTokenSymbol, + ) + const toToken = buildToToken( + args, + position, + riskIsIncreasing, + collateralTokenSymbol, + debtTokenSymbol, + ) + const preFlightSwapAmount = amountToWei(ONE, fromToken.precision) + const fee = SwapUtils.feeResolver(fromToken.symbol, toToken.symbol, { + isIncreasingRisk: riskIsIncreasing, + isEarnPosition: SwapUtils.isCorrelatedPosition(fromToken.symbol, toToken.symbol), + }) + + const { swapData: preFlightSwapData } = await SwapUtils.getSwapDataHelper< + typeof dependencies.addresses, + string + >({ + args: { + fromToken, + toToken, + slippage: args.slippage, + fee, + swapAmountBeforeFees: preFlightSwapAmount, + }, + addresses: dependencies.addresses, + services: { + getSwapData: dependencies.getSwapData, + }, + }) + + const preFlightMarketPrice = DomainUtils.standardiseAmountTo18Decimals( + preFlightSwapData.fromTokenAmount, + fromToken.precision, + ).div( + DomainUtils.standardiseAmountTo18Decimals(preFlightSwapData.toTokenAmount, toToken.precision), + ) + + const collectFeeFrom = SwapUtils.acceptedFeeTokenBySymbol({ + fromTokenSymbol: fromToken.symbol, + toTokenSymbol: toToken.symbol, + }) + + const positionAdjustArgs = { + toDeposit: { + collateral: args.collateralAmount, + debt: ZERO, + }, + fees: { + oazo: fee, + flashLoan: BALANCER_FEE, + }, + prices: { + oracle: oraclePrice, + // Get pre-flight market price from 1inch + market: riskIsIncreasing ? preFlightMarketPrice : ONE.div(preFlightMarketPrice), + }, + slippage: args.slippage, + options: { + collectSwapFeeFrom: collectFeeFrom, + }, + network: dependencies.network, + } + + const mappedPosition = { + debt: { + amount: position.debtAmount, + symbol: riskIsIncreasing ? fromToken.symbol : toToken.symbol, + precision: args.quoteTokenPrecision, + }, + collateral: { + amount: position.collateralAmount, + symbol: riskIsIncreasing ? toToken.symbol : fromToken.symbol, + precision: args.collateralTokenPrecision, + }, + riskRatio: position.riskRatio, + } + + return Domain.adjustToTargetRiskRatio(mappedPosition, args.riskRatio, positionAdjustArgs) +} + +async function buildOperation( + args: AdjustArgs, + dependencies: MorphoMultiplyDependencies, + position: MorphoBluePosition, + simulatedAdjust: Domain.ISimulationV2 & Domain.WithSwap, + swapData: SwapData, + riskIsIncreasing: true, +): Promise { + /** Not relevant for Ajna */ + const debtTokensDeposited = ZERO + const borrowAmount = simulatedAdjust.delta.debt.minus(debtTokensDeposited) + const collateralTokenSymbol = simulatedAdjust.position.collateral.symbol.toUpperCase() + const debtTokenSymbol = simulatedAdjust.position.debt.symbol.toUpperCase() + const fee = SwapUtils.feeResolver(collateralTokenSymbol, debtTokenSymbol, { + isIncreasingRisk: riskIsIncreasing, + isEarnPosition: SwapUtils.isCorrelatedPosition(collateralTokenSymbol, debtTokenSymbol), + }) + const swapAmountBeforeFees = simulatedAdjust.swap.fromTokenAmount + const collectFeeFrom = SwapUtils.acceptedFeeTokenBySymbol({ + fromTokenSymbol: simulatedAdjust.position.debt.symbol, + toTokenSymbol: simulatedAdjust.position.collateral.symbol, + }) + + const network = await getNetwork(dependencies.provider) + + const openMultiplyArgs: MorphoBlueOpenOperationArgs = { + morphoBlueMarket: { + loanToken: position.marketParams.loanToken, + collateralToken: position.marketParams.collateralToken, + oracle: position.marketParams.oracle, + irm: position.marketParams.irm, + lltv: position.marketParams.lltv.times(TEN.pow(18)), + }, + collateral: { + address: position.marketParams.collateralToken, + isEth: areAddressesEqual(position.marketParams.collateralToken, dependencies.addresses.WETH), + }, + debt: { + address: position.marketParams.loanToken, + isEth: areAddressesEqual(position.marketParams.loanToken, dependencies.addresses.WETH), + borrow: { + amount: borrowAmount, + }, + }, + deposit: { + address: position.marketParams.collateralToken, + amount: args.collateralAmount.times(TEN.pow(args.collateralTokenPrecision)).integerValue(), + }, + swap: { + fee: fee.toNumber(), + data: swapData.exchangeCalldata, + amount: swapAmountBeforeFees, + collectFeeFrom, + receiveAtLeast: swapData.minToTokenAmount, + }, + flashloan: { + token: { + amount: Domain.debtToCollateralSwapFlashloan(swapAmountBeforeFees), + address: position.marketParams.loanToken, + }, + amount: Domain.debtToCollateralSwapFlashloan(swapAmountBeforeFees), + provider: FlashloanProvider.Balancer, + }, + position: { + type: positionType, + }, + addresses: { + morphoblue: dependencies.morphoAddress, + operationExecutor: dependencies.operationExecutor, + tokens: dependencies.addresses, + }, + proxy: { + address: args.dpmProxyAddress, + isDPMProxy: true, + owner: args.user, + }, + network, + } + return await operations.morphoblue.multiply.open(openMultiplyArgs) +} + +export async function getSwapData( + args: AdjustArgs, + position: MorphoBluePosition, + dependencies: MorphoMultiplyDependencies, + simulatedAdjust: Domain.ISimulationV2 & Domain.WithSwap, + riskIsIncreasing: boolean, + positionType: PositionType, + collateralTokenSymbol: string, + debtTokenSymbol: string, + __feeOverride?: BigNumber, +) { + const swapAmountBeforeFees = simulatedAdjust.swap.fromTokenAmount + const fee = + __feeOverride || + SwapUtils.feeResolver( + simulatedAdjust.position.collateral.symbol, + simulatedAdjust.position.debt.symbol, + { + isIncreasingRisk: riskIsIncreasing, + // Strategy is called open multiply (not open earn) + isEarnPosition: positionType === 'Earn', + }, + ) + const { swapData, collectFeeFrom, preSwapFee } = await SwapUtils.getSwapDataHelper< + typeof dependencies.addresses, + string + >({ + args: { + fromToken: buildFromToken( + args, + position, + riskIsIncreasing, + collateralTokenSymbol, + debtTokenSymbol, + ), + toToken: buildToToken( + args, + position, + riskIsIncreasing, + collateralTokenSymbol, + debtTokenSymbol, + ), + slippage: args.slippage, + fee, + swapAmountBeforeFees: swapAmountBeforeFees, + }, + addresses: dependencies.addresses, + services: { + getSwapData: dependencies.getSwapData, + }, + }) + + return { swapData, collectFeeFrom, preSwapFee } +} + +export async function getTokenSymbol( + token: Address, + provider: providers.Provider, +): Promise { + const erc20 = new ethers.Contract( + token, + [ + { + constant: true, + inputs: [], + name: 'symbol', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + ], + provider, + ) + + const symbol = await erc20.symbol() + + return symbol +} + +export function prepareMorphoMultiplyDMAPayload( + args: AdjustArgs, + dependencies: MorphoMultiplyDependencies, + simulatedAdjustment: Domain.ISimulationV2 & Domain.WithSwap, + operation: IOperation, + swapData: SwapData, + collectFeeFrom: CollectFeeFrom, + preSwapFee: BigNumber, + riskIsIncreasing: boolean, + position: MorphoBluePosition, + collateralTokenSymbol: string, + debtTokenSymbol: string, +) { + const collateralAmount = amountFromWei( + simulatedAdjustment.position.collateral.amount, + simulatedAdjustment.position.collateral.precision, + ) + const debtAmount = amountFromWei( + simulatedAdjustment.position.debt.amount, + simulatedAdjustment.position.debt.precision, + ) + + const targetPosition = new MorphoBluePosition( + position.owner, + collateralAmount, + debtAmount, + position.collateralPrice, + position.debtPrice, + position.marketParams, + position.market, + position.price, + position.rate, + position.pnl, + ) + + const isDepositingEth = areAddressesEqual( + position.marketParams.collateralToken, + dependencies.addresses.WETH, + ) + const txAmount = args.collateralAmount + const fromTokenSymbol = riskIsIncreasing ? debtTokenSymbol : collateralTokenSymbol + const toTokenSymbol = riskIsIncreasing ? collateralTokenSymbol : debtTokenSymbol + const fee = SwapUtils.feeResolver(fromTokenSymbol, toTokenSymbol, { + isIncreasingRisk: riskIsIncreasing, + isEarnPosition: false, + }) + const postSwapFee = + collectFeeFrom === 'sourceToken' ? ZERO : calculateFee(swapData.toTokenAmount, fee.toNumber()) + const tokenFee = preSwapFee.plus(postSwapFee) + + const withdrawUndercollateralized = !riskIsIncreasing + ? validateWithdrawUndercollateralized( + targetPosition, + position, + args.collateralTokenPrecision, + position.collateralAmount.minus(collateralAmount).abs(), + ) + : [] + + const errors = [ + ...validateLiquidity(position, targetPosition, position.debtAmount.minus(debtAmount).abs()), + ...validateBorrowUndercollateralized( + targetPosition, + position, + position.debtAmount.minus(debtAmount).abs(), + ), + ...withdrawUndercollateralized, + ] + + const warnings = [...validateGenerateCloseToMaxLtv(targetPosition, position)] + + return prepareDMAPayload({ + swaps: [{ ...swapData, collectFeeFrom, tokenFee }], + dependencies, + targetPosition, + data: encodeOperation(operation, dependencies), + errors, + warnings, + successes: [], + notices: [], + txValue: resolveTxValue(isDepositingEth, txAmount), + }) +} + +const prepareDMAPayload = ({ + dependencies, + targetPosition, + errors, + warnings, + data, + txValue, + swaps, +}: { + dependencies: CommonDMADependencies + targetPosition: MorphoBluePosition + errors: AjnaError[] + warnings: AjnaWarning[] + notices: AjnaNotice[] + successes: AjnaSuccess[] + data: string + txValue: string + swaps: (SwapData & { collectFeeFrom: 'sourceToken' | 'targetToken'; tokenFee: BigNumber })[] +}): SummerStrategy => { + return { + simulation: { + swaps, + errors, + warnings, + notices: [], + successes: [], + targetPosition, + position: targetPosition, + }, + tx: { + to: dependencies.operationExecutor, + data, + value: txValue, + }, + } +} diff --git a/packages/dma-library/src/strategies/morphoblue/validation/getMarketRate.ts b/packages/dma-library/src/strategies/morphoblue/validation/getMarketRate.ts new file mode 100644 index 00000000..82c6e0b5 --- /dev/null +++ b/packages/dma-library/src/strategies/morphoblue/validation/getMarketRate.ts @@ -0,0 +1,12 @@ +import { BigNumber } from 'bignumber.js' + +export function getMarketRate(rate: string): BigNumber { + return new BigNumber( + Math.E ** + new BigNumber(rate) + .shiftedBy(-18) + .times(3600 * 24 * 365) + .toNumber() - + 1, + ) +} diff --git a/packages/dma-library/src/strategies/morphoblue/validation/index.ts b/packages/dma-library/src/strategies/morphoblue/validation/index.ts new file mode 100644 index 00000000..5898833a --- /dev/null +++ b/packages/dma-library/src/strategies/morphoblue/validation/index.ts @@ -0,0 +1,3 @@ +export * from './getMarketRate' +export * from './validateBorrowUndercollateralized' +export * from './validateWithdrawUndercollateralized' diff --git a/packages/dma-library/src/strategies/morphoblue/validation/validateBorrowUndercollateralized.ts b/packages/dma-library/src/strategies/morphoblue/validation/validateBorrowUndercollateralized.ts new file mode 100644 index 00000000..a65a068e --- /dev/null +++ b/packages/dma-library/src/strategies/morphoblue/validation/validateBorrowUndercollateralized.ts @@ -0,0 +1,33 @@ +import { formatCryptoBalance } from '@dma-common/utils/common' +import { AjnaError, MorphoBluePosition } from '@dma-library/types' +import BigNumber from 'bignumber.js' + +import { validateLiquidity } from './validateLiquidity' + +export function validateBorrowUndercollateralized( + targetPosition: MorphoBluePosition, + position: MorphoBluePosition, + borrowAmount: BigNumber, +): AjnaError[] { + if (validateLiquidity(position, targetPosition, borrowAmount).length > 0) { + return [] + } + + if (targetPosition.riskRatio.loanToValue.gt(targetPosition.maxRiskRatio.loanToValue)) { + const maxDebt = position.debtAvailable( + targetPosition?.collateralAmount || position.collateralAmount, + position.debtAmount, + ) + + return [ + { + name: 'borrow-undercollateralized', + data: { + amount: formatCryptoBalance(maxDebt), + }, + }, + ] + } + + return [] +} diff --git a/packages/dma-library/src/strategies/morphoblue/validation/validateLiquidity.ts b/packages/dma-library/src/strategies/morphoblue/validation/validateLiquidity.ts new file mode 100644 index 00000000..991c88f1 --- /dev/null +++ b/packages/dma-library/src/strategies/morphoblue/validation/validateLiquidity.ts @@ -0,0 +1,24 @@ +import { AjnaError, MorphoBluePosition } from '@dma-library/types' +import { BigNumber } from 'bignumber.js' + +export function validateLiquidity( + position: MorphoBluePosition, + targetPosition: MorphoBluePosition, + borrowAmount: BigNumber, +): AjnaError[] { + const liquidity = position.market.totalSupplyAssets.minus(position.market.totalBorrowAssets) + const isIncreasingRisk = position.riskRatio.loanToValue.lte(targetPosition.riskRatio.loanToValue) + + if (liquidity.lt(borrowAmount) && isIncreasingRisk) { + return [ + { + name: 'not-enough-liquidity', + data: { + amount: liquidity.toString(), + }, + }, + ] + } + + return [] +} diff --git a/packages/dma-library/src/strategies/morphoblue/validation/validateWithdrawUndercollateralized.ts b/packages/dma-library/src/strategies/morphoblue/validation/validateWithdrawUndercollateralized.ts new file mode 100644 index 00000000..644e1ee1 --- /dev/null +++ b/packages/dma-library/src/strategies/morphoblue/validation/validateWithdrawUndercollateralized.ts @@ -0,0 +1,30 @@ +import { formatCryptoBalance } from '@dma-common/utils/common' +import { AjnaError, MorphoBluePosition } from '@dma-library/types' +import BigNumber from 'bignumber.js' + +export function validateWithdrawUndercollateralized( + targetPosition: MorphoBluePosition, + position: MorphoBluePosition, + collateralPrecision: number, + collateralToWithdraw: BigNumber, +): AjnaError[] { + const resolvedDebtAmount = targetPosition?.debtAmount || position.debtAmount + const resolvedLtv = targetPosition?.maxRiskRatio.loanToValue || position.maxRiskRatio.loanToValue + + const withdrawMax = position.collateralAmount + .minus(resolvedDebtAmount.div(resolvedLtv).div(position.price)) + .decimalPlaces(collateralPrecision, BigNumber.ROUND_DOWN) + + if (collateralToWithdraw.gt(withdrawMax)) { + return [ + { + name: 'withdraw-undercollateralized', + data: { + amount: formatCryptoBalance(withdrawMax), + }, + }, + ] + } + + return [] +} From d69e54303134e2072f58374351641f0ea9301ccd Mon Sep 17 00:00:00 2001 From: James Tuckett Date: Thu, 25 Apr 2024 12:20:13 +0100 Subject: [PATCH 2/2] chore: move morpho and related directories to Automation Lib --- .../external/protocols/morphoblue/irm.json | 168 ++ .../external/protocols/morphoblue/morpho.json | 1480 +++++++++++++++++ .../external/protocols/morphoblue/oracle.json | 15 + packages/abis/external/tokens/IERC4626.json | 614 +++++++ .../types/deployment-config/morpho-blue.ts | 2 +- .../types/deployment-config/system-keys.ts | 1 + .../libs/morpho-blue/MarketParamsLib.sol | 21 + .../contracts/libs/morpho-blue/MathLib.sol | 15 + .../contracts/libs/morpho-blue/MorphoLib.sol | 36 + .../libs/morpho-blue/MorphoStorageLib.sol | 44 + .../libs/morpho-blue/SharesMathLib.sol | 33 + .../scripts/deployment/deploy.ts | 62 +- .../tasks/generate-op-tuple/index.ts | 8 +- .../core/operation-executor.test.ts | 18 +- .../test/utils/aave/aave.operation.close.ts | 45 +- .../dma-contracts/utils/deploy-test-system.ts | 2 +- .../dma-library/src/operations/aave/index.ts | 12 +- .../src/operations/ajna/addresses.ts | 2 +- .../src/operations/ajna/adjust-risk-down.ts | 5 +- .../src/operations/ajna/adjust-risk-up.ts | 5 +- .../operations/ajna/close-to-collateral.ts | 29 +- .../src/operations/ajna/close-to-quote.ts | 27 +- .../dma-library/src/operations/ajna/index.ts | 2 +- .../dma-library/src/operations/ajna/open.ts | 9 +- .../dma-library/src/strategies/aave/index.ts | 2 +- .../common/close-to-coll-swap-data.ts | 57 +- .../src/strategies/common/erc4626/deposit.ts | 174 ++ .../validation/validate-max-deposit.ts | 24 + .../validation/validate-max-withdraw.ts | 24 + .../src/strategies/common/erc4626/withdraw.ts | 182 ++ .../strategies/common/generic-swap-data.ts | 62 + .../src/strategies/common/index.ts | 18 + packages/dma-library/src/strategies/index.ts | 6 + .../dma-library/src/strategies/spark/index.ts | 2 +- .../strategies/validation/closeToMaxLtv.ts | 47 + .../types/aave-like/aave-like-position-v2.ts | 202 +++ .../dma-library/src/types/aave-like/index.ts | 2 + .../src/types/ajna/ajna-dependencies.ts | 6 +- .../src/types/ajna/ajna-earn-position.ts | 11 + .../dma-library/src/types/ajna/ajna-pool.ts | 3 + .../src/types/ajna/ajna-position.ts | 71 +- .../src/types/ajna/ajna-strategy.ts | 6 +- .../src/types/ajna/ajna-validations.ts | 66 +- packages/dma-library/src/types/ajna/index.ts | 7 +- .../src/types/common/erc4626-addresses.ts | 13 + .../src/types/common/erc4626-strategies.ts | 55 + .../src/types/common/erc4626-validation.ts | 15 + .../src/types/common/erc4626-view.ts | 338 ++++ .../dma-library/src/types/common/index.ts | 10 + packages/dma-library/src/types/cumulatives.ts | 61 + packages/dma-library/src/types/index.ts | 32 +- .../types/morphoblue/morphoblue-position.ts | 128 +- packages/dma-library/src/types/operations.ts | 24 +- packages/dma-library/src/views/ajna/index.ts | 104 +- .../dma-library/src/views/common/erc4626.ts | 171 ++ .../src/views/common/get-buying-power.ts | 29 + .../dma-library/src/views/common/index.ts | 2 + .../dma-library/src/views/morpho/index.ts | 145 +- 58 files changed, 4479 insertions(+), 275 deletions(-) create mode 100644 packages/abis/external/protocols/morphoblue/irm.json create mode 100644 packages/abis/external/protocols/morphoblue/morpho.json create mode 100644 packages/abis/external/protocols/morphoblue/oracle.json create mode 100644 packages/abis/external/tokens/IERC4626.json create mode 100644 packages/dma-contracts/contracts/libs/morpho-blue/MarketParamsLib.sol create mode 100644 packages/dma-contracts/contracts/libs/morpho-blue/MathLib.sol create mode 100644 packages/dma-contracts/contracts/libs/morpho-blue/MorphoLib.sol create mode 100644 packages/dma-contracts/contracts/libs/morpho-blue/MorphoStorageLib.sol create mode 100644 packages/dma-contracts/contracts/libs/morpho-blue/SharesMathLib.sol create mode 100644 packages/dma-library/src/strategies/common/erc4626/deposit.ts create mode 100644 packages/dma-library/src/strategies/common/erc4626/validation/validate-max-deposit.ts create mode 100644 packages/dma-library/src/strategies/common/erc4626/validation/validate-max-withdraw.ts create mode 100644 packages/dma-library/src/strategies/common/erc4626/withdraw.ts create mode 100644 packages/dma-library/src/strategies/common/generic-swap-data.ts create mode 100644 packages/dma-library/src/strategies/validation/closeToMaxLtv.ts create mode 100644 packages/dma-library/src/types/aave-like/aave-like-position-v2.ts create mode 100644 packages/dma-library/src/types/common/erc4626-addresses.ts create mode 100644 packages/dma-library/src/types/common/erc4626-strategies.ts create mode 100644 packages/dma-library/src/types/common/erc4626-validation.ts create mode 100644 packages/dma-library/src/types/common/erc4626-view.ts create mode 100644 packages/dma-library/src/types/cumulatives.ts create mode 100644 packages/dma-library/src/views/common/erc4626.ts create mode 100644 packages/dma-library/src/views/common/get-buying-power.ts create mode 100644 packages/dma-library/src/views/common/index.ts diff --git a/packages/abis/external/protocols/morphoblue/irm.json b/packages/abis/external/protocols/morphoblue/irm.json new file mode 100644 index 00000000..62802333 --- /dev/null +++ b/packages/abis/external/protocols/morphoblue/irm.json @@ -0,0 +1,168 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "loanToken", + "type": "address" + }, + { + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "irm", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "internalType": "struct MarketParams", + "name": "marketParams", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "totalSupplyAssets", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "totalSupplyShares", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "totalBorrowAssets", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "totalBorrowShares", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "lastUpdate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "fee", + "type": "uint128" + } + ], + "internalType": "struct Market", + "name": "market", + "type": "tuple" + } + ], + "name": "borrowRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "loanToken", + "type": "address" + }, + { + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "irm", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "internalType": "struct MarketParams", + "name": "marketParams", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "totalSupplyAssets", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "totalSupplyShares", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "totalBorrowAssets", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "totalBorrowShares", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "lastUpdate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "fee", + "type": "uint128" + } + ], + "internalType": "struct Market", + "name": "market", + "type": "tuple" + } + ], + "name": "borrowRateView", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/packages/abis/external/protocols/morphoblue/morpho.json b/packages/abis/external/protocols/morphoblue/morpho.json new file mode 100644 index 00000000..d467cd4c --- /dev/null +++ b/packages/abis/external/protocols/morphoblue/morpho.json @@ -0,0 +1,1480 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "loanToken", + "type": "address" + }, + { + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "irm", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "internalType": "struct MarketParams", + "name": "marketParams", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "onBehalf", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "borrow", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "loanToken", + "type": "address" + }, + { + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "irm", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "internalType": "struct MarketParams", + "name": "marketParams", + "type": "tuple" + } + ], + "name": "createMarket", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "irm", + "type": "address" + } + ], + "name": "enableIrm", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "name": "enableLltv", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "slots", + "type": "bytes32[]" + } + ], + "name": "extSloads", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "res", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeRecipient", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "flashLoan", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "", + "type": "bytes32" + } + ], + "name": "idToMarketParams", + "outputs": [ + { + "internalType": "address", + "name": "loanToken", + "type": "address" + }, + { + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "irm", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isAuthorized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isIrmEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "isLltvEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "loanToken", + "type": "address" + }, + { + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "irm", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "internalType": "struct MarketParams", + "name": "marketParams", + "type": "tuple" + }, + { + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "internalType": "uint256", + "name": "seizedAssets", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "repaidShares", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "liquidate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "", + "type": "bytes32" + } + ], + "name": "market", + "outputs": [ + { + "internalType": "uint128", + "name": "totalSupplyAssets", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "totalSupplyShares", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "totalBorrowAssets", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "totalBorrowShares", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "lastUpdate", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "fee", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Id", + "name": "", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "position", + "outputs": [ + { + "internalType": "uint256", + "name": "supplyShares", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "borrowShares", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "collateral", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "loanToken", + "type": "address" + }, + { + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "irm", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "internalType": "struct MarketParams", + "name": "marketParams", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "onBehalf", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "repay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "authorized", + "type": "address" + }, + { + "internalType": "bool", + "name": "newIsAuthorized", + "type": "bool" + } + ], + "name": "setAuthorization", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "authorizer", + "type": "address" + }, + { + "internalType": "address", + "name": "authorized", + "type": "address" + }, + { + "internalType": "bool", + "name": "isAuthorized", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct Authorization", + "name": "authorization", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "internalType": "struct Signature", + "name": "signature", + "type": "tuple" + } + ], + "name": "setAuthorizationWithSig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "loanToken", + "type": "address" + }, + { + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "irm", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "internalType": "struct MarketParams", + "name": "marketParams", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "newFee", + "type": "uint256" + } + ], + "name": "setFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newFeeRecipient", + "type": "address" + } + ], + "name": "setFeeRecipient", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "setOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "loanToken", + "type": "address" + }, + { + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "irm", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "internalType": "struct MarketParams", + "name": "marketParams", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "onBehalf", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "supply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "loanToken", + "type": "address" + }, + { + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "irm", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "internalType": "struct MarketParams", + "name": "marketParams", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "address", + "name": "onBehalf", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "supplyCollateral", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "loanToken", + "type": "address" + }, + { + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "irm", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "internalType": "struct MarketParams", + "name": "marketParams", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "onBehalf", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "loanToken", + "type": "address" + }, + { + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "irm", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "internalType": "struct MarketParams", + "name": "marketParams", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "address", + "name": "onBehalf", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "withdrawCollateral", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "prevBorrowRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "interest", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feeShares", + "type": "uint256" + } + ], + "name": "AccrueInterest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "onBehalf", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "Borrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "address", + "name": "loanToken", + "type": "address" + }, + { + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "irm", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct MarketParams", + "name": "marketParams", + "type": "tuple" + } + ], + "name": "CreateMarket", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "irm", + "type": "address" + } + ], + "name": "EnableIrm", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "lltv", + "type": "uint256" + } + ], + "name": "EnableLltv", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "FlashLoan", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "authorizer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "usedNonce", + "type": "uint256" + } + ], + "name": "IncrementNonce", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "borrower", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "repaidAssets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "repaidShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "seizedAssets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "badDebtShares", + "type": "uint256" + } + ], + "name": "Liquidate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "onBehalf", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "Repay", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "authorizer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "authorized", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newIsAuthorized", + "type": "bool" + } + ], + "name": "SetAuthorization", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newFee", + "type": "uint256" + } + ], + "name": "SetFee", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newFeeRecipient", + "type": "address" + } + ], + "name": "SetFeeRecipient", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "SetOwner", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "onBehalf", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "Supply", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "onBehalf", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "SupplyCollateral", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "onBehalf", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Id", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "onBehalf", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "WithdrawCollateral", + "type": "event" + } +] diff --git a/packages/abis/external/protocols/morphoblue/oracle.json b/packages/abis/external/protocols/morphoblue/oracle.json new file mode 100644 index 00000000..cd356ff1 --- /dev/null +++ b/packages/abis/external/protocols/morphoblue/oracle.json @@ -0,0 +1,15 @@ +[ + { + "inputs": [], + "name": "price", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/packages/abis/external/tokens/IERC4626.json b/packages/abis/external/tokens/IERC4626.json new file mode 100644 index 00000000..80a3929f --- /dev/null +++ b/packages/abis/external/tokens/IERC4626.json @@ -0,0 +1,614 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "asset", + "outputs": [ + { + "internalType": "address", + "name": "assetTokenAddress", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "convertToAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "convertToShares", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "maxDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "maxAssets", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "maxMint", + "outputs": [ + { + "internalType": "uint256", + "name": "maxShares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "maxRedeem", + "outputs": [ + { + "internalType": "uint256", + "name": "maxShares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "maxWithdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "maxAssets", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "previewDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "previewMint", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "previewRedeem", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "previewWithdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "redeem", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "totalManagedAssets", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/packages/deploy-configurations/types/deployment-config/morpho-blue.ts b/packages/deploy-configurations/types/deployment-config/morpho-blue.ts index 86e4213a..fe623717 100644 --- a/packages/deploy-configurations/types/deployment-config/morpho-blue.ts +++ b/packages/deploy-configurations/types/deployment-config/morpho-blue.ts @@ -1,5 +1,5 @@ import { ConfigEntry } from './config-entries' -export type MorphoBlueProtocol = 'MorphoBlue' +export type MorphoBlueProtocol = 'MorphoBlue' | 'AdaptiveCurveIrm' export type MorphoBlueProtocolContracts = Record diff --git a/packages/deploy-configurations/types/deployment-config/system-keys.ts b/packages/deploy-configurations/types/deployment-config/system-keys.ts index 45a3f865..335fa4f3 100644 --- a/packages/deploy-configurations/types/deployment-config/system-keys.ts +++ b/packages/deploy-configurations/types/deployment-config/system-keys.ts @@ -6,4 +6,5 @@ export enum SystemKeys { MAKER = 'maker', AUTOMATION = 'automation', AJNA = 'ajna', + MORPHO_BLUE = 'morphoblue', } diff --git a/packages/dma-contracts/contracts/libs/morpho-blue/MarketParamsLib.sol b/packages/dma-contracts/contracts/libs/morpho-blue/MarketParamsLib.sol new file mode 100644 index 00000000..a4f6c860 --- /dev/null +++ b/packages/dma-contracts/contracts/libs/morpho-blue/MarketParamsLib.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { Id, MarketParams } from "../../interfaces/morpho-blue/IMorpho.sol"; + +/// @title MarketParamsLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Library to convert a market to its id. +library MarketParamsLib { + /// @notice The length of the data used to compute the id of a market. + /// @dev The length is 5 * 32 because `MarketParams` has 5 variables of 32 bytes each. + uint256 internal constant MARKET_PARAMS_BYTES_LENGTH = 5 * 32; + + /// @notice Returns the id of the market `marketParams`. + function id(MarketParams memory marketParams) internal pure returns (Id marketParamsId) { + assembly ("memory-safe") { + marketParamsId := keccak256(marketParams, MARKET_PARAMS_BYTES_LENGTH) + } + } +} diff --git a/packages/dma-contracts/contracts/libs/morpho-blue/MathLib.sol b/packages/dma-contracts/contracts/libs/morpho-blue/MathLib.sol new file mode 100644 index 00000000..5d61511c --- /dev/null +++ b/packages/dma-contracts/contracts/libs/morpho-blue/MathLib.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +/** DISCLAIMER: This file has been striped down to the minimum needed for Morpho actions */ + +/// @title MathLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Library to manage fixed-point arithmetic. +library MathLib { + /// @dev (x * y) / d rounded up. + function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { + return (x * y + (d - 1)) / d; + } +} diff --git a/packages/dma-contracts/contracts/libs/morpho-blue/MorphoLib.sol b/packages/dma-contracts/contracts/libs/morpho-blue/MorphoLib.sol new file mode 100644 index 00000000..12bdab6e --- /dev/null +++ b/packages/dma-contracts/contracts/libs/morpho-blue/MorphoLib.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { IMorpho, Id } from "../../interfaces/morpho-blue/IMorpho.sol"; +import { MorphoStorageLib } from "./MorphoStorageLib.sol"; + +/** DISCLAIMER: This file has been striped down to the minimum needed for Morpho actions */ + +/// @title MorphoLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Helper library to access Morpho storage variables. +library MorphoLib { + function borrowShares(IMorpho morpho, Id id, address user) internal view returns (uint256) { + bytes32[] memory slot = _array( + MorphoStorageLib.positionBorrowSharesAndCollateralSlot(id, user) + ); + return uint128(uint256(morpho.extSloads(slot)[0])); + } + + function totalBorrowAssets(IMorpho morpho, Id id) internal view returns (uint256) { + bytes32[] memory slot = _array(MorphoStorageLib.marketTotalBorrowAssetsAndSharesSlot(id)); + return uint128(uint256(morpho.extSloads(slot)[0])); + } + + function totalBorrowShares(IMorpho morpho, Id id) internal view returns (uint256) { + bytes32[] memory slot = _array(MorphoStorageLib.marketTotalBorrowAssetsAndSharesSlot(id)); + return uint256(morpho.extSloads(slot)[0] >> 128); + } + + function _array(bytes32 x) private pure returns (bytes32[] memory) { + bytes32[] memory res = new bytes32[](1); + res[0] = x; + return res; + } +} diff --git a/packages/dma-contracts/contracts/libs/morpho-blue/MorphoStorageLib.sol b/packages/dma-contracts/contracts/libs/morpho-blue/MorphoStorageLib.sol new file mode 100644 index 00000000..cf27f18b --- /dev/null +++ b/packages/dma-contracts/contracts/libs/morpho-blue/MorphoStorageLib.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { Id } from "../../interfaces/morpho-blue/IMorpho.sol"; + +/** DISCLAIMER: This file has been striped down to the minimum needed for Morpho actions */ + +/// @title MorphoStorageLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Helper library exposing getters to access Morpho storage variables' slot. +/// @dev This library is not used in Morpho itself and is intended to be used by integrators. +library MorphoStorageLib { + /* SLOTS */ + + uint256 internal constant POSITION_SLOT = 2; + uint256 internal constant MARKET_SLOT = 3; + + /* SLOT OFFSETS */ + + uint256 internal constant BORROW_SHARES_AND_COLLATERAL_OFFSET = 1; + + uint256 internal constant TOTAL_BORROW_ASSETS_AND_SHARES_OFFSET = 1; + + /* GETTERS */ + + function positionBorrowSharesAndCollateralSlot( + Id id, + address user + ) internal pure returns (bytes32) { + return + bytes32( + uint256(keccak256(abi.encode(user, keccak256(abi.encode(id, POSITION_SLOT))))) + + BORROW_SHARES_AND_COLLATERAL_OFFSET + ); + } + + function marketTotalBorrowAssetsAndSharesSlot(Id id) internal pure returns (bytes32) { + return + bytes32( + uint256(keccak256(abi.encode(id, MARKET_SLOT))) + TOTAL_BORROW_ASSETS_AND_SHARES_OFFSET + ); + } +} diff --git a/packages/dma-contracts/contracts/libs/morpho-blue/SharesMathLib.sol b/packages/dma-contracts/contracts/libs/morpho-blue/SharesMathLib.sol new file mode 100644 index 00000000..5020cf55 --- /dev/null +++ b/packages/dma-contracts/contracts/libs/morpho-blue/SharesMathLib.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { MathLib } from "./MathLib.sol"; + +/** DISCLAIMER: This file has been striped down to the minimum needed for Morpho actions */ + +/// @title SharesMathLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Shares management library. +/// @dev This implementation mitigates share price manipulations, using OpenZeppelin's method of virtual shares: +/// https://docs.openzeppelin.com/contracts/4.x/erc4626#inflation-attack. +library SharesMathLib { + using MathLib for uint256; + + /// @dev The number of virtual shares has been chosen low enough to prevent overflows, and high enough to ensure + /// high precision computations. + uint256 internal constant VIRTUAL_SHARES = 1e6; + + /// @dev A number of virtual assets of 1 enforces a conversion rate between shares and assets when a market is + /// empty. + uint256 internal constant VIRTUAL_ASSETS = 1; + + /// @dev Calculates the value of `shares` quoted in assets, rounding up. + function toAssetsUp( + uint256 shares, + uint256 totalAssets, + uint256 totalShares + ) internal pure returns (uint256) { + return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES); + } +} diff --git a/packages/dma-contracts/scripts/deployment/deploy.ts b/packages/dma-contracts/scripts/deployment/deploy.ts index e2ea52c3..f68b624c 100644 --- a/packages/dma-contracts/scripts/deployment/deploy.ts +++ b/packages/dma-contracts/scripts/deployment/deploy.ts @@ -42,34 +42,48 @@ import { getSparkDepositOperationDefinition, getSparkOpenDepositBorrowOperationDefinition, getSparkOpenOperationDefinition, - getSparkPaybackWithdrawOperationDefinition -} from "@deploy-configurations/operation-definitions"; -import { ContractProps, DeployedSystem, System, SystemTemplate } from "@deploy-configurations/types/deployed-system"; + getSparkPaybackWithdrawOperationDefinition, +} from '@deploy-configurations/operation-definitions' +import { + ContractProps, + DeployedSystem, + System, + SystemTemplate, +} from '@deploy-configurations/types/deployed-system' import { ConfigEntry, SystemConfig, SystemConfigEntry, - SystemContracts -} from "@deploy-configurations/types/deployment-config"; -import { EtherscanGasPrice } from "@deploy-configurations/types/etherscan"; -import { Network } from "@deploy-configurations/types/network"; -import { NetworkByChainId } from "@deploy-configurations/utils/network/index"; -import { OperationsRegistry, ServiceRegistry } from "@deploy-configurations/utils/wrappers/index"; -import { loadContractNames } from "@dma-contracts/../deploy-configurations/constants"; -import { RecursivePartial } from "@dma-contracts/utils/recursive-partial"; -import Safe from "@safe-global/safe-core-sdk"; -import { SafeTransactionDataPartial } from "@safe-global/safe-core-sdk-types"; -import EthersAdapter from "@safe-global/safe-ethers-lib"; -import SafeServiceClient from "@safe-global/safe-service-client"; -import axios from "axios"; -import BigNumber from "bignumber.js"; -import { BigNumber as EthersBN, constants, Contract, ContractFactory, ethers, providers, Signer, utils } from "ethers"; -import { HardhatRuntimeEnvironment } from "hardhat/types"; -import _ from "lodash"; -import NodeCache from "node-cache"; -import * as path from "path"; -import prompts from "prompts"; -import { inspect } from "util"; + SystemContracts, +} from '@deploy-configurations/types/deployment-config' +import { EtherscanGasPrice } from '@deploy-configurations/types/etherscan' +import { Network } from '@deploy-configurations/types/network' +import { NetworkByChainId } from '@deploy-configurations/utils/network/index' +import { OperationsRegistry, ServiceRegistry } from '@deploy-configurations/utils/wrappers/index' +import { loadContractNames } from '@dma-contracts/../deploy-configurations/constants' +import { RecursivePartial } from '@dma-contracts/utils/recursive-partial' +import Safe from '@safe-global/safe-core-sdk' +import { SafeTransactionDataPartial } from '@safe-global/safe-core-sdk-types' +import EthersAdapter from '@safe-global/safe-ethers-lib' +import SafeServiceClient from '@safe-global/safe-service-client' +import axios from 'axios' +import BigNumber from 'bignumber.js' +import { + BigNumber as EthersBN, + constants, + Contract, + ContractFactory, + ethers, + providers, + Signer, + utils, +} from 'ethers' +import { HardhatRuntimeEnvironment } from 'hardhat/types' +import _ from 'lodash' +import NodeCache from 'node-cache' +import * as path from 'path' +import prompts from 'prompts' +import { inspect } from 'util' const restrictedNetworks = [Network.MAINNET, Network.OPTIMISM, Network.BASE, Network.GOERLI] diff --git a/packages/dma-contracts/tasks/generate-op-tuple/index.ts b/packages/dma-contracts/tasks/generate-op-tuple/index.ts index 857dafd9..776e6595 100644 --- a/packages/dma-contracts/tasks/generate-op-tuple/index.ts +++ b/packages/dma-contracts/tasks/generate-op-tuple/index.ts @@ -1,6 +1,6 @@ import { Network } from '@deploy-configurations/types/network' -import { task } from 'hardhat/config' import { utils } from 'ethers' +import { task } from 'hardhat/config' import { OperationsDatabase } from '../common' @@ -24,7 +24,11 @@ function generateOperationTuple( operationsDatabase: OperationsDatabase, ) { console.log('======================================================================') - console.log(`Generating tuple for operation ${taskArgs.op} (hash: ${utils.formatBytes32String(taskArgs.op)}) on network '${network}'`) + console.log( + `Generating tuple for operation ${taskArgs.op} (hash: ${utils.formatBytes32String( + taskArgs.op, + )}) on network '${network}'`, + ) console.log('======================================================================\n') const operationTuple: string | undefined = operationsDatabase.getTuple(taskArgs.op) diff --git a/packages/dma-contracts/test/integration/morphoblue/core/operation-executor.test.ts b/packages/dma-contracts/test/integration/morphoblue/core/operation-executor.test.ts index f22db1ba..cbaec43c 100644 --- a/packages/dma-contracts/test/integration/morphoblue/core/operation-executor.test.ts +++ b/packages/dma-contracts/test/integration/morphoblue/core/operation-executor.test.ts @@ -219,9 +219,11 @@ describe('OperationExecutorFL', async function () { const operationNameBytes32 = ethers.utils.formatBytes32String('CUSTOM_OPERATION2') - const tx = await system.opExecTester.execute(system.operationExecutor.address, system.operationExecutor.interface.encodeFunctionData('executeOp', [ - dummyOperation, - ]), operationNameBytes32) + const tx = await system.opExecTester.execute( + system.operationExecutor.address, + system.operationExecutor.interface.encodeFunctionData('executeOp', [dummyOperation]), + operationNameBytes32, + ) const res = await tx.wait() expect(res.status).to.be.eq(1) @@ -236,14 +238,14 @@ describe('OperationExecutorFL', async function () { const operationNameBytes32 = ethers.utils.formatBytes32String('CUSTOM_OPERATION3') try { - await system.opExecTester.execute(system.operationExecutor.address, system.operationExecutor.interface.encodeFunctionData('executeOp', [ - dummyOperation, - ]), operationNameBytes32) - + await system.opExecTester.execute( + system.operationExecutor.address, + system.operationExecutor.interface.encodeFunctionData('executeOp', [dummyOperation]), + operationNameBytes32, + ) } catch (e: any) { expect(e.reason.includes('OpExecTester: opName mismatch')).to.be.true } - }) it('should fail because of non-existent operation', async () => { diff --git a/packages/dma-contracts/test/utils/aave/aave.operation.close.ts b/packages/dma-contracts/test/utils/aave/aave.operation.close.ts index 8d84e703..ef761d34 100644 --- a/packages/dma-contracts/test/utils/aave/aave.operation.close.ts +++ b/packages/dma-contracts/test/utils/aave/aave.operation.close.ts @@ -1,18 +1,17 @@ +import AAVEProtocolDataProviderABI from '@abis/external/protocols/aave/v3/aaveProtocolDataProvider.json' import { getNetwork } from '@deploy-configurations/utils/network' import { mockExchangeGetData } from '@dma-common/test-utils' import { executeThroughDPMProxy } from '@dma-common/utils/execute' import { Snapshot } from '@dma-contracts/utils' import { aaveOperations } from '@dma-library/operations/aave' +import { CloseArgs } from '@dma-library/operations/aave/multiply/v3/close' import { AaveLikeStrategyAddresses } from '@dma-library/operations/aave-like' -import { FlashloanProvider, PositionType } from '@dma-library/types' +import { FlashloanProvider } from '@dma-library/types' import { encodeOperation } from '@dma-library/utils/operation' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { ERC20, WETH } from '@typechain' import { BigNumber as BigNumberJS } from 'bignumber.js' import { BigNumber, ethers } from 'ethers' -import AAVEProtocolDataProviderABI from '@abis/external/protocols/aave/v3/aaveProtocolDataProvider.json' - -import { CloseArgs } from '@dma-library/operations/aave/multiply/v3/close' export async function closeAAVEv3( snapshot: Snapshot, @@ -50,11 +49,17 @@ export async function closeAAVEv3( type: 'Multiply', collateral: { amount: new BigNumberJS(depositEthAmount.toString()), - } + }, }, swap: { fee: 0, - data: mockExchangeGetData(mockExchange, WETH.address, DAI, new BigNumberJS(1).times(10 ** 18).toString(), false), + data: mockExchangeGetData( + mockExchange, + WETH.address, + DAI, + new BigNumberJS(1).times(10 ** 18).toString(), + false, + ), collectFeeFrom: 'sourceToken', receiveAtLeast: new BigNumberJS(0), amount: new BigNumberJS(flashloanAmount.toString()), @@ -101,24 +106,32 @@ export async function closeAAVEv3( const ethBalanceAfter = await config.provider.getBalance(signer.address) const daiBalanceAfter = await debtToken.balanceOf(signer.address) - - console.log('ETH AFTER - SIGNER', ethBalanceAfter.toString()); - console.log('DAI AFTER - SGINER', daiBalanceAfter.toString() ); + console.log('ETH AFTER - SIGNER', ethBalanceAfter.toString()) + console.log('DAI AFTER - SGINER', daiBalanceAfter.toString()) const aavePoolDataProvider = '0x7B4EB56E7CD4b454BA8ff71E4518426369a138a3' const USDCaddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' const WETHAddress = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' - const aaveProtocolDataProviderContract = new ethers.Contract(aavePoolDataProvider, AAVEProtocolDataProviderABI, signer.provider).connect(signer); + const aaveProtocolDataProviderContract = new ethers.Contract( + aavePoolDataProvider, + AAVEProtocolDataProviderABI, + signer.provider, + ).connect(signer) + + const aaveCollInfo2 = await aaveProtocolDataProviderContract.getUserReserveData( + WETHAddress, + helpers.userDPMProxy.address, + ) + const aaveDebtInfo2 = await aaveProtocolDataProviderContract.getUserReserveData( + USDCaddress, + helpers.userDPMProxy.address, + ) - const aaveCollInfo2 = await aaveProtocolDataProviderContract.getUserReserveData(WETHAddress, helpers.userDPMProxy.address) - const aaveDebtInfo2 = await aaveProtocolDataProviderContract.getUserReserveData(USDCaddress, helpers.userDPMProxy.address) + console.log('aaveCollInfo post op', aaveCollInfo2) + console.log('aaveDebtInfo post op', aaveDebtInfo2) - console.log('aaveCollInfo post op', aaveCollInfo2); - console.log('aaveDebtInfo post op', aaveDebtInfo2); - - return { success, } diff --git a/packages/dma-contracts/utils/deploy-test-system.ts b/packages/dma-contracts/utils/deploy-test-system.ts index 86598ced..dfa6a41f 100644 --- a/packages/dma-contracts/utils/deploy-test-system.ts +++ b/packages/dma-contracts/utils/deploy-test-system.ts @@ -295,7 +295,7 @@ async function postDeploymentSystemOverrides( }, }, }) - + ds.addConfigOverrides({ common: { OneInchAggregator: { diff --git a/packages/dma-library/src/operations/aave/index.ts b/packages/dma-library/src/operations/aave/index.ts index 1164a832..86d91590 100644 --- a/packages/dma-library/src/operations/aave/index.ts +++ b/packages/dma-library/src/operations/aave/index.ts @@ -1,4 +1,10 @@ // Borrow +// Auto +import { type AaveV3WithdrawOperation, withdraw as aaveV3Withdraw } from './auto/withdraw' +import { + type AaveV3WithdrawToDebtOperation, + withdrawToDebt as aaveV3WithdrawToDebt, +} from './auto/withdraw-to-debt' import { AaveV2BorrowOperation, borrow as aaveV2Borrow } from './borrow/v2/borrow' import { AaveV2DepositOperation, deposit as aaveV2Deposit } from './borrow/v2/deposit' import { @@ -48,12 +54,6 @@ import { } from './multiply/v3/adjust-risk-up' import { AaveV3CloseOperation, close as aaveV3Close } from './multiply/v3/close' import { AaveV3OpenOperation, open as aaveV3Open } from './multiply/v3/open' -// Auto -import { type AaveV3WithdrawOperation, withdraw as aaveV3Withdraw } from './auto/withdraw' -import { - type AaveV3WithdrawToDebtOperation, - withdrawToDebt as aaveV3WithdrawToDebt, -} from './auto/withdraw-to-debt' const borrow = { v2: { diff --git a/packages/dma-library/src/operations/ajna/addresses.ts b/packages/dma-library/src/operations/ajna/addresses.ts index f35c06eb..3d8ca91f 100644 --- a/packages/dma-library/src/operations/ajna/addresses.ts +++ b/packages/dma-library/src/operations/ajna/addresses.ts @@ -1,4 +1,4 @@ -export interface AjnaStrategyAddresses { +export interface SummerStrategyAddresses { DAI: string ETH: string WETH: string diff --git a/packages/dma-library/src/operations/ajna/adjust-risk-down.ts b/packages/dma-library/src/operations/ajna/adjust-risk-down.ts index 565de590..233f17f6 100644 --- a/packages/dma-library/src/operations/ajna/adjust-risk-down.ts +++ b/packages/dma-library/src/operations/ajna/adjust-risk-down.ts @@ -5,12 +5,12 @@ import { BALANCER_FEE } from '@dma-library/config/flashloan-fees' import { IOperation, WithAjnaBucketPrice, - WithAjnaStrategyAddresses, WithCollateralAndWithdrawal, WithDebt, WithFlashloan, WithNetwork, WithProxy, + WithSummerStrategyAddresses, WithSwap, } from '@dma-library/types' import { FlashloanProvider } from '@dma-library/types/common' @@ -21,7 +21,7 @@ type AjnaAdjustRiskDownArgs = WithCollateralAndWithdrawal & WithSwap & WithFlashloan & WithProxy & - WithAjnaStrategyAddresses & + WithSummerStrategyAddresses & WithAjnaBucketPrice & WithNetwork @@ -61,6 +61,7 @@ export const adjustRiskDown: AjnaAdjustRiskDownOperation = async ({ }) const paybackWithdraw = actions.ajna.ajnaPaybackWithdraw( + network, { quoteToken: debt.address, collateralToken: collateral.address, diff --git a/packages/dma-library/src/operations/ajna/adjust-risk-up.ts b/packages/dma-library/src/operations/ajna/adjust-risk-up.ts index 1697336f..51a4293d 100644 --- a/packages/dma-library/src/operations/ajna/adjust-risk-up.ts +++ b/packages/dma-library/src/operations/ajna/adjust-risk-up.ts @@ -5,13 +5,13 @@ import { BALANCER_FEE } from '@dma-library/config/flashloan-fees' import { IOperation, WithAjnaBucketPrice, - WithAjnaStrategyAddresses, WithCollateral, WithDebtAndBorrow, WithFlashloan, WithNetwork, WithOptionalDeposit, WithProxy, + WithSummerStrategyAddresses, WithSwap, } from '@dma-library/types' import { FlashloanProvider } from '@dma-library/types/common' @@ -24,7 +24,7 @@ type AjnaAdjustRiskUpArgs = WithCollateral & WithSwap & WithFlashloan & WithProxy & - WithAjnaStrategyAddresses & + WithSummerStrategyAddresses & WithAjnaBucketPrice & WithNetwork @@ -90,6 +90,7 @@ export const adjustRiskUp: AjnaAdjustRiskUpOperation = async ({ ) const depositBorrow = actions.ajna.ajnaDepositBorrow( + network, { quoteToken: debt.address, collateralToken: collateral.address, diff --git a/packages/dma-library/src/operations/ajna/close-to-collateral.ts b/packages/dma-library/src/operations/ajna/close-to-collateral.ts index 46b03594..876f1d54 100644 --- a/packages/dma-library/src/operations/ajna/close-to-collateral.ts +++ b/packages/dma-library/src/operations/ajna/close-to-collateral.ts @@ -1,16 +1,16 @@ import { getAjnaCloseToCollateralOperationDefinition } from '@deploy-configurations/operation-definitions' -import { Network } from '@deploy-configurations/types/network' import { FEE_BASE, MAX_UINT, ZERO } from '@dma-common/constants' import { actions } from '@dma-library/actions' import { BALANCER_FEE } from '@dma-library/config/flashloan-fees' import { IOperation, WithAjnaBucketPrice, - WithAjnaStrategyAddresses, WithCollateral, WithDebt, WithFlashloan, + WithNetwork, WithProxy, + WithSummerStrategyAddresses, WithSwap, } from '@dma-library/types' import { FlashloanProvider } from '@dma-library/types/common' @@ -21,8 +21,9 @@ type AjnaCloseArgs = WithCollateral & WithSwap & WithFlashloan & WithProxy & - WithAjnaStrategyAddresses & - WithAjnaBucketPrice + WithSummerStrategyAddresses & + WithAjnaBucketPrice & + WithNetwork export type AjnaCloseToCollateralOperation = ({ collateral, @@ -32,6 +33,7 @@ export type AjnaCloseToCollateralOperation = ({ proxy, addresses, price, + network, }: AjnaCloseArgs) => Promise export const closeToCollateral: AjnaCloseToCollateralOperation = async ({ @@ -42,15 +44,16 @@ export const closeToCollateral: AjnaCloseToCollateralOperation = async ({ proxy, addresses, price, + network, }) => { - const setDebtTokenApprovalOnPool = actions.common.setApproval(Network.MAINNET, { + const setDebtTokenApprovalOnPool = actions.common.setApproval(network, { asset: debt.address, delegate: addresses.pool, amount: flashloan.token.amount, sumAmounts: false, }) - const paybackWithdraw = actions.ajna.ajnaPaybackWithdraw({ + const paybackWithdraw = actions.ajna.ajnaPaybackWithdraw(network, { quoteToken: debt.address, collateralToken: collateral.address, withdrawAmount: ZERO, @@ -60,7 +63,7 @@ export const closeToCollateral: AjnaCloseToCollateralOperation = async ({ price, }) - const swapCollateralTokensForDebtTokens = actions.common.swap(Network.MAINNET, { + const swapCollateralTokensForDebtTokens = actions.common.swap(network, { fromAsset: collateral.address, toAsset: debt.address, amount: swap.amount, @@ -70,23 +73,23 @@ export const closeToCollateral: AjnaCloseToCollateralOperation = async ({ collectFeeInFromToken: swap.collectFeeFrom === 'sourceToken', }) - const sendQuoteTokenToOpExecutor = actions.common.sendToken(Network.MAINNET, { + const sendQuoteTokenToOpExecutor = actions.common.sendToken(network, { asset: debt.address, to: addresses.operationExecutor, amount: flashloan.token.amount.plus(BALANCER_FEE.div(FEE_BASE).times(flashloan.token.amount)), }) - const unwrapEth = actions.common.unwrapEth(Network.MAINNET, { + const unwrapEth = actions.common.unwrapEth(network, { amount: new BigNumber(MAX_UINT), }) unwrapEth.skipped = !debt.isEth && !collateral.isEth - const returnDebtFunds = actions.common.returnFunds(Network.MAINNET, { + const returnDebtFunds = actions.common.returnFunds(network, { asset: debt.isEth ? addresses.ETH : debt.address, }) - const returnCollateralFunds = actions.common.returnFunds(Network.MAINNET, { + const returnCollateralFunds = actions.common.returnFunds(network, { asset: collateral.isEth ? addresses.ETH : collateral.address, }) @@ -98,7 +101,7 @@ export const closeToCollateral: AjnaCloseToCollateralOperation = async ({ unwrapEth, ] - const takeAFlashLoan = actions.common.takeAFlashLoan(Network.MAINNET, { + const takeAFlashLoan = actions.common.takeAFlashLoan(network, { isDPMProxy: proxy.isDPMProxy, asset: flashloan.token.address, flashloanAmount: flashloan.token.amount, @@ -109,6 +112,6 @@ export const closeToCollateral: AjnaCloseToCollateralOperation = async ({ return { calls: [takeAFlashLoan, returnDebtFunds, returnCollateralFunds], - operationName: getAjnaCloseToCollateralOperationDefinition(Network.MAINNET).name, + operationName: getAjnaCloseToCollateralOperationDefinition(network).name, } } diff --git a/packages/dma-library/src/operations/ajna/close-to-quote.ts b/packages/dma-library/src/operations/ajna/close-to-quote.ts index f6d051be..4d391e93 100644 --- a/packages/dma-library/src/operations/ajna/close-to-quote.ts +++ b/packages/dma-library/src/operations/ajna/close-to-quote.ts @@ -1,16 +1,16 @@ import { getAjnaCloseToQuoteOperationDefinition } from '@deploy-configurations/operation-definitions' -import { Network } from '@deploy-configurations/types/network' import { FEE_BASE, MAX_UINT, ZERO } from '@dma-common/constants' import { actions } from '@dma-library/actions' import { BALANCER_FEE } from '@dma-library/config/flashloan-fees' import { IOperation, WithAjnaBucketPrice, - WithAjnaStrategyAddresses, WithCollateral, WithDebt, WithFlashloan, + WithNetwork, WithProxy, + WithSummerStrategyAddresses, WithSwap, } from '@dma-library/types' import { FlashloanProvider } from '@dma-library/types/common' @@ -21,8 +21,9 @@ type AjnaCloseArgs = WithCollateral & WithSwap & WithFlashloan & WithProxy & - WithAjnaStrategyAddresses & - WithAjnaBucketPrice + WithSummerStrategyAddresses & + WithAjnaBucketPrice & + WithNetwork export type AjnaCloseToQuoteOperation = ({ collateral, @@ -32,6 +33,7 @@ export type AjnaCloseToQuoteOperation = ({ proxy, addresses, price, + network, }: AjnaCloseArgs) => Promise export const closeToQuote: AjnaCloseToQuoteOperation = async ({ @@ -42,15 +44,16 @@ export const closeToQuote: AjnaCloseToQuoteOperation = async ({ proxy, addresses, price, + network, }) => { - const setDebtTokenApprovalOnPool = actions.common.setApproval(Network.MAINNET, { + const setDebtTokenApprovalOnPool = actions.common.setApproval(network, { asset: debt.address, delegate: addresses.pool, amount: flashloan.token.amount, sumAmounts: false, }) - const paybackWithdraw = actions.ajna.ajnaPaybackWithdraw({ + const paybackWithdraw = actions.ajna.ajnaPaybackWithdraw(network, { quoteToken: debt.address, collateralToken: collateral.address, withdrawAmount: ZERO, @@ -60,7 +63,7 @@ export const closeToQuote: AjnaCloseToQuoteOperation = async ({ price, }) - const swapCollateralTokensForDebtTokens = actions.common.swap(Network.MAINNET, { + const swapCollateralTokensForDebtTokens = actions.common.swap(network, { fromAsset: collateral.address, toAsset: debt.address, amount: swap.amount, @@ -70,19 +73,19 @@ export const closeToQuote: AjnaCloseToQuoteOperation = async ({ collectFeeInFromToken: swap.collectFeeFrom === 'sourceToken', }) - const sendQuoteTokenToOpExecutor = actions.common.sendToken(Network.MAINNET, { + const sendQuoteTokenToOpExecutor = actions.common.sendToken(network, { asset: debt.address, to: addresses.operationExecutor, amount: flashloan.token.amount.plus(BALANCER_FEE.div(FEE_BASE).times(flashloan.token.amount)), }) - const unwrapEth = actions.common.unwrapEth(Network.MAINNET, { + const unwrapEth = actions.common.unwrapEth(network, { amount: new BigNumber(MAX_UINT), }) unwrapEth.skipped = !debt.isEth && !collateral.isEth - const returnDebtFunds = actions.common.returnFunds(Network.MAINNET, { + const returnDebtFunds = actions.common.returnFunds(network, { asset: debt.isEth ? addresses.ETH : debt.address, }) @@ -94,7 +97,7 @@ export const closeToQuote: AjnaCloseToQuoteOperation = async ({ unwrapEth, ] - const takeAFlashLoan = actions.common.takeAFlashLoan(Network.MAINNET, { + const takeAFlashLoan = actions.common.takeAFlashLoan(network, { isDPMProxy: proxy.isDPMProxy, asset: debt.address, flashloanAmount: flashloan.token.amount, @@ -105,6 +108,6 @@ export const closeToQuote: AjnaCloseToQuoteOperation = async ({ return { calls: [takeAFlashLoan, returnDebtFunds], - operationName: getAjnaCloseToQuoteOperationDefinition(Network.MAINNET).name, + operationName: getAjnaCloseToQuoteOperationDefinition(network).name, } } diff --git a/packages/dma-library/src/operations/ajna/index.ts b/packages/dma-library/src/operations/ajna/index.ts index 61ec90e1..6d663929 100644 --- a/packages/dma-library/src/operations/ajna/index.ts +++ b/packages/dma-library/src/operations/ajna/index.ts @@ -1,4 +1,4 @@ -export { AjnaStrategyAddresses } from './addresses' +export { SummerStrategyAddresses } from './addresses' import { adjustRiskDown, AjnaAdjustRiskDownOperation } from './adjust-risk-down' import { adjustRiskUp, AjnaAdjustRiskUpOperation } from './adjust-risk-up' import { AjnaCloseToCollateralOperation, closeToCollateral } from './close-to-collateral' diff --git a/packages/dma-library/src/operations/ajna/open.ts b/packages/dma-library/src/operations/ajna/open.ts index 555d2ad7..38d97a9e 100644 --- a/packages/dma-library/src/operations/ajna/open.ts +++ b/packages/dma-library/src/operations/ajna/open.ts @@ -8,7 +8,6 @@ import { IOperation, Protocol, WithAjnaBucketPrice, - WithAjnaStrategyAddresses, WithCollateral, WithDebtAndBorrow, WithFlashloan, @@ -16,6 +15,7 @@ import { WithOptionalDeposit, WithPosition, WithProxy, + WithSummerStrategyAddresses, WithSwap, } from '@dma-library/types' import BigNumber from 'bignumber.js' @@ -28,7 +28,7 @@ type OpenArgs = WithCollateral & WithFlashloan & WithProxy & WithPosition & - WithAjnaStrategyAddresses & + WithSummerStrategyAddresses & WithAjnaBucketPrice & WithNetwork @@ -98,6 +98,7 @@ export const open: AjnaOpenOperation = async ({ ) const depositBorrow = actions.ajna.ajnaDepositBorrow( + network, { quoteToken: debt.address, collateralToken: collateral.address, @@ -115,7 +116,7 @@ export const open: AjnaOpenOperation = async ({ amount: flashloan.amount.plus(BALANCER_FEE.div(FEE_BASE).times(flashloan.amount)), }) - const protocol: Protocol = 'Ajna' + const protocol: Protocol = network === Network.MAINNET ? 'Ajna_rc13' : 'Ajna_rc14' const positionCreated = actions.common.positionCreated(network, { protocol, @@ -145,6 +146,6 @@ export const open: AjnaOpenOperation = async ({ return { calls: [takeAFlashLoan], - operationName: getAjnaOpenOperationDefinition(Network.MAINNET).name, + operationName: getAjnaOpenOperationDefinition(network).name, } } diff --git a/packages/dma-library/src/strategies/aave/index.ts b/packages/dma-library/src/strategies/aave/index.ts index c4f7830a..979e467b 100644 --- a/packages/dma-library/src/strategies/aave/index.ts +++ b/packages/dma-library/src/strategies/aave/index.ts @@ -3,6 +3,7 @@ import { AaveVersion } from '@dma-library/types/aave' import { WithV2Protocol, WithV3Protocol } from '@dma-library/types/aave/protocol' import { views } from '@dma-library/views' +import { AaveV3WithdrawToLTV, withdraw } from './auto/withdraw-to-ltv' import { AaveV2ChangeDebt, changeDebt } from './borrow/change-debt' import { AaveV2DepositBorrow, AaveV3DepositBorrow, depositBorrow } from './borrow/deposit-borrow' import { @@ -18,7 +19,6 @@ import { import { AaveV2Adjust, AaveV3Adjust, adjust } from './multiply/adjust' import { AaveV2Close, AaveV3Close, close } from './multiply/close' import { AaveV2Open, AaveV3Open, open } from './multiply/open' -import { AaveV3WithdrawToLTV, withdraw } from './auto/withdraw-to-ltv' export const aave: { borrow: { diff --git a/packages/dma-library/src/strategies/common/close-to-coll-swap-data.ts b/packages/dma-library/src/strategies/common/close-to-coll-swap-data.ts index e9d37214..36f9b1c1 100644 --- a/packages/dma-library/src/strategies/common/close-to-coll-swap-data.ts +++ b/packages/dma-library/src/strategies/common/close-to-coll-swap-data.ts @@ -1,6 +1,5 @@ import { Address } from '@deploy-configurations/types/address' import { FEE_BASE, ONE, TEN, ZERO } from '@dma-common/constants' -import { areAddressesEqual } from '@dma-common/utils/addresses' import { calculateFee } from '@dma-common/utils/swap' import { SAFETY_MARGIN } from '@dma-library/strategies/aave-like/multiply/close/constants' import { GetSwapData } from '@dma-library/types/common' @@ -22,7 +21,6 @@ interface GetSwapDataToCloseToCollateralArgs { debtPrice: BigNumber outstandingDebt: BigNumber slippage: BigNumber - ETHAddress: Address getSwapData: GetSwapData __feeOverride?: BigNumber } @@ -34,7 +32,6 @@ export async function getSwapDataForCloseToCollateral({ debtPrice, outstandingDebt, slippage, - ETHAddress, getSwapData, __feeOverride, }: GetSwapDataToCloseToCollateralArgs) { @@ -65,7 +62,7 @@ export async function getSwapDataForCloseToCollateral({ colPrice, collateralTokenPrecision, _outstandingDebt, - fee, + fee.div(new BigNumber(FEE_BASE).plus(fee)), slippage, ) @@ -74,47 +71,21 @@ export async function getSwapDataForCloseToCollateral({ // there is a deviation threshold value that shows how much the prices on/off chain might differ // When there is a 1inch swap, we use real-time market price. To calculate that, // A preflight request is sent to calculate the existing market price. - const debtIsEth = areAddressesEqual(debtToken.address, ETHAddress) - const collateralIsEth = areAddressesEqual(collateralToken.address, ETHAddress) - - if (debtIsEth) { - debtPrice = ONE.times(TEN.pow(debtTokenPrecision)) - } else { - const debtPricePreflightSwapData = await getSwapData( - debtToken.address, - ETHAddress, - _outstandingDebt, - slippage, - undefined, - true, // inverts swap mock in tests ignored in prod - ) - debtPrice = new BigNumber( - debtPricePreflightSwapData.toTokenAmount - .div(debtPricePreflightSwapData.fromTokenAmount) - .times(TEN.pow(debtTokenPrecision)) - .toFixed(0), - ) - } + debtPrice = ONE.times(TEN.pow(debtTokenPrecision)) - if (collateralIsEth) { - colPrice = ONE.times(TEN.pow(collateralTokenPrecision)) - } else { - const colPricePreflightSwapData = - !collateralIsEth && - (await getSwapData( - collateralToken.address, - ETHAddress, - collateralNeeded.integerValue(BigNumber.ROUND_DOWN), - slippage, - )) + const colPricePreflightSwapData = await getSwapData( + collateralToken.address, + debtToken.address, + collateralNeeded.integerValue(BigNumber.ROUND_DOWN), + slippage, + ) - colPrice = new BigNumber( - colPricePreflightSwapData.toTokenAmount - .div(colPricePreflightSwapData.fromTokenAmount) - .times(TEN.pow(collateralTokenPrecision)) - .toFixed(0), - ) - } + colPrice = new BigNumber( + colPricePreflightSwapData.toTokenAmount + .div(colPricePreflightSwapData.fromTokenAmount) + .times(TEN.pow(collateralTokenPrecision)) + .toFixed(0), + ) // 4. Get Swap Data // This is the actual swap data that will be used in the transaction. diff --git a/packages/dma-library/src/strategies/common/erc4626/deposit.ts b/packages/dma-library/src/strategies/common/erc4626/deposit.ts new file mode 100644 index 00000000..60a859a8 --- /dev/null +++ b/packages/dma-library/src/strategies/common/erc4626/deposit.ts @@ -0,0 +1,174 @@ +import { ADDRESSES, SystemKeys } from '@deploy-configurations/addresses' +import { amountToWei } from '@dma-common/utils/common' +import { operations } from '@dma-library/operations' +import { getGenericSwapData } from '@dma-library/strategies/common' +import { encodeOperation } from '@dma-library/utils/operation' +import { views } from '@dma-library/views' +import BigNumber from 'bignumber.js' +import { ethers } from 'ethers' + +import { + Erc4626CommonDependencies, + Erc4626DepositPayload, + Erc4626DepositStrategy, +} from '../../../types/common' +import { validateMaxDeposit } from './validation/validate-max-deposit' + +export const deposit: Erc4626DepositStrategy = async (args, dependencies) => { + const addresses = { tokens: { ...ADDRESSES[dependencies.network][SystemKeys.COMMON] } } + + const getPosition = views.common.getErc4626Position + const position = await getPosition( + { + vaultAddress: args.vault, + proxyAddress: args.proxyAddress, + user: args.user, + quotePrice: args.quoteTokenPrice, + underlyingAsset: { + address: args.depositTokenAddress, + precision: args.depositTokenPrecision, + symbol: args.depositTokenSymbol, + }, + }, + { + provider: dependencies.provider, + getLazyVaultSubgraphResponse: dependencies.getLazyVaultSubgraphResponse, + getVaultApyParameters: dependencies.getVaultApyParameters, + }, + ) + const isOpen = position.netValue.toString() === '0' + + const isPullingEth = args.pullTokenAddress.toLowerCase() === addresses.tokens.WETH.toLowerCase() + + const isSwapping = args.depositTokenAddress.toLowerCase() !== args.pullTokenAddress.toLowerCase() + + if (isSwapping) { + const { swapData, collectFeeFrom, fee, tokenFee } = await getSwapData(args, dependencies) + const operation = await operations.erc4626Operations.deposit( + { + vault: args.vault, + depositToken: args.depositTokenAddress, + pullToken: args.pullTokenAddress, + amountToDeposit: amountToWei(args.amount, args.pullTokenPrecision), + isPullingEth: isPullingEth, + swap: { + fee: fee, + data: swapData.exchangeCalldata, + amount: amountToWei(args.amount, args.pullTokenPrecision), + collectFeeFrom, + receiveAtLeast: swapData.minToTokenAmount, + }, + proxy: { + address: args.proxyAddress, + isDPMProxy: true, + owner: args.user, + }, + isOpen, + }, + dependencies.network, + ) + const depositAmount = new BigNumber( + ethers.utils + .formatUnits(swapData.minToTokenAmount.toString(), args.depositTokenPrecision) + .toString(), + ) + const targetPosition = position.deposit(depositAmount) + + const warnings = [] + + const errors = [...validateMaxDeposit(depositAmount, position)] + + const swap = { + fromTokenAddress: args.pullTokenAddress, + toTokenAddress: args.depositTokenAddress, + fromTokenAmount: amountToWei(args.amount, args.pullTokenPrecision), + toTokenAmount: swapData.toTokenAmount, + minToTokenAmount: swapData.minToTokenAmount, + tokenFee: tokenFee, + collectFeeFrom: collectFeeFrom, + exchangeCalldata: swapData.exchangeCalldata, + } + return { + simulation: { + swaps: [swap], + errors, + warnings, + notices: [], + successes: [], + targetPosition, + position: targetPosition, + }, + tx: { + to: dependencies.operationExecutor, + data: encodeOperation(operation, dependencies), + value: isPullingEth ? amountToWei(args.amount, 18).toString() : '0', + }, + } + } else { + const operation = await operations.erc4626Operations.deposit( + { + vault: args.vault, + depositToken: args.depositTokenAddress, + pullToken: args.pullTokenAddress, + amountToDeposit: amountToWei(args.amount, args.pullTokenPrecision), + isPullingEth: isPullingEth, + + proxy: { + address: args.proxyAddress, + isDPMProxy: true, + owner: args.user, + }, + isOpen, + }, + dependencies.network, + ) + + const targetPosition = position.deposit(args.amount) + + const warnings = [] + + const errors = [...validateMaxDeposit(args.amount, position)] + + return { + simulation: { + swaps: [], + errors, + warnings, + notices: [], + successes: [], + targetPosition, + position: targetPosition, + }, + tx: { + to: dependencies.operationExecutor, + data: encodeOperation(operation, dependencies), + value: isPullingEth ? amountToWei(args.amount, 18).toString() : '0', + }, + } + } +} + +async function getSwapData(args: Erc4626DepositPayload, dependencies: Erc4626CommonDependencies) { + const swapAmountBeforeFees = amountToWei(args.amount, args.pullTokenPrecision).integerValue( + BigNumber.ROUND_DOWN, + ) + + const fromToken = { + symbol: args.pullTokenSymbol, + precision: args.pullTokenPrecision, + address: args.pullTokenAddress, + } + const toToken = { + symbol: args.depositTokenSymbol, + precision: args.depositTokenPrecision, + address: args.depositTokenAddress, + } + + return getGenericSwapData({ + fromToken, + toToken, + slippage: args.slippage, + swapAmountBeforeFees: swapAmountBeforeFees, + getSwapData: dependencies.getSwapData, + }) +} diff --git a/packages/dma-library/src/strategies/common/erc4626/validation/validate-max-deposit.ts b/packages/dma-library/src/strategies/common/erc4626/validation/validate-max-deposit.ts new file mode 100644 index 00000000..18ec36e5 --- /dev/null +++ b/packages/dma-library/src/strategies/common/erc4626/validation/validate-max-deposit.ts @@ -0,0 +1,24 @@ +import { formatCryptoBalance } from '@dma-common/utils/common' +import { Erc4626Position } from '@dma-library/types' +import { Erc4626StrategyError } from '@dma-library/types/common/erc4626-validation' +import { BigNumber } from 'bignumber.js' + +export function validateMaxDeposit( + depositAmount: BigNumber, + position: Erc4626Position, +): Erc4626StrategyError[] { + const maxDeposit = position.maxDeposit + + if (depositAmount.gt(maxDeposit)) { + return [ + { + name: 'deposit-more-than-possible', + data: { + amount: formatCryptoBalance(maxDeposit), + }, + }, + ] + } + + return [] +} diff --git a/packages/dma-library/src/strategies/common/erc4626/validation/validate-max-withdraw.ts b/packages/dma-library/src/strategies/common/erc4626/validation/validate-max-withdraw.ts new file mode 100644 index 00000000..f4511600 --- /dev/null +++ b/packages/dma-library/src/strategies/common/erc4626/validation/validate-max-withdraw.ts @@ -0,0 +1,24 @@ +import { formatCryptoBalance } from '@dma-common/utils/common' +import { Erc4626Position } from '@dma-library/types' +import { Erc4626StrategyError } from '@dma-library/types/common/erc4626-validation' +import { BigNumber } from 'bignumber.js' + +export function validateMaxWithdraw( + withdrawAmount: BigNumber, + position: Erc4626Position, +): Erc4626StrategyError[] { + const maxWithdraw = position.maxWithdrawal + + if (withdrawAmount.gt(maxWithdraw)) { + return [ + { + name: 'withdraw-more-than-available', + data: { + amount: formatCryptoBalance(maxWithdraw), + }, + }, + ] + } + + return [] +} diff --git a/packages/dma-library/src/strategies/common/erc4626/withdraw.ts b/packages/dma-library/src/strategies/common/erc4626/withdraw.ts new file mode 100644 index 00000000..49c3ac07 --- /dev/null +++ b/packages/dma-library/src/strategies/common/erc4626/withdraw.ts @@ -0,0 +1,182 @@ +import { ADDRESSES, SystemKeys } from '@deploy-configurations/addresses' +import { amountToWei } from '@dma-common/utils/common' +import { operations } from '@dma-library/operations' +import { getGenericSwapData } from '@dma-library/strategies/common' +import { encodeOperation } from '@dma-library/utils/operation' +import { views } from '@dma-library/views' +import BigNumber from 'bignumber.js' + +import { + Erc4626CommonDependencies, + Erc4626WithdrawPayload, + Erc4626WithdrawStrategy, +} from '../../../types/common' +import { validateMaxWithdraw } from './validation/validate-max-withdraw' + +export const withdraw: Erc4626WithdrawStrategy = async (args, dependencies) => { + const addresses = { tokens: { ...ADDRESSES[dependencies.network][SystemKeys.COMMON] } } + const getPosition = views.common.getErc4626Position + const position = await getPosition( + { + vaultAddress: args.vault, + proxyAddress: args.proxyAddress, + user: args.user, + quotePrice: args.quoteTokenPrice, + underlyingAsset: { + address: args.withdrawTokenAddress, + precision: args.withdrawTokenPrecision, + symbol: args.withdrawTokenSymbol, + }, + }, + { + provider: dependencies.provider, + getLazyVaultSubgraphResponse: dependencies.getLazyVaultSubgraphResponse, + getVaultApyParameters: dependencies.getVaultApyParameters, + }, + ) + + const isReturningEth = + args.returnTokenAddress.toLowerCase() === addresses.tokens.WETH.toLowerCase() + const isWithdrawingEth = + args.withdrawTokenAddress.toLowerCase() === addresses.tokens.WETH.toLowerCase() + const isSwapping = + args.returnTokenAddress.toLowerCase() !== args.withdrawTokenAddress.toLowerCase() + + const isClose = args.amount.isGreaterThan(position.quoteTokenAmount) + + if (isSwapping) { + const { swapData, collectFeeFrom, fee, tokenFee } = await getSwapData( + { ...args, amount: isClose ? position.quoteTokenAmount : args.amount }, + dependencies, + ) + const swapInfo = { + fee: fee, + data: swapData.exchangeCalldata, + amount: amountToWei( + isClose ? position.quoteTokenAmount : args.amount, + args.withdrawTokenPrecision, + ), + collectFeeFrom, + receiveAtLeast: swapData.minToTokenAmount, + } + const operation = await operations.erc4626Operations.withdraw( + { + vault: args.vault, + withdrawToken: args.withdrawTokenAddress, + returnToken: args.returnTokenAddress, + amountToWithdraw: amountToWei(args.amount, args.withdrawTokenPrecision), + isWithdrawingEth: isWithdrawingEth, + isReturningEth: isReturningEth, + swap: swapInfo, + proxy: { + address: args.proxyAddress, + isDPMProxy: true, + owner: args.user, + }, + isClose, + }, + addresses, + dependencies.network, + ) + + const targetPosition = position.withdraw(args.amount) + + const warnings = [] + + const errors = [...validateMaxWithdraw(args.amount, position)] + + const swap = { + fromTokenAddress: args.withdrawTokenAddress, + toTokenAddress: args.returnTokenAddress, + fromTokenAmount: amountToWei(args.amount, args.withdrawTokenPrecision), + toTokenAmount: swapData.toTokenAmount, + minToTokenAmount: swapData.minToTokenAmount, + exchangeCalldata: swapData.exchangeCalldata, + tokenFee: tokenFee, + collectFeeFrom: collectFeeFrom, + } + return { + simulation: { + swaps: [swap], + errors, + warnings, + notices: [], + successes: [], + targetPosition, + position: targetPosition, + }, + tx: { + to: dependencies.operationExecutor, + data: encodeOperation(operation, dependencies), + value: '0', + }, + } + } else { + const operation = await operations.erc4626Operations.withdraw( + { + vault: args.vault, + withdrawToken: args.withdrawTokenAddress, + returnToken: args.returnTokenAddress, + amountToWithdraw: amountToWei(args.amount, args.withdrawTokenPrecision), + isWithdrawingEth: isWithdrawingEth, + isReturningEth: isReturningEth, + proxy: { + address: args.proxyAddress, + isDPMProxy: true, + owner: args.user, + }, + isClose, + }, + addresses, + dependencies.network, + ) + + const targetPosition = position.withdraw(args.amount) + + const warnings = [] + + const errors = [...validateMaxWithdraw(args.amount, position)] + + return { + simulation: { + swaps: [], + errors, + warnings, + notices: [], + successes: [], + targetPosition, + position: targetPosition, + }, + tx: { + to: dependencies.operationExecutor, + data: encodeOperation(operation, dependencies), + value: '0', + }, + } + } +} + +async function getSwapData(args: Erc4626WithdrawPayload, dependencies: Erc4626CommonDependencies) { + const swapAmountBeforeFees = amountToWei(args.amount, args.withdrawTokenPrecision).integerValue( + BigNumber.ROUND_DOWN, + ) + + const fromToken = { + symbol: args.withdrawTokenSymbol, + precision: args.withdrawTokenPrecision, + address: args.withdrawTokenAddress, + } + const toToken = { + symbol: args.returnTokenSymbol, + precision: args.returnTokenPrecision, + address: args.returnTokenAddress, + } + + return getGenericSwapData({ + fromToken, + toToken, + slippage: args.slippage, + swapAmountBeforeFees: swapAmountBeforeFees, + getSwapData: dependencies.getSwapData, + }) +} diff --git a/packages/dma-library/src/strategies/common/generic-swap-data.ts b/packages/dma-library/src/strategies/common/generic-swap-data.ts new file mode 100644 index 00000000..25e8f8b1 --- /dev/null +++ b/packages/dma-library/src/strategies/common/generic-swap-data.ts @@ -0,0 +1,62 @@ +import { Address } from '@deploy-configurations/types/address' +import { FEE_ESTIMATE_INFLATOR } from '@dma-common/constants' +import { calculateFee } from '@dma-common/utils/swap' +import { GetSwapData } from '@dma-library/types/common' +import * as SwapUtils from '@dma-library/utils/swap' +import BigNumber from 'bignumber.js' + +import { ONE, ZERO } from '../../../../dma-common/constants/numbers' + +interface GetGenericSwapDataArgs { + fromToken: { + symbol: string + precision: number + address: Address + } + toToken: { + symbol: string + precision: number + address: Address + } + slippage: BigNumber + swapAmountBeforeFees: BigNumber + getSwapData: GetSwapData + __feeOverride?: BigNumber +} + +export async function getGenericSwapData({ + fromToken, + toToken, + slippage, + swapAmountBeforeFees, + getSwapData, + __feeOverride, +}: GetGenericSwapDataArgs) { + const collectFeeFrom = SwapUtils.acceptedFeeTokenByAddress({ + fromTokenAddress: fromToken.address, + toTokenAddress: toToken.address, + }) + + const fee = __feeOverride || SwapUtils.feeResolver(fromToken.symbol, toToken.symbol) + + const preSwapFee = + collectFeeFrom === 'sourceToken' ? calculateFee(swapAmountBeforeFees, fee.toNumber()) : ZERO + + const swapAmountAfterFees = swapAmountBeforeFees + .minus(preSwapFee) + .integerValue(BigNumber.ROUND_DOWN) + + const swapData = await getSwapData( + fromToken.address, + toToken.address, + swapAmountAfterFees, + slippage, + ) + const postSwapFee = + collectFeeFrom === 'targetToken' ? calculateFee(swapData.toTokenAmount, fee.toNumber()) : ZERO + + const tokenFee = preSwapFee.plus( + postSwapFee.times(ONE.plus(FEE_ESTIMATE_INFLATOR)).integerValue(BigNumber.ROUND_DOWN), + ) + return { swapData, collectFeeFrom, fee: fee.toString(), tokenFee } +} diff --git a/packages/dma-library/src/strategies/common/index.ts b/packages/dma-library/src/strategies/common/index.ts index e7bdb182..405dac80 100644 --- a/packages/dma-library/src/strategies/common/index.ts +++ b/packages/dma-library/src/strategies/common/index.ts @@ -1,2 +1,20 @@ +import { Erc4626DepositStrategy, Erc4626WithdrawStrategy } from '@dma-library/types' + +import { deposit } from './erc4626/deposit' +import { withdraw } from './erc4626/withdraw' + export { getSwapDataForCloseToCollateral } from './close-to-coll-swap-data' export { getSwapDataForCloseToDebt } from './close-to-debt-swap-data' +export { getGenericSwapData } from './generic-swap-data' + +export const common: { + erc4626: { + deposit: Erc4626DepositStrategy + withdraw: Erc4626WithdrawStrategy + } +} = { + erc4626: { + deposit, + withdraw, + }, +} diff --git a/packages/dma-library/src/strategies/index.ts b/packages/dma-library/src/strategies/index.ts index aefea33b..e53fa1fc 100644 --- a/packages/dma-library/src/strategies/index.ts +++ b/packages/dma-library/src/strategies/index.ts @@ -1,16 +1,22 @@ import { aave } from './aave' import { ajna } from './ajna' import buckets from './ajna/earn/buckets.json' +import { common } from './common' +import { morphoblue } from './morphoblue' import { spark } from './spark' export const strategies: { aave: typeof aave ajna: typeof ajna + morphoblue: typeof morphoblue spark: typeof spark + common: typeof common } = { aave, ajna, spark, + morphoblue, + common, } export const ajnaBuckets = buckets diff --git a/packages/dma-library/src/strategies/spark/index.ts b/packages/dma-library/src/strategies/spark/index.ts index 14baafcf..10d8e41d 100644 --- a/packages/dma-library/src/strategies/spark/index.ts +++ b/packages/dma-library/src/strategies/spark/index.ts @@ -1,3 +1,4 @@ +import { SparkWithdrawToLTV, withdraw } from './auto/withdraw-to-ltv' import { depositBorrow as sparkDepositBorrow, SparkDepositBorrow } from './borrow/deposit-borrow' import { openDepositBorrow as sparkOpenDepositBorrow, @@ -10,7 +11,6 @@ import { import { adjust as sparkAdjust, SparkAdjust } from './multiply/adjust' import { close as sparkClose, SparkClose } from './multiply/close' import { open as sparkOpen, SparkOpen } from './multiply/open' -import { SparkWithdrawToLTV, withdraw } from './auto/withdraw-to-ltv' export const spark: { borrow: { diff --git a/packages/dma-library/src/strategies/validation/closeToMaxLtv.ts b/packages/dma-library/src/strategies/validation/closeToMaxLtv.ts new file mode 100644 index 00000000..0b7e7dbb --- /dev/null +++ b/packages/dma-library/src/strategies/validation/closeToMaxLtv.ts @@ -0,0 +1,47 @@ +import { formatCryptoBalance } from '@dma-common/utils/common/formaters' +import { AjnaWarning } from '@dma-library/types/ajna' +import { LendingPosition } from '@dma-library/types/morphoblue/morphoblue-position' + +const MAX_LTV_OFFSET = 0.05 + +export function validateGenerateCloseToMaxLtv( + position: LendingPosition, + positionBefore: LendingPosition, +): AjnaWarning[] { + if ( + position.maxRiskRatio.loanToValue.minus(MAX_LTV_OFFSET).lte(position.riskRatio.loanToValue) && + !positionBefore.debtAmount.eq(position.debtAmount) + ) { + return [ + { + name: 'generate-close-to-max-ltv', + data: { + amount: formatCryptoBalance(position.debtAmount.minus(positionBefore.debtAmount)), + }, + }, + ] + } + return [] +} + +export function validateWithdrawCloseToMaxLtv( + position: LendingPosition, + positionBefore: LendingPosition, +): AjnaWarning[] { + if ( + position.maxRiskRatio.loanToValue.minus(MAX_LTV_OFFSET).lte(position.riskRatio.loanToValue) && + !positionBefore.collateralAmount.eq(position.collateralAmount) + ) { + return [ + { + name: 'withdraw-close-to-max-ltv', + data: { + amount: formatCryptoBalance( + position.collateralAmount.minus(positionBefore.collateralAmount).abs(), + ), + }, + }, + ] + } + return [] +} diff --git a/packages/dma-library/src/types/aave-like/aave-like-position-v2.ts b/packages/dma-library/src/types/aave-like/aave-like-position-v2.ts new file mode 100644 index 00000000..bdce8bf8 --- /dev/null +++ b/packages/dma-library/src/types/aave-like/aave-like-position-v2.ts @@ -0,0 +1,202 @@ +import { Address } from '@deploy-configurations/types/address' +import { ZERO } from '@dma-common/constants' +import { negativeToZero, normalizeValue } from '@dma-common/utils/common' +import { LendingCumulativesData } from '@dma-library/types' +import { LendingPosition } from '@dma-library/types/morphoblue/morphoblue-position' +import { ReserveData } from '@dma-library/views/aave/types' +import { getBuyingPower } from '@dma-library/views/common' +import { IPositionCategory, RiskRatio } from '@domain' +import { BigNumber } from 'bignumber.js' + +export class AaveLikePositionV2 implements LendingPosition { + constructor( + public owner: Address, + public collateralAmount: BigNumber, + public debtAmount: BigNumber, + public collateralPrice: BigNumber, + public debtPrice: BigNumber, + public price: BigNumber, + public pnl: { + withFees: BigNumber + withoutFees: BigNumber + cumulatives: LendingCumulativesData + }, + public category: IPositionCategory, + public oraclePrice: BigNumber, + public debtVariableBorrowRate: BigNumber, + public collateralLiquidityRate: BigNumber, + public liquidationPenalty: BigNumber, + public reserveData: ReserveData, + ) {} + + get liquidationPrice() { + return normalizeValue( + this.debtAmount.div(this.collateralAmount.times(this.category.liquidationThreshold)), + ) + } + + get marketPrice() { + return this.price + } + + get liquidationToMarketPrice() { + return this.liquidationPrice.div(this.marketPrice) + } + + get collateralAvailable() { + const approximatelyMinimumCollateral = this.debtAmount + .dividedBy(this.oraclePrice) + .dividedBy(this.category.maxLoanToValue) + + return negativeToZero( + normalizeValue(this.collateralAmount.minus(approximatelyMinimumCollateral)), + ) + } + + get riskRatio() { + const loanToValue = this.debtAmount.div(this.collateralAmount.times(this.oraclePrice)) + + return new RiskRatio(normalizeValue(loanToValue), RiskRatio.TYPE.LTV) + } + + get maxRiskRatio() { + return new RiskRatio(normalizeValue(this.category.maxLoanToValue), RiskRatio.TYPE.LTV) + } + + get borrowRate(): BigNumber { + const costOfBorrowingDebt = this.debtVariableBorrowRate + .times(this.debtAmount) + .times(this.debtPrice) + const profitFromProvidingCollateral = this.collateralLiquidityRate + .times(this.collateralAmount) + .times(this.collateralPrice) + + return normalizeValue( + costOfBorrowingDebt.minus(profitFromProvidingCollateral).div(this.netValue), + ) + } + + get netValue(): BigNumber { + return this.collateralAmount + .times(this.collateralPrice) + .minus(this.debtAmount.times(this.debtPrice)) + } + + get minRiskRatio() { + return new RiskRatio(normalizeValue(ZERO), RiskRatio.TYPE.LTV) + } + + get buyingPower() { + return getBuyingPower({ + netValue: this.netValue, + collateralPrice: this.collateralPrice, + marketPrice: this.marketPrice, + debtAmount: this.debtAmount, + maxRiskRatio: this.maxRiskRatio, + }) + } + + debtAvailable() { + const maxLoanToValue = this.category.maxLoanToValue + + return negativeToZero( + normalizeValue( + this.collateralAmount.times(this.oraclePrice).times(maxLoanToValue).minus(this.debtAmount), + ), + ) + } + + deposit(collateralAmount: BigNumber) { + const newCollateralAmount = negativeToZero(this.collateralAmount.plus(collateralAmount)) + return new AaveLikePositionV2( + this.owner, + newCollateralAmount, + this.debtAmount, + this.collateralPrice, + this.debtPrice, + this.price, + this.pnl, + this.category, + this.oraclePrice, + this.debtVariableBorrowRate, + this.collateralLiquidityRate, + this.liquidationPenalty, + this.reserveData, + ) + } + + withdraw(collateralAmount: BigNumber) { + const newCollateralAmount = negativeToZero(this.collateralAmount.minus(collateralAmount)) + return new AaveLikePositionV2( + this.owner, + newCollateralAmount, + this.debtAmount, + this.collateralPrice, + this.debtPrice, + this.price, + this.pnl, + this.category, + this.oraclePrice, + this.debtVariableBorrowRate, + this.collateralLiquidityRate, + this.liquidationPenalty, + this.reserveData, + ) + } + + borrow(quoteAmount: BigNumber): AaveLikePositionV2 { + const newDebt = negativeToZero(this.debtAmount.plus(quoteAmount)) + return new AaveLikePositionV2( + this.owner, + this.collateralAmount, + newDebt, + this.collateralPrice, + this.debtPrice, + this.price, + this.pnl, + this.category, + this.oraclePrice, + this.debtVariableBorrowRate, + this.collateralLiquidityRate, + this.liquidationPenalty, + this.reserveData, + ) + } + + payback(quoteAmount: BigNumber): AaveLikePositionV2 { + const newDebt = negativeToZero(this.debtAmount.minus(quoteAmount)) + return new AaveLikePositionV2( + this.owner, + this.collateralAmount, + newDebt, + this.collateralPrice, + this.debtPrice, + this.price, + this.pnl, + this.category, + this.oraclePrice, + this.debtVariableBorrowRate, + this.collateralLiquidityRate, + this.liquidationPenalty, + this.reserveData, + ) + } + + close(): AaveLikePositionV2 { + return new AaveLikePositionV2( + this.owner, + ZERO, + ZERO, + this.collateralPrice, + this.debtPrice, + this.price, + this.pnl, + this.category, + this.oraclePrice, + this.debtVariableBorrowRate, + this.collateralLiquidityRate, + this.liquidationPenalty, + this.reserveData, + ) + } +} diff --git a/packages/dma-library/src/types/aave-like/index.ts b/packages/dma-library/src/types/aave-like/index.ts index 3594667f..4f4213fc 100644 --- a/packages/dma-library/src/types/aave-like/index.ts +++ b/packages/dma-library/src/types/aave-like/index.ts @@ -1,2 +1,4 @@ export { AaveLikePosition } from './aave-like-position' +export { AaveLikePositionV2 } from './aave-like-position-v2' +export { AaveLikeProtocolEnum } from './aave-like-protocol-enum' export { AaveLikeTokens } from './tokens' diff --git a/packages/dma-library/src/types/ajna/ajna-dependencies.ts b/packages/dma-library/src/types/ajna/ajna-dependencies.ts index c14c9592..ee0a53fa 100644 --- a/packages/dma-library/src/types/ajna/ajna-dependencies.ts +++ b/packages/dma-library/src/types/ajna/ajna-dependencies.ts @@ -3,7 +3,7 @@ import { Network } from '@deploy-configurations/types/network' import { AjnaEarnPosition, AjnaPosition } from '@dma-library/types' import { GetSwapData } from '@dma-library/types/common' import { GetEarnData } from '@dma-library/views' -import { GetCumulativesData, GetPoolData } from '@dma-library/views/ajna' +import { AjnaCumulativesData, GetCumulativesData, GetPoolData } from '@dma-library/views/ajna' import { IRiskRatio } from '@domain' import BigNumber from 'bignumber.js' import { ethers } from 'ethers' @@ -14,7 +14,7 @@ export interface AjnaCommonDependencies { provider: ethers.providers.Provider WETH: Address getPoolData: GetPoolData - getCumulatives: GetCumulativesData + getCumulatives: GetCumulativesData network: Network } @@ -41,6 +41,8 @@ export interface AjnaCommonPayload { collateralTokenPrecision: number quotePrice: BigNumber quoteTokenPrecision: number + collateralToken: string + quoteToken: string } export interface AjnaOpenBorrowPayload extends AjnaCommonPayload { diff --git a/packages/dma-library/src/types/ajna/ajna-earn-position.ts b/packages/dma-library/src/types/ajna/ajna-earn-position.ts index beebe286..7521975a 100644 --- a/packages/dma-library/src/types/ajna/ajna-earn-position.ts +++ b/packages/dma-library/src/types/ajna/ajna-earn-position.ts @@ -50,6 +50,11 @@ export class AjnaEarnPosition implements SupplyPosition { }, public totalEarnings: { withFees: BigNumber; withoutFees: BigNumber }, public isBucketFrozen: boolean, + public historicalApy: { + previousDayAverage: BigNumber + sevenDayAverage: BigNumber + thirtyDayAverage: BigNumber + }, ) { this.fundsLockedUntil = Date.now() + 5 * 60 * 60 * 1000 // MOCK funds locked until 5h from now this.price = priceIndex ? priceIndexToPrice(priceIndex) : ZERO @@ -133,6 +138,7 @@ export class AjnaEarnPosition implements SupplyPosition { this.pnl, this.totalEarnings, this.isBucketFrozen, + this.historicalApy, ) } @@ -149,6 +155,7 @@ export class AjnaEarnPosition implements SupplyPosition { this.pnl, this.totalEarnings, this.isBucketFrozen, + this.historicalApy, ) } @@ -165,6 +172,7 @@ export class AjnaEarnPosition implements SupplyPosition { this.pnl, this.totalEarnings, this.isBucketFrozen, + this.historicalApy, ) } @@ -181,6 +189,7 @@ export class AjnaEarnPosition implements SupplyPosition { this.pnl, this.totalEarnings, this.isBucketFrozen, + this.historicalApy, ) } @@ -197,6 +206,7 @@ export class AjnaEarnPosition implements SupplyPosition { this.pnl, this.totalEarnings, this.isBucketFrozen, + this.historicalApy, ) } @@ -213,6 +223,7 @@ export class AjnaEarnPosition implements SupplyPosition { this.pnl, this.totalEarnings, this.isBucketFrozen, + this.historicalApy, ) } } diff --git a/packages/dma-library/src/types/ajna/ajna-pool.ts b/packages/dma-library/src/types/ajna/ajna-pool.ts index 7ad35045..4be233e3 100644 --- a/packages/dma-library/src/types/ajna/ajna-pool.ts +++ b/packages/dma-library/src/types/ajna/ajna-pool.ts @@ -39,6 +39,9 @@ export interface AjnaPool { debt: BigNumber depositSize: BigNumber apr30dAverage: BigNumber + apr7dAverage: BigNumber + lendApr30dAverage: BigNumber + lendApr7dAverage: BigNumber dailyPercentageRate30dAverage: BigNumber monthlyPercentageRate30dAverage: BigNumber currentBurnEpoch: BigNumber diff --git a/packages/dma-library/src/types/ajna/ajna-position.ts b/packages/dma-library/src/types/ajna/ajna-position.ts index 7e96f052..4dddb211 100644 --- a/packages/dma-library/src/types/ajna/ajna-position.ts +++ b/packages/dma-library/src/types/ajna/ajna-position.ts @@ -2,6 +2,7 @@ import { Address } from '@deploy-configurations/types/address' import { ZERO } from '@dma-common/constants' import { negativeToZero, normalizeValue } from '@dma-common/utils/common' import { + ajnaCollateralizationFactor, calculateMaxGenerate, getAjnaBorrowOriginationFee, getNeutralPrice, @@ -9,6 +10,7 @@ import { } from '@dma-library/protocols/ajna' import { AjnaWarning } from '@dma-library/types/ajna' import { AjnaCumulativesData } from '@dma-library/views/ajna' +import { getBuyingPower } from '@dma-library/views/common' import { IRiskRatio, RiskRatio } from '@domain' import { BigNumber } from 'bignumber.js' @@ -85,7 +87,7 @@ export class AjnaPosition implements LendingPosition { get collateralAvailable() { const collateralAvailable = this.collateralAmount.minus( - this.debtAmount.div(this.pool.lowestUtilizedPrice), + this.debtAmount.times(ajnaCollateralizationFactor).div(this.pool.lowestUtilizedPrice), ) return negativeToZero(normalizeValue(collateralAvailable)) @@ -113,18 +115,21 @@ export class AjnaPosition implements LendingPosition { } get minRiskRatio() { - const loanToValue = this.pool.poolMinDebtAmount.div( - this.collateralAmount.times(this.collateralPrice), - ) + const loanToValue = this.pool.loansCount.gt(10) + ? this.pool.poolMinDebtAmount.div(this.collateralAmount.times(this.collateralPrice)) + : ZERO return new RiskRatio(normalizeValue(loanToValue), RiskRatio.TYPE.LTV) } get buyingPower() { - return this.collateralAmount - .times(this.collateralPrice) - .times(this.maxRiskRatio.loanToValue) - .minus(this.debtAmount.times(this.quotePrice)) + return getBuyingPower({ + netValue: this.netValue, + collateralPrice: this.collateralPrice, + marketPrice: this.marketPrice, + debtAmount: this.debtAmount, + maxRiskRatio: this.maxRiskRatio, + }) } debtAvailable(collateralAmount?: BigNumber) { @@ -151,7 +156,15 @@ export class AjnaPosition implements LendingPosition { this.debtAmount, this.collateralPrice, this.quotePrice, - getNeutralPrice(this.debtAmount, newCollateralAmount, this.pool.interestRate), + getNeutralPrice( + this.debtAmount, + newCollateralAmount, + this.pool.interestRate, + this.t0NeutralPrice, + this.thresholdPrice, + false, + false, + ), this.pnl, ) } @@ -165,7 +178,15 @@ export class AjnaPosition implements LendingPosition { this.debtAmount, this.collateralPrice, this.quotePrice, - getNeutralPrice(this.debtAmount, newCollateralAmount, this.pool.interestRate), + getNeutralPrice( + this.debtAmount, + newCollateralAmount, + this.pool.interestRate, + this.t0NeutralPrice, + this.thresholdPrice, + false, + this.collateralAmount.gt(newCollateralAmount), + ), this.pnl, ) } @@ -179,7 +200,15 @@ export class AjnaPosition implements LendingPosition { newDebt, this.collateralPrice, this.quotePrice, - getNeutralPrice(newDebt, this.collateralAmount, this.pool.interestRate), + getNeutralPrice( + newDebt, + this.collateralAmount, + this.pool.interestRate, + this.t0NeutralPrice, + this.thresholdPrice, + this.debtAmount.lt(newDebt), + false, + ), this.pnl, ) } @@ -193,7 +222,15 @@ export class AjnaPosition implements LendingPosition { newDebt, this.collateralPrice, this.quotePrice, - getNeutralPrice(newDebt, this.collateralAmount, this.pool.interestRate), + getNeutralPrice( + newDebt, + this.collateralAmount, + this.pool.interestRate, + this.t0NeutralPrice, + this.thresholdPrice, + false, + false, + ), this.pnl, ) } @@ -206,7 +243,15 @@ export class AjnaPosition implements LendingPosition { ZERO, this.collateralPrice, this.quotePrice, - getNeutralPrice(ZERO, ZERO, this.pool.interestRate), + getNeutralPrice( + ZERO, + ZERO, + this.pool.interestRate, + this.t0NeutralPrice, + this.thresholdPrice, + false, + true, + ), this.pnl, ) } diff --git a/packages/dma-library/src/types/ajna/ajna-strategy.ts b/packages/dma-library/src/types/ajna/ajna-strategy.ts index 618724a5..d7d00726 100644 --- a/packages/dma-library/src/types/ajna/ajna-strategy.ts +++ b/packages/dma-library/src/types/ajna/ajna-strategy.ts @@ -1,9 +1,11 @@ import { Strategy } from '@dma-library/types' import { AjnaError, AjnaNotice, AjnaSuccess, AjnaWarning } from '@dma-library/types/ajna' -export type AjnaStrategy = Strategy & { +import { Erc4626StrategyError } from '../common/erc4626-validation' + +export type SummerStrategy = Strategy & { simulation: Strategy['simulation'] & { - errors: AjnaError[] + errors: AjnaError[] | Erc4626StrategyError[] warnings: AjnaWarning[] notices: AjnaNotice[] successes: AjnaSuccess[] diff --git a/packages/dma-library/src/types/ajna/ajna-validations.ts b/packages/dma-library/src/types/ajna/ajna-validations.ts index b8f57f98..4176005d 100644 --- a/packages/dma-library/src/types/ajna/ajna-validations.ts +++ b/packages/dma-library/src/types/ajna/ajna-validations.ts @@ -21,9 +21,6 @@ export type AjnaErrorDustLimit = { export type AjnaErrorDustLimitMultiply = { name: 'debt-less-then-dust-limit-multiply' - data: { - minDebtAmount: string - } } export type AjnaErrorWithdrawMoreThanAvailable = { @@ -33,6 +30,10 @@ export type AjnaErrorWithdrawMoreThanAvailable = { } } +export type AjnaErrorWithdrawNotAvailable = { + name: 'withdraw-not-available' +} + export type AjnaErrorNotEnoughLiquidity = { name: 'not-enough-liquidity' data: { @@ -55,24 +56,49 @@ export type AjnaErrorOverWithdraw = { } } -export type AjnaErrorOverRepay = { - name: 'payback-amount-exceeds-debt-token-balance' +export type AaveLikeErrorTargetLtvExceedsSupplyCap = { + name: 'target-ltv-exceeds-supply-cap' data: { - amount: string + cap: string + } +} + +export type AaveLikeErrorTargetLtvExceedsBorrowCap = { + name: 'target-ltv-exceeds-borrow-cap' + data: { + cap: string + } +} + +export type AaveLikeErrorAmountExceedsSupplyCap = { + name: 'deposit-amount-exceeds-supply-cap' + data: { + cap: string } } -export type AjnaError = +export type AaveLikeErrorAmountExceedsBorrowCap = { + name: 'debt-amount-exceeds-borrow-cap' + data: { + cap: string + } +} + +export type StrategyError = | AjnaErrorWithdrawUndercollateralized | AjnaErrorBorrowUndercollateralized | AjnaErrorWithdrawMoreThanAvailable + | AjnaErrorWithdrawNotAvailable | AjnaErrorAfterLupIndexBiggerThanHtpIndexDeposit | AjnaErrorAfterLupIndexBiggerThanHtpIndexWithdraw | AjnaErrorDustLimit | AjnaErrorDustLimitMultiply | AjnaErrorNotEnoughLiquidity | AjnaErrorOverWithdraw - | AjnaErrorOverRepay + | AaveLikeErrorTargetLtvExceedsSupplyCap + | AaveLikeErrorTargetLtvExceedsBorrowCap + | AaveLikeErrorAmountExceedsSupplyCap + | AaveLikeErrorAmountExceedsBorrowCap type AjnaWarningGenerateCloseToMaxLtv = { name: 'generate-close-to-max-ltv' @@ -92,10 +118,19 @@ type AjnaWarningLiquidationPriceCloseToMarketPrice = { name: 'liquidation-price-close-to-market-price' } -export type AjnaWarning = +type AaveLikeWarningYieldLoopCloseToLiquidation = { + name: 'yield-loop-close-to-liquidation' + data: { + rangeToLiquidation: string + liquidationPenalty: string + } +} + +export type StrategyWarning = | AjnaWarningGenerateCloseToMaxLtv | AjnaWarningWithdrawCloseToMaxLtv | AjnaWarningLiquidationPriceCloseToMarketPrice + | AaveLikeWarningYieldLoopCloseToLiquidation export type AjnaNoticePriceBelowHtp = { name: 'price-below-htp' @@ -114,4 +149,15 @@ export type AjnaSuccessPriceaboveLup = { } } -export type AjnaSuccess = AjnaSuccessPriceBetweenHtpAndLup | AjnaSuccessPriceaboveLup +type AaveLikeSuccessYieldLoopSafeFromLiquidation = { + name: 'yield-loop-safe-from-liquidation' + data: { + rangeToLiquidation: string + liquidationPenalty: string + } +} + +export type AjnaSuccess = + | AjnaSuccessPriceBetweenHtpAndLup + | AjnaSuccessPriceaboveLup + | AaveLikeSuccessYieldLoopSafeFromLiquidation diff --git a/packages/dma-library/src/types/ajna/index.ts b/packages/dma-library/src/types/ajna/index.ts index 45980af2..c6886555 100644 --- a/packages/dma-library/src/types/ajna/index.ts +++ b/packages/dma-library/src/types/ajna/index.ts @@ -13,15 +13,14 @@ export type { AjnaEarnActions } from './ajna-earn-position' export { AjnaEarnPosition } from './ajna-earn-position' export type { AjnaPool } from './ajna-pool' export { AjnaPosition } from './ajna-position' -export type { AjnaStrategy } from './ajna-strategy' +export type { SummerStrategy as SummerStrategy } from './ajna-strategy' export type { - AjnaError, + StrategyError as AjnaError, AjnaErrorAfterLupIndexBiggerThanHtpIndexDeposit, AjnaErrorAfterLupIndexBiggerThanHtpIndexWithdraw, AjnaErrorBorrowUndercollateralized, AjnaErrorDustLimit, AjnaErrorNotEnoughLiquidity, - AjnaErrorOverRepay, AjnaErrorOverWithdraw, AjnaErrorWithdrawMoreThanAvailable, AjnaErrorWithdrawUndercollateralized, @@ -30,5 +29,5 @@ export type { AjnaSuccess, AjnaSuccessPriceaboveLup, AjnaSuccessPriceBetweenHtpAndLup, - AjnaWarning, + StrategyWarning as AjnaWarning, } from './ajna-validations' diff --git a/packages/dma-library/src/types/common/erc4626-addresses.ts b/packages/dma-library/src/types/common/erc4626-addresses.ts new file mode 100644 index 00000000..e3267b05 --- /dev/null +++ b/packages/dma-library/src/types/common/erc4626-addresses.ts @@ -0,0 +1,13 @@ +import { Address } from '@deploy-configurations/types/address' +import { Tokens } from '@deploy-configurations/types/deployment-config' + +type RequiredTokens = 'WETH' | 'DAI' | 'ETH' | 'USDC' +type OptionalTokens = Exclude + +export type TokenAddresses = { + [K in RequiredTokens]: Address +} & Partial> + +export interface Erc4626StrategyAddresses { + tokens: TokenAddresses +} diff --git a/packages/dma-library/src/types/common/erc4626-strategies.ts b/packages/dma-library/src/types/common/erc4626-strategies.ts new file mode 100644 index 00000000..cea7f318 --- /dev/null +++ b/packages/dma-library/src/types/common/erc4626-strategies.ts @@ -0,0 +1,55 @@ +import { Address } from '@deploy-configurations/types/address' +import { Network } from '@deploy-configurations/types/network' +import { BigNumber } from 'bignumber.js' +import { ethers } from 'ethers' + +import { SummerStrategy } from '../ajna' +import { Erc4626Position, Erc4646ViewDependencies } from './erc4626-view' +import { GetSwapData } from './get-swap-data' + +export interface Erc4626DepositPayload { + pullTokenSymbol: string + pullTokenPrecision: number + pullTokenAddress: Address + depositTokenSymbol: string + depositTokenPrecision: number + depositTokenAddress: Address + amount: BigNumber + vault: string + proxyAddress: Address + user: Address + slippage: BigNumber + quoteTokenPrice: BigNumber +} + +export type Erc4626DepositStrategy = ( + args: Erc4626DepositPayload, + dependencies: Erc4626CommonDependencies & Erc4646ViewDependencies, +) => Promise> + +export interface Erc4626CommonDependencies { + provider: ethers.providers.Provider + network: Network + operationExecutor: Address + getSwapData: GetSwapData +} + +export interface Erc4626WithdrawPayload { + returnTokenSymbol: string + returnTokenPrecision: number + returnTokenAddress: Address + withdrawTokenSymbol: string + withdrawTokenPrecision: number + withdrawTokenAddress: Address + amount: BigNumber + vault: string + proxyAddress: Address + user: Address + slippage: BigNumber + quoteTokenPrice: BigNumber +} + +export type Erc4626WithdrawStrategy = ( + args: Erc4626WithdrawPayload, + dependencies: Erc4626CommonDependencies & Erc4646ViewDependencies, +) => Promise> diff --git a/packages/dma-library/src/types/common/erc4626-validation.ts b/packages/dma-library/src/types/common/erc4626-validation.ts new file mode 100644 index 00000000..1cff6f80 --- /dev/null +++ b/packages/dma-library/src/types/common/erc4626-validation.ts @@ -0,0 +1,15 @@ +export type Erc4626StrategyError = Erc4626MaxWithdrawalError | Erc4626MaxDepositError + +export type Erc4626MaxWithdrawalError = { + name: 'withdraw-more-than-available' + data: { + amount: string + } +} + +export type Erc4626MaxDepositError = { + name: 'deposit-more-than-possible' + data: { + amount: string + } +} diff --git a/packages/dma-library/src/types/common/erc4626-view.ts b/packages/dma-library/src/types/common/erc4626-view.ts new file mode 100644 index 00000000..c96f45cf --- /dev/null +++ b/packages/dma-library/src/types/common/erc4626-view.ts @@ -0,0 +1,338 @@ +import { Address } from '@deploy-configurations/types/address' +import { ZERO } from '@dma-common/constants' +import { RiskRatio } from '@domain/risk-ratio' +import BigNumber from 'bignumber.js' +import type { providers } from 'ethers' + +import { SupplyPosition } from '../ajna/ajna-earn-position' + +export enum FeeType { + CURATOR = 'curator', + PERFORMANCE = 'performance', +} +export enum AllocationType { + LENDING = 'lending', + LP = 'lp', + STAKING = 'staking', + OTHER = 'other', +} + +export enum AllocationInfoValueType { + LTV = 'ltv', + COLL_RATIO = 'coll-ratio', + APY = 'apy', +} + +export type AllocationAdditionalInfoType = { + valueType: AllocationInfoValueType + value: string +} + +interface ApyFromRewards { + token: string + value: BigNumber + per1kUsd?: BigNumber +} + +type VaultApyResponse = { + vault: { + apy: string + fee?: string + curator?: string + } + apyFromRewards?: { + token: string + value: string + per1kUsd?: string + }[] + rewards?: { + token: string + earned: string + claimable: string + }[] + allocations?: { + token: string + supply: string + riskRatio: string + }[] +} + +export type Erc4626SubgraphRepsonse = { + positions: { + earnCumulativeDepositInQuoteToken: string + earnCumulativeDepositUSD: string + earnCumulativeFeesInQuoteToken: string + earnCumulativeFeesUSD: string + earnCumulativeWithdrawInQuoteToken: string + earnCumulativeWithdrawUSD: string + id: string + shares: string + }[] + vaults: { + interestRates: { + timestamp: string + rate: string + }[] + totalAssets: string + totalShares: string + }[] +} + +export type Erc4646ViewDependencies = { + provider: providers.Provider + getVaultApyParameters: (vaultAddress: string) => Promise + getLazyVaultSubgraphResponse: ( + vaultAddress: string, + dpmAccount: string, + ) => Promise +} + +export type Erc4626Args = { + proxyAddress: string + user: string + vaultAddress: string + underlyingAsset: Token + quotePrice: BigNumber +} +export type Token = { + address: string + precision: number + symbol?: string +} + +/** + * Represents an ERC4626 position. + */ +export interface IErc4626Position extends SupplyPosition { + vault: Erc4626Vault + netValue: BigNumber + pnl: { + withFees: BigNumber + withoutFees: BigNumber + } + totalEarnings: { + withFees: BigNumber + withoutFees: BigNumber + } + apyFromRewards: { + per1d: ApyFromRewards[] + per7d: ApyFromRewards[] + per30d: ApyFromRewards[] + per90d: ApyFromRewards[] + per365d: ApyFromRewards[] + } + historicalApy: { + previousDayAverage: BigNumber + sevenDayAverage: BigNumber + thirtyDayAverage: BigNumber + } + tvl: BigNumber + maxWithdrawal: BigNumber + maxDeposit: BigNumber + allocations?: { + token: string + supply: BigNumber + riskRatio?: RiskRatio + }[] + rewards?: { + token: string + earned: BigNumber + claimable: BigNumber + }[] + + fee?: { + curator: string + type: FeeType + amount: BigNumber + } + + /** + * Simulates the deposit of the specified quote token amount into the ERC4626 position. + * @param quoteTokenAmount The amount of quote token to deposit. + * @returns The updated ERC4626 position. + */ + deposit(quoteTokenAmount: BigNumber): Erc4626Position + + /** + * Simulates the withdrawal the specified quote token amount from the ERC4626 position. + * @param quoteTokenAmount The amount of quote token to withdraw. + * @returns The updated ERC4626 position. + */ + withdraw(quoteTokenAmount: BigNumber): Erc4626Position + + /** + * Simulates closing of the ERC4626 position. + * @returns The closed ERC4626 position. + */ + close(): Erc4626Position +} + +interface Erc4626Vault { + address: string + quoteToken: string +} + +export class Erc4626Position implements IErc4626Position { + constructor( + public annualizedApy: BigNumber, + public annualizedApyFromRewards: + | { + token: string + value: BigNumber + per1kUsd?: BigNumber + }[] + | undefined, + public historicalApy: { + previousDayAverage: BigNumber + sevenDayAverage: BigNumber + thirtyDayAverage: BigNumber + }, + public vault: Erc4626Vault, + public owner: Address, + public quoteTokenAmount: BigNumber, + public marketPrice: BigNumber, + public netValue: BigNumber, + public pnl: { + withFees: BigNumber + withoutFees: BigNumber + }, + public totalEarnings: { withFees: BigNumber; withoutFees: BigNumber }, + public maxWithdrawal: BigNumber, + public maxDeposit: BigNumber, + public tvl: BigNumber, + public allocations?: { + token: string + supply: BigNumber + riskRatio?: RiskRatio + }[], + public rewards?: { + token: string + earned: BigNumber + claimable: BigNumber + }[], + public fee?: { + curator: string + type: FeeType + amount: BigNumber + }, + ) {} + + /** + * Represents the Annual Percentage Yield (APY) from native vault yield. + * @returns An object containing the APY for different time periods. + */ + get apy() { + return { + per1d: this.getTotalApyForDays({ days: 1 }), + per7d: this.getTotalApyForDays({ days: 7 }), + per30d: this.getTotalApyForDays({ days: 30 }), + per90d: this.getTotalApyForDays({ days: 90 }), + per365d: this.getTotalApyForDays({ days: 365 }), + } + } + + /** + * Calculates the annual percentage yield (APY) from additional rewards. + * @returns An object containing the APY for different time periods. + */ + get apyFromRewards() { + return { + per1d: this.getTotalApyFromRewardsForDays({ days: 1 }), + per7d: this.getTotalApyFromRewardsForDays({ days: 7 }), + per30d: this.getTotalApyFromRewardsForDays({ days: 30 }), + per90d: this.getTotalApyFromRewardsForDays({ days: 90 }), + per365d: this.getTotalApyFromRewardsForDays({ days: 365 }), + } + } + + /** + * Calculates the total APY (Annual Percentage Yield) from rewards for a given number of days. + * + * @param days - The number of days to calculate the APY for. + * @returns - An array of objects containing the token, value, and per1kUsd (optional) properties. + */ + getTotalApyFromRewardsForDays({ days }: { days: number }) { + return this.annualizedApyFromRewards + ? this.annualizedApyFromRewards.map(reward => { + return { + token: reward.token, + value: reward.value.div(365).times(days), + per1kUsd: reward.per1kUsd ? reward.per1kUsd.div(365).times(days) : undefined, + } + }) + : [] + } + + /** + * Calculates the total APY (Annual Percentage Yield) for a given number of days. + * + * @param {number} days - The number of days to calculate the total APY for. + * @returns - The total APY for the specified number of days. + */ + getTotalApyForDays({ days }: { days: number }) { + return this.annualizedApy.div(365).times(days) + } + + deposit(quoteTokenAmount: BigNumber) { + return new Erc4626Position( + this.annualizedApy, + this.annualizedApyFromRewards, + this.historicalApy, + this.vault, + this.owner, + this.quoteTokenAmount.plus(quoteTokenAmount), + this.marketPrice, + this.netValue, + this.pnl, + this.totalEarnings, + this.maxWithdrawal.plus(quoteTokenAmount), + this.maxDeposit.minus(quoteTokenAmount), + this.tvl, + this.allocations, + this.rewards, + this.fee, + ) + } + + withdraw(quoteTokenAmount: BigNumber) { + return new Erc4626Position( + this.annualizedApy, + this.annualizedApyFromRewards, + this.historicalApy, + this.vault, + this.owner, + this.quoteTokenAmount.minus(quoteTokenAmount), + this.marketPrice, + this.netValue, + this.pnl, + this.totalEarnings, + this.maxWithdrawal.minus(quoteTokenAmount), + this.maxDeposit.plus(quoteTokenAmount), + this.tvl, + this.allocations, + this.rewards, + this.fee, + ) + } + + close() { + return new Erc4626Position( + this.annualizedApy, + this.annualizedApyFromRewards, + this.historicalApy, + this.vault, + this.owner, + ZERO, + this.marketPrice, + this.netValue, + this.pnl, + this.totalEarnings, + ZERO, + this.maxDeposit, + this.tvl, + this.allocations, + this.rewards, + this.fee, + ) + } +} diff --git a/packages/dma-library/src/types/common/index.ts b/packages/dma-library/src/types/common/index.ts index cf034aa6..d307f494 100644 --- a/packages/dma-library/src/types/common/index.ts +++ b/packages/dma-library/src/types/common/index.ts @@ -1,5 +1,15 @@ import { Address, Swap, Tx } from '@dma-common/types' import { ethers } from 'ethers' +export type { Erc4626StrategyAddresses } from './erc4626-addresses' +export type { + Erc4626CommonDependencies, + Erc4626DepositPayload, + Erc4626DepositStrategy, + Erc4626WithdrawPayload, + Erc4626WithdrawStrategy, +} from './erc4626-strategies' +export type { IErc4626Position } from './erc4626-view' +export { Erc4626Position, FeeType } from './erc4626-view' export type Strategy = { simulation: { diff --git a/packages/dma-library/src/types/cumulatives.ts b/packages/dma-library/src/types/cumulatives.ts new file mode 100644 index 00000000..d00a0d35 --- /dev/null +++ b/packages/dma-library/src/types/cumulatives.ts @@ -0,0 +1,61 @@ +import { BigNumber } from 'bignumber.js' + +export interface LendingCumulativesData { + borrowCumulativeDepositUSD: BigNumber + borrowCumulativeDepositInQuoteToken: BigNumber + borrowCumulativeDepositInCollateralToken: BigNumber + borrowCumulativeWithdrawUSD: BigNumber + borrowCumulativeWithdrawInQuoteToken: BigNumber + borrowCumulativeWithdrawInCollateralToken: BigNumber + borrowCumulativeCollateralDeposit: BigNumber + borrowCumulativeCollateralWithdraw: BigNumber + borrowCumulativeDebtDeposit: BigNumber + borrowCumulativeDebtWithdraw: BigNumber + borrowCumulativeFeesUSD: BigNumber + borrowCumulativeFeesInQuoteToken: BigNumber + borrowCumulativeFeesInCollateralToken: BigNumber +} + +export interface LendingCumulativesRawData { + borrowCumulativeDepositUSD: number + borrowCumulativeDepositInQuoteToken: number + borrowCumulativeDepositInCollateralToken: number + borrowCumulativeWithdrawUSD: number + borrowCumulativeWithdrawInQuoteToken: number + borrowCumulativeWithdrawInCollateralToken: number + borrowCumulativeCollateralDeposit: number + borrowCumulativeCollateralWithdraw: number + borrowCumulativeDebtDeposit: number + borrowCumulativeDebtWithdraw: number + borrowCumulativeFeesUSD: number + borrowCumulativeFeesInQuoteToken: number + borrowCumulativeFeesInCollateralToken: number +} + +export interface EarnCumulativesData { + earnCumulativeFeesInQuoteToken: BigNumber + earnCumulativeQuoteTokenDeposit: BigNumber + earnCumulativeQuoteTokenWithdraw: BigNumber + earnCumulativeDepositUSD: BigNumber + earnCumulativeDepositInQuoteToken: BigNumber + earnCumulativeDepositInCollateralToken: BigNumber + earnCumulativeWithdrawUSD: BigNumber + earnCumulativeWithdrawInQuoteToken: BigNumber + earnCumulativeWithdrawInCollateralToken: BigNumber + earnCumulativeFeesUSD: BigNumber + earnCumulativeFeesInCollateralToken: BigNumber +} + +export interface EarnCumulativesRawData { + earnCumulativeFeesInQuoteToken: number + earnCumulativeQuoteTokenDeposit: number + earnCumulativeQuoteTokenWithdraw: number + earnCumulativeDepositUSD: number + earnCumulativeDepositInQuoteToken: number + earnCumulativeDepositInCollateralToken: number + earnCumulativeWithdrawUSD: number + earnCumulativeWithdrawInQuoteToken: number + earnCumulativeWithdrawInCollateralToken: number + earnCumulativeFeesUSD: number + earnCumulativeFeesInCollateralToken: number +} diff --git a/packages/dma-library/src/types/index.ts b/packages/dma-library/src/types/index.ts index 5457998b..91703d53 100644 --- a/packages/dma-library/src/types/index.ts +++ b/packages/dma-library/src/types/index.ts @@ -2,7 +2,7 @@ import { isProtocol, Protocol, ProtocolNames } from '@deploy-configurations/type import { ActionCall } from './action-call' import { calldataTypes } from './actions' -import type { AjnaError, AjnaStrategy } from './ajna' +import type { AjnaError, SummerStrategy } from './ajna' import { AjnaBorrowPayload, AjnaCommonDependencies, @@ -20,7 +20,6 @@ import { MorphoBlueMarket, MorphoBluePosition } from './morphoblue' import type { IOperation, WithAjnaBucketPrice, - WithAjnaStrategyAddresses, WithBorrowing, WithCollateral, WithCollateralAndWithdrawal, @@ -34,6 +33,7 @@ import type { WithPosition, WithPositionAndLockedCollateral, WithProxy, + WithSummerStrategyAddresses, WithSwap, WithWithdrawal, } from './operations' @@ -57,7 +57,7 @@ import type { SwapData } from './swap-data' export type { IMultiplyStrategy, IStrategy } export type { CommonDMADependencies } from './common' export { FlashloanProvider } -export type { AjnaError, AjnaStrategy, Strategy } +export type { AjnaError, Strategy, SummerStrategy } export { AjnaEarnPosition, AjnaPosition } export type { @@ -70,7 +70,12 @@ export type { AjnaOpenMultiplyPayload, } -export { AaveLikePosition, AaveLikeTokens } from './aave-like' +export { + AaveLikePosition, + AaveLikePositionV2, + AaveLikeProtocolEnum, + AaveLikeTokens, +} from './aave-like' export { ActionCall } export { calldataTypes } @@ -78,7 +83,6 @@ export { calldataTypes } export type { IOperation, WithAjnaBucketPrice, - WithAjnaStrategyAddresses, WithBorrowing, WithCollateral, WithCollateralAndWithdrawal, @@ -92,6 +96,7 @@ export type { WithPosition, WithPositionAndLockedCollateral, WithProxy, + WithSummerStrategyAddresses, WithSwap, WithWithdrawal, } @@ -117,3 +122,20 @@ export type { Swap } export { MorphoBluePosition } export type { MorphoBlueMarket } + +export type { + Erc4626CommonDependencies, + Erc4626DepositPayload, + Erc4626DepositStrategy, + Erc4626StrategyAddresses, + Erc4626WithdrawPayload, + Erc4626WithdrawStrategy, + IErc4626Position, +} from './common' +export { Erc4626Position, FeeType } from './common' +export type { + EarnCumulativesData, + EarnCumulativesRawData, + LendingCumulativesData, + LendingCumulativesRawData, +} from './cumulatives' diff --git a/packages/dma-library/src/types/morphoblue/morphoblue-position.ts b/packages/dma-library/src/types/morphoblue/morphoblue-position.ts index 18c40c25..37b5786f 100644 --- a/packages/dma-library/src/types/morphoblue/morphoblue-position.ts +++ b/packages/dma-library/src/types/morphoblue/morphoblue-position.ts @@ -1,6 +1,8 @@ import { Address } from '@deploy-configurations/types/address' -import { ZERO } from '@dma-common/constants' +import { ONE, ZERO } from '@dma-common/constants' import { negativeToZero, normalizeValue } from '@dma-common/utils/common' +import { getBuyingPower } from '@dma-library/views/common' +import { MorphoCumulativesData } from '@dma-library/views/morpho' import { IRiskRatio, RiskRatio } from '@domain' import { BigNumber } from 'bignumber.js' @@ -26,7 +28,7 @@ export interface LendingPosition { withoutFees: BigNumber } - debtAvailable(collateralAmount?: BigNumber): BigNumber + debtAvailable(collateralAmount?: BigNumber, debtAmount?: BigNumber): BigNumber deposit(amount: BigNumber): LendingPosition @@ -37,76 +39,122 @@ export interface LendingPosition { payback(amount: BigNumber): LendingPosition } +interface MarketParams { + id: string + loanToken: Address + collateralToken: Address + oracle: Address + irm: Address + lltv: BigNumber +} + +interface Market { + totalSupplyAssets: BigNumber + totalSupplyShares: BigNumber + totalBorrowAssets: BigNumber + totalBorrowShares: BigNumber + lastUpdate: BigNumber + fee: BigNumber +} + export class MorphoBluePosition implements LendingPosition { constructor( public owner: Address, public collateralAmount: BigNumber, public debtAmount: BigNumber, public collateralPrice: BigNumber, - public quotePrice: BigNumber, + public debtPrice: BigNumber, + public marketParams: MarketParams, + public market: Market, + // collateral / debt + public price: BigNumber, + public rate: BigNumber, public pnl: { withFees: BigNumber withoutFees: BigNumber + cumulatives: MorphoCumulativesData }, ) {} get liquidationPrice() { - return new BigNumber(1234) + return normalizeValue( + ONE.div(this.collateralAmount.times(this.maxRiskRatio.loanToValue).div(this.debtAmount)), + ) } get marketPrice() { - return this.collateralPrice.div(this.quotePrice) + return this.price } get liquidationToMarketPrice() { return this.liquidationPrice.div(this.marketPrice) } + // How much collateral can we withdraw to not get liquidated, (to get to the verge of liquidation) get collateralAvailable() { - const collateralAvailable = ZERO + const collateralAvailable = this.collateralAmount.minus( + this.debtAmount.div(this.maxRiskRatio.loanToValue).div(this.price), + ) return negativeToZero(normalizeValue(collateralAvailable)) } get riskRatio() { - const loanToValue = this.debtAmount - .times(this.quotePrice) - .div(this.collateralAmount.times(this.collateralPrice)) + const loanToValue = this.debtAmount.div(this.collateralAmount.times(this.price)) return new RiskRatio(normalizeValue(loanToValue), RiskRatio.TYPE.LTV) } get maxRiskRatio() { - const loanToValue = new BigNumber(0.85) - return new RiskRatio(normalizeValue(loanToValue), RiskRatio.TYPE.LTV) + return new RiskRatio(normalizeValue(this.marketParams.lltv), RiskRatio.TYPE.LTV) } get borrowRate(): BigNumber { - return ZERO + return this.rate } get netValue(): BigNumber { return this.collateralAmount .times(this.collateralPrice) - .minus(this.debtAmount.times(this.quotePrice)) + .minus(this.debtAmount.times(this.debtPrice)) } get minRiskRatio() { - const loanToValue = ZERO - - return new RiskRatio(normalizeValue(loanToValue), RiskRatio.TYPE.LTV) + return new RiskRatio(normalizeValue(ZERO), RiskRatio.TYPE.LTV) } get buyingPower() { - return this.collateralAmount - .times(this.collateralPrice) - .times(this.maxRiskRatio.loanToValue) - .minus(this.debtAmount.times(this.quotePrice)) - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - debtAvailable(collateralAmount?: BigNumber) { - return ZERO + return getBuyingPower({ + netValue: this.netValue, + collateralPrice: this.collateralPrice, + marketPrice: this.marketPrice, + debtAmount: this.debtAmount, + maxRiskRatio: this.maxRiskRatio, + }) + } + + get liquidationPenalty() { + const M = new BigNumber(1.15) + const BETA = new BigNumber(0.3) + + return BigNumber.min( + M, + ONE.div(BETA.times(this.maxRiskRatio.loanToValue).plus(ONE.minus(BETA))), + ).minus(ONE) + } + + debtAvailable(collateralAmount?: BigNumber, debtAmount?: BigNumber) { + // (debt + addDebt) / ((col + addedColl) * price) = lltv + // lltv*price*(col + addedColl) - debt = addDebt + + return negativeToZero( + normalizeValue( + this.maxRiskRatio.loanToValue + .times(this.price) + .times(collateralAmount || this.collateralAmount) + .minus(debtAmount || this.debtAmount), + ), + ) } deposit(collateralAmount: BigNumber) { @@ -116,7 +164,11 @@ export class MorphoBluePosition implements LendingPosition { newCollateralAmount, this.debtAmount, this.collateralPrice, - this.quotePrice, + this.debtPrice, + this.marketParams, + this.market, + this.price, + this.rate, this.pnl, ) } @@ -128,7 +180,11 @@ export class MorphoBluePosition implements LendingPosition { newCollateralAmount, this.debtAmount, this.collateralPrice, - this.quotePrice, + this.debtPrice, + this.marketParams, + this.market, + this.price, + this.rate, this.pnl, ) } @@ -140,7 +196,11 @@ export class MorphoBluePosition implements LendingPosition { this.collateralAmount, newDebt, this.collateralPrice, - this.quotePrice, + this.debtPrice, + this.marketParams, + this.market, + this.price, + this.rate, this.pnl, ) } @@ -152,7 +212,11 @@ export class MorphoBluePosition implements LendingPosition { this.collateralAmount, newDebt, this.collateralPrice, - this.quotePrice, + this.debtPrice, + this.marketParams, + this.market, + this.price, + this.rate, this.pnl, ) } @@ -163,7 +227,11 @@ export class MorphoBluePosition implements LendingPosition { ZERO, ZERO, this.collateralPrice, - this.quotePrice, + this.debtPrice, + this.marketParams, + this.market, + this.price, + this.rate, this.pnl, ) } diff --git a/packages/dma-library/src/types/operations.ts b/packages/dma-library/src/types/operations.ts index 0f3922ca..f0dce5ab 100644 --- a/packages/dma-library/src/types/operations.ts +++ b/packages/dma-library/src/types/operations.ts @@ -5,7 +5,8 @@ import { AaveLikeStrategyAddresses } from '@dma-library/operations/aave-like' import { MorphoBlueStrategyAddresses } from '@dma-library/operations/morphoblue/addresses' import { BigNumber } from 'bignumber.js' -import { AjnaStrategyAddresses } from '../operations/ajna' +import { SummerStrategyAddresses } from '../operations/ajna' +import { AaveLikePosition } from './aave-like' import { ActionCall } from './action-call' import { FlashloanProvider } from './common' import { MorphoBlueMarket } from './morphoblue' @@ -137,6 +138,10 @@ export type WithBorrowedDebt = { } } +export type WithAaveLikePosition = { + position: AaveLikePosition +} + export type WithPositionAndLockedCollateral = WithPosition & { position: WithPosition['position'] & WithLockedCollateral } @@ -145,8 +150,8 @@ export type WithAaveLikeStrategyAddresses = { addresses: AaveLikeStrategyAddresses } -export type WithAjnaStrategyAddresses = { - addresses: AjnaStrategyAddresses +export type WithSummerStrategyAddresses = { + addresses: SummerStrategyAddresses } export type WithEMode = { @@ -223,3 +228,16 @@ export type WithCloseToOpenSwap = { export type WithAfterOpenSwap = { swapAfterOpen: WithSwap['swap'] } + +export type WithAToken = { + aToken: { + address: Address + amount: BigNumber + } +} + +export type WithVDToken = { + vdToken: { + address: Address + } +} diff --git a/packages/dma-library/src/views/ajna/index.ts b/packages/dma-library/src/views/ajna/index.ts index 7cb9405f..9c301a51 100644 --- a/packages/dma-library/src/views/ajna/index.ts +++ b/packages/dma-library/src/views/ajna/index.ts @@ -2,8 +2,11 @@ import poolAbi from '@abis/external/protocols/ajna/ajnaPoolERC20.json' import poolInfoAbi from '@abis/external/protocols/ajna/poolInfoUtils.json' import { Address } from '@deploy-configurations/types/address' import { ZERO } from '@dma-common/constants' +import { normalizeValue } from '@dma-common/utils/common' +import { EarnCumulativesData, LendingCumulativesData } from '@dma-library/types' import { AjnaEarnPosition, AjnaPosition } from '@dma-library/types/ajna' import { AjnaPool } from '@dma-library/types/ajna/ajna-pool' +import { isCorrelatedPosition } from '@dma-library/utils/swap' import { BigNumber } from 'bignumber.js' import { ethers } from 'ethers' @@ -12,6 +15,8 @@ interface Args { poolAddress: Address collateralPrice: BigNumber quotePrice: BigNumber + collateralToken: string + quoteToken: string } interface EarnData { @@ -30,24 +35,17 @@ export interface GetPoolData { (poolAddress: Address): Promise } -export interface AjnaCumulativesData { - borrowCumulativeDepositUSD: BigNumber - borrowCumulativeFeesUSD: BigNumber - borrowCumulativeWithdrawUSD: BigNumber - earnCumulativeFeesInQuoteToken: BigNumber - earnCumulativeQuoteTokenDeposit: BigNumber - earnCumulativeQuoteTokenWithdraw: BigNumber -} +export type AjnaCumulativesData = LendingCumulativesData & EarnCumulativesData -export interface GetCumulativesData { - (proxyAddress: Address, poolAddress: Address): Promise +export interface GetCumulativesData { + (proxyAddress: Address, poolAddress: Address): Promise } interface Dependencies { poolInfoAddress: Address provider: ethers.providers.Provider getPoolData: GetPoolData - getCumulatives: GetCumulativesData + getCumulatives: GetCumulativesData } interface EarnDependencies { @@ -60,51 +58,68 @@ interface EarnDependencies { const WAD = new BigNumber(10).pow(18) export async function getPosition( - { proxyAddress, poolAddress, collateralPrice, quotePrice }: Args, + { proxyAddress, poolAddress, collateralPrice, quotePrice, collateralToken, quoteToken }: Args, { poolInfoAddress, provider, getPoolData, getCumulatives }: Dependencies, ): Promise { const poolInfo = new ethers.Contract(poolInfoAddress, poolInfoAbi, provider) - const [ - pool, - borrowerInfo, - { - borrowCumulativeFeesUSD, - borrowCumulativeDepositUSD, - borrowCumulativeWithdrawUSD, - earnCumulativeFeesInQuoteToken, - earnCumulativeQuoteTokenDeposit, - earnCumulativeQuoteTokenWithdraw, - }, - ] = await Promise.all([ + const [pool, borrowerInfo, cumulatives] = await Promise.all([ getPoolData(poolAddress), poolInfo.borrowerInfo(poolAddress, proxyAddress), getCumulatives(proxyAddress, poolAddress), ]) + const { + borrowCumulativeWithdrawInCollateralToken, + borrowCumulativeDepositInCollateralToken, + borrowCumulativeFeesInCollateralToken, + borrowCumulativeWithdrawInQuoteToken, + borrowCumulativeDepositInQuoteToken, + borrowCumulativeFeesInQuoteToken, + } = cumulatives const collateralAmount = new BigNumber(borrowerInfo.collateral_.toString()).div(WAD) const debtAmount = new BigNumber(borrowerInfo.debt_.toString()).div(WAD) const netValue = collateralAmount.times(collateralPrice).minus(debtAmount.times(quotePrice)) - const pnl = { - withFees: borrowCumulativeWithdrawUSD - .plus(netValue) - .minus(borrowCumulativeFeesUSD) - .minus(borrowCumulativeDepositUSD) - .div(borrowCumulativeDepositUSD), - withoutFees: borrowCumulativeWithdrawUSD - .plus(netValue) - .minus(borrowCumulativeDepositUSD) - .div(borrowCumulativeDepositUSD), - cumulatives: { - borrowCumulativeDepositUSD, - borrowCumulativeFeesUSD, - borrowCumulativeWithdrawUSD, - earnCumulativeFeesInQuoteToken, - earnCumulativeQuoteTokenDeposit, - earnCumulativeQuoteTokenWithdraw, - }, + const isCorrelated = isCorrelatedPosition(collateralToken, quoteToken) + + let pnl + + if (isCorrelated) { + pnl = { + withFees: normalizeValue( + borrowCumulativeWithdrawInQuoteToken + .plus(netValue.div(quotePrice)) + .minus(borrowCumulativeDepositInQuoteToken) + .minus(borrowCumulativeFeesInQuoteToken) + .div(borrowCumulativeDepositInQuoteToken), + ), + withoutFees: normalizeValue( + borrowCumulativeWithdrawInQuoteToken + .plus(netValue.div(quotePrice)) + .minus(borrowCumulativeDepositInQuoteToken) + .div(borrowCumulativeDepositInQuoteToken), + ), + cumulatives, + } + } else { + pnl = { + withFees: normalizeValue( + borrowCumulativeWithdrawInCollateralToken + .plus(netValue.div(collateralPrice)) + .minus(borrowCumulativeDepositInCollateralToken) + .minus(borrowCumulativeFeesInCollateralToken) + .div(borrowCumulativeDepositInCollateralToken), + ), + withoutFees: normalizeValue( + borrowCumulativeWithdrawInCollateralToken + .plus(netValue.div(collateralPrice)) + .minus(borrowCumulativeDepositInCollateralToken) + .div(borrowCumulativeDepositInCollateralToken), + ), + cumulatives, + } } return new AjnaPosition( @@ -205,5 +220,10 @@ export async function getEarnPosition( pnl, totalEarnings, isBucketFrozen, + { + previousDayAverage: pool.lendApr, + sevenDayAverage: pool.lendApr7dAverage, + thirtyDayAverage: pool.lendApr30dAverage, + }, ) } diff --git a/packages/dma-library/src/views/common/erc4626.ts b/packages/dma-library/src/views/common/erc4626.ts new file mode 100644 index 00000000..7cec5e14 --- /dev/null +++ b/packages/dma-library/src/views/common/erc4626.ts @@ -0,0 +1,171 @@ +import { RISK_RATIO_CTOR_TYPE, RiskRatio } from '@domain/risk-ratio' +import { IERC4626 } from '@typechain/index' +import { BigNumber } from 'bignumber.js' +import { ethers } from 'ethers' + +import erc4626abi from '../../../../abis/external/tokens/IERC4626.json' +import { Erc4626Position, FeeType } from '../../types/common' +import type { + Erc4626Args, + Erc4626SubgraphRepsonse, + Erc4646ViewDependencies, +} from '../../types/common/erc4626-view' + +/** + * Retrieves the ERC4626 position based on the provided arguments and dependencies. + * + * @param {Erc4626Args} args - The arguments required to fetch the ERC4626 position. + * @param {Erc4646ViewDependencies} dependencies - The dependencies required to fetch the ERC4626 position. + * @returns {Promise} - A promise that resolves to the ERC4626 position. + */ +export async function getErc4626Position( + { proxyAddress, vaultAddress, quotePrice, user, underlyingAsset }: Erc4626Args, + { provider, getLazyVaultSubgraphResponse, getVaultApyParameters }: Erc4646ViewDependencies, +): Promise { + const { precision } = underlyingAsset + + const vaultContractInstance = new ethers.Contract(vaultAddress, erc4626abi, provider) as IERC4626 + const [vaultParameters, subgraphResponse] = await Promise.all([ + getVaultApyParameters(vaultAddress), + getLazyVaultSubgraphResponse(vaultAddress, proxyAddress), + ]) + + const positionParameters = subgraphResponse.positions[0] + const vaultParametersFromSubgraph = subgraphResponse.vaults[0] + const [balance, maxWithdraw, maxDeposit] = await Promise.all([ + vaultContractInstance.balanceOf(proxyAddress), + vaultContractInstance.maxWithdraw(proxyAddress), + vaultContractInstance.maxDeposit(proxyAddress), + ]) + const assets = await vaultContractInstance.convertToAssets(balance) + const quoteTokenAmount = new BigNumber(ethers.utils.formatUnits(assets, precision).toString()) + const maxWithdrawalAmount = new BigNumber( + ethers.utils.formatUnits(maxWithdraw, precision).toString(), + ) + const maxDepositAmount = new BigNumber(ethers.utils.formatUnits(maxDeposit, precision).toString()) + + const vault = { + address: vaultAddress, + quoteToken: underlyingAsset.address, + } + const netValue = quoteTokenAmount + + const totalEarnings = { + withFees: netValue + .minus( + new BigNumber(positionParameters.earnCumulativeDepositInQuoteToken).minus( + new BigNumber(positionParameters.earnCumulativeWithdrawInQuoteToken), + ), + ) + .minus(new BigNumber(positionParameters.earnCumulativeFeesInQuoteToken)), + withoutFees: netValue.minus( + new BigNumber(positionParameters.earnCumulativeDepositInQuoteToken).minus( + new BigNumber(positionParameters.earnCumulativeWithdrawInQuoteToken), + ), + ), + } + + const pnl = { + withFees: totalEarnings.withFees.div(netValue), + withoutFees: totalEarnings.withoutFees.div(netValue), + } + + const annualizedApy = new BigNumber(vaultParameters.vault.apy) + const annualizedApyFromRewards = vaultParameters.apyFromRewards + ? vaultParameters.apyFromRewards.map(reward => { + return { + token: reward.token, + value: new BigNumber(reward.value), + per1kUsd: reward.per1kUsd ? new BigNumber(reward.per1kUsd) : undefined, + } + }) + : undefined + + const tvl = new BigNumber( + ethers.utils.formatUnits(vaultParametersFromSubgraph.totalAssets, precision).toString(), + ) + + const allocations = vaultParameters.allocations + ? vaultParameters.allocations.map(allocation => { + return { + token: allocation.token, + supply: new BigNumber(allocation.supply), + riskRatio: new RiskRatio(new BigNumber(allocation.riskRatio), RISK_RATIO_CTOR_TYPE.LTV), + } + }) + : undefined + + const rewards = vaultParameters.rewards + ? vaultParameters.rewards.map(reward => { + return { + token: reward.token, + earned: new BigNumber(reward.earned), + claimable: new BigNumber(reward.claimable), + } + }) + : undefined + + const fee = vaultParameters.vault.fee + ? { + curator: vaultParameters.vault.curator ? vaultParameters.vault.curator : '', + type: FeeType.CURATOR, + amount: new BigNumber(vaultParameters.vault.fee), + } + : undefined + + return new Erc4626Position( + annualizedApy, + annualizedApyFromRewards, + getHistoricalApys(subgraphResponse), + vault, + user, + quoteTokenAmount, + quotePrice, + netValue, + pnl, + totalEarnings, + maxWithdrawalAmount, + maxDepositAmount, + tvl, + allocations, + rewards, + fee, + ) +} + +/** + * Calculates the historical APYs (Annual Percentage Yields) based on the given position parameters. + * @param positionParameters - The position parameters containing the vault and interest rates. + * @returns An object containing the historical APYs. + */ +function getHistoricalApys(subgraphResponse: Erc4626SubgraphRepsonse) { + const historicalApy = { + previousDayAverage: new BigNumber(0), + sevenDayAverage: new BigNumber(0), + thirtyDayAverage: new BigNumber(0), + } + const previousDayAverage = subgraphResponse.vaults[0].interestRates[0] + const sevenDayRates = subgraphResponse.vaults[0].interestRates.slice(0, 7) + const thirtyDayRates = subgraphResponse.vaults[0].interestRates.slice(0, 30) + + if (sevenDayRates.length > 0) { + historicalApy.sevenDayAverage = sevenDayRates + .reduce((acc, rate) => { + return acc.plus(new BigNumber(rate.rate)) + }, new BigNumber(0)) + .div(sevenDayRates.length) + } + + if (thirtyDayRates.length > 0) { + historicalApy.thirtyDayAverage = thirtyDayRates + .reduce((acc, rate) => { + return acc.plus(new BigNumber(rate.rate)) + }, new BigNumber(0)) + .div(thirtyDayRates.length) + } + if (subgraphResponse.vaults[0].interestRates.length > 0) { + historicalApy.previousDayAverage = new BigNumber(previousDayAverage.rate) + } + + return historicalApy +} diff --git a/packages/dma-library/src/views/common/get-buying-power.ts b/packages/dma-library/src/views/common/get-buying-power.ts new file mode 100644 index 00000000..5b02e254 --- /dev/null +++ b/packages/dma-library/src/views/common/get-buying-power.ts @@ -0,0 +1,29 @@ +import { ONE } from '@dma-common/constants' +import { negativeToZero } from '@dma-common/utils/common' +import { RiskRatio } from '@domain' +import BigNumber from 'bignumber.js' + +// buying power in $ +export const getBuyingPower = ({ + netValue, + collateralPrice, + marketPrice, + debtAmount, + maxRiskRatio, +}: { + netValue: BigNumber + collateralPrice: BigNumber + marketPrice: BigNumber + debtAmount: BigNumber + maxRiskRatio: RiskRatio +}) => { + const netValueInCollateral = netValue.div(collateralPrice) + + return negativeToZero( + netValueInCollateral + .div(ONE.minus(maxRiskRatio.loanToValue)) + .minus(netValueInCollateral) + .minus(debtAmount.div(marketPrice)) + .times(collateralPrice), + ) +} diff --git a/packages/dma-library/src/views/common/index.ts b/packages/dma-library/src/views/common/index.ts new file mode 100644 index 00000000..1fe31a3b --- /dev/null +++ b/packages/dma-library/src/views/common/index.ts @@ -0,0 +1,2 @@ +export { getErc4626Position } from './erc4626' +export * from './get-buying-power' diff --git a/packages/dma-library/src/views/morpho/index.ts b/packages/dma-library/src/views/morpho/index.ts index 361ca18b..efa8c24a 100644 --- a/packages/dma-library/src/views/morpho/index.ts +++ b/packages/dma-library/src/views/morpho/index.ts @@ -1,52 +1,145 @@ -import { MorphoBluePosition } from '@dma-library/types' +import irmAbi from '@abis/external/protocols/morphoblue/irm.json' +import morphoAbi from '@abis/external/protocols/morphoblue/morpho.json' +import oracleAbi from '@abis/external/protocols/morphoblue/oracle.json' +import { normalizeValue } from '@dma-common/utils/common' +import { getMarketRate } from '@dma-library/strategies/morphoblue/validation' +import { LendingCumulativesData, MorphoBluePosition } from '@dma-library/types' +import { GetCumulativesData } from '@dma-library/views' import { BigNumber } from 'bignumber.js' import { ethers } from 'ethers' +import { ONE, TEN } from '../../../../dma-common/constants/numbers' +import type { Irm } from '../../../../dma-contracts/typechain/abis/external/protocols/morphoblue/Irm' +import type { Morpho } from '../../../../dma-contracts/typechain/abis/external/protocols/morphoblue/Morpho' +import type { Oracle } from '../../../../dma-contracts/typechain/abis/external/protocols/morphoblue/Oracle' + interface Args { proxyAddress: string - collateralPrice: BigNumber - quotePrice: BigNumber + collateralPriceUSD: BigNumber + quotePriceUSD: BigNumber + collateralPrecision: number + quotePrecision: number + marketId: string } +export type MorphoCumulativesData = LendingCumulativesData + interface Dependencies { provider: ethers.providers.Provider - getCumulatives: () => { - borrowCumulativeDepositUSD: BigNumber - borrowCumulativeFeesUSD: BigNumber - borrowCumulativeWithdrawUSD: BigNumber - } + morphoAddress: string + getCumulatives: GetCumulativesData +} + +const VIRTUAL_SHARES = TEN.pow(6) +const VIRTUAL_ASSETS = ONE + +function mulDivDown(x: BigNumber, y: BigNumber, d: BigNumber): BigNumber { + return x.times(y).div(d) +} + +function toAssetsDown( + shares: BigNumber, + totalAssets: BigNumber, + totalShares: BigNumber, +): BigNumber { + return mulDivDown(shares, totalAssets.plus(VIRTUAL_ASSETS), totalShares.plus(VIRTUAL_SHARES)) } export async function getMorphoPosition( - { proxyAddress, collateralPrice, quotePrice }: Args, - { getCumulatives }: Dependencies, + { + proxyAddress, + collateralPriceUSD, + quotePriceUSD, + marketId, + collateralPrecision, + quotePrecision, + }: Args, + { getCumulatives, morphoAddress, provider }: Dependencies, ): Promise { - const collateralAmount = new BigNumber(5) - const debtAmount = new BigNumber(2000) + const morpho = new ethers.Contract(morphoAddress, morphoAbi, provider) as any as Morpho + + const marketParams = await morpho.idToMarketParams(marketId) + const market = await morpho.market(marketId) + const positionParams = await morpho.position(marketId, proxyAddress) + + const totals = { + totalSupplyAssets: new BigNumber(market.totalSupplyAssets.toString()).div( + TEN.pow(quotePrecision), + ), + totalSupplyShares: new BigNumber(market.totalSupplyShares.toString()).div(TEN.pow(24)), + totalBorrowAssets: new BigNumber(market.totalBorrowAssets.toString()).div( + TEN.pow(quotePrecision), + ), + totalBorrowShares: new BigNumber(market.totalBorrowShares.toString()).div(TEN.pow(24)), + } + + const oracle = new ethers.Contract(marketParams.oracle, oracleAbi, provider) as any as Oracle + const irm = new ethers.Contract(marketParams.irm, irmAbi, provider) as any as Irm + + const price = await oracle.price() + const rate = await irm.borrowRateView(marketParams, market) + + const apy = getMarketRate(rate.toString()) + + const debtAmount = toAssetsDown( + new BigNumber(positionParams.borrowShares.toString()), + new BigNumber(market.totalBorrowAssets.toString()), + new BigNumber(market.totalBorrowShares.toString()), + ) + .integerValue() + .div(TEN.pow(quotePrecision)) + const collateralAmount = new BigNumber(positionParams.collateral.toString()).div( + TEN.pow(collateralPrecision), + ) + + const cumulatives = await getCumulatives(proxyAddress, marketId) - const { borrowCumulativeWithdrawUSD, borrowCumulativeFeesUSD, borrowCumulativeDepositUSD } = - getCumulatives() + const { + borrowCumulativeWithdrawInCollateralToken, + borrowCumulativeDepositInCollateralToken, + borrowCumulativeFeesInCollateralToken, + } = cumulatives - const netValue = collateralAmount.times(collateralPrice).minus(debtAmount.times(quotePrice)) + const netValue = collateralAmount.times(collateralPriceUSD).minus(debtAmount.times(quotePriceUSD)) const pnl = { - withFees: borrowCumulativeWithdrawUSD - .plus(netValue) - .minus(borrowCumulativeFeesUSD) - .minus(borrowCumulativeDepositUSD) - .div(borrowCumulativeDepositUSD), - withoutFees: borrowCumulativeWithdrawUSD - .plus(netValue) - .minus(borrowCumulativeDepositUSD) - .div(borrowCumulativeDepositUSD), + withFees: normalizeValue( + borrowCumulativeWithdrawInCollateralToken + .plus(netValue.div(collateralPriceUSD)) + .minus(borrowCumulativeDepositInCollateralToken) + .minus(borrowCumulativeFeesInCollateralToken) + .div(borrowCumulativeDepositInCollateralToken), + ), + withoutFees: normalizeValue( + borrowCumulativeWithdrawInCollateralToken + .plus(netValue.div(collateralPriceUSD)) + .minus(borrowCumulativeDepositInCollateralToken) + .div(borrowCumulativeDepositInCollateralToken), + ), + cumulatives, } return new MorphoBluePosition( proxyAddress, collateralAmount, debtAmount, - collateralPrice, - quotePrice, + collateralPriceUSD, + quotePriceUSD, + { + id: marketId, + loanToken: marketParams.loanToken, + collateralToken: marketParams.collateralToken, + oracle: marketParams.oracle, + irm: marketParams.irm, + lltv: new BigNumber(marketParams.lltv.toString()).div(TEN.pow(18)), + }, + { + ...totals, + lastUpdate: new BigNumber(market.lastUpdate.toString()), + fee: new BigNumber(market.fee.toString()), + }, + new BigNumber(price.toString()).div(TEN.pow(36 + quotePrecision - collateralPrecision)), + apy, pnl, ) }