diff --git a/packages/blue-sdk-viem/contracts/vault-v2/GetVaultV2MorphoMarketV1AdapterV2.sol b/packages/blue-sdk-viem/contracts/vault-v2/GetVaultV2MorphoMarketV1AdapterV2.sol new file mode 100644 index 00000000..aa748643 --- /dev/null +++ b/packages/blue-sdk-viem/contracts/vault-v2/GetVaultV2MorphoMarketV1AdapterV2.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {IMorphoMarketV1AdapterV2} from "./interfaces/IMorphoMarketV1AdapterV2.sol"; + + +struct MarketSupplyShares { + bytes32 marketId; + uint256 supplyShares; +} + +struct VaultV2MorphoMarketV1AdapterV2Response { + address parentVault; + address skimRecipient; + address adaptiveCurveIrm; + MarketSupplyShares[] marketSupplyShares; +} + +contract GetVaultV2MorphoMarketV1AdapterV2 { + function query(IMorphoMarketV1AdapterV2 adapter) + external + view + returns (VaultV2MorphoMarketV1AdapterV2Response memory res) + { + res.parentVault = adapter.parentVault(); + res.skimRecipient = adapter.skimRecipient(); + res.adaptiveCurveIrm = adapter.adaptiveCurveIrm(); + + uint256 length = adapter.marketIdsLength(); + res.marketSupplyShares = new MarketSupplyShares[](length); + for (uint256 i = 0; i < length; i++) { + bytes32 marketId = adapter.marketIds(i); + res.marketSupplyShares[i] = MarketSupplyShares(marketId, adapter.supplyShares(marketId)); + } + } +} diff --git a/packages/blue-sdk-viem/contracts/vault-v2/interfaces/IMorphoMarketV1AdapterV2.sol b/packages/blue-sdk-viem/contracts/vault-v2/interfaces/IMorphoMarketV1AdapterV2.sol new file mode 100644 index 00000000..02980dd4 --- /dev/null +++ b/packages/blue-sdk-viem/contracts/vault-v2/interfaces/IMorphoMarketV1AdapterV2.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2025 Morpho Association +pragma solidity >=0.5.0; + +import {IAdapter} from "./IAdapter.sol"; +import {MarketParams} from "../../interfaces/IMorpho.sol"; + +interface IMorphoMarketV1AdapterV2 is IAdapter { + /* EVENTS */ + + event Submit(bytes4 indexed selector, bytes data, uint256 executableAt); + event Revoke(address indexed sender, bytes4 indexed selector, bytes data); + event Accept(bytes4 indexed selector, bytes data); + event Abdicate(bytes4 indexed selector); + event IncreaseTimelock(bytes4 indexed selector, uint256 newDuration); + event DecreaseTimelock(bytes4 indexed selector, uint256 newDuration); + event SetSkimRecipient(address indexed newSkimRecipient); + event Skim(address indexed token, uint256 assets); + event BurnShares(bytes32 indexed marketId, uint256 supplyShares); + event Allocate(bytes32 indexed marketId, uint256 newAllocation, uint256 mintedShares); + event Deallocate(bytes32 indexed marketId, uint256 newAllocation, uint256 burnedShares); + + /* ERRORS */ + + error Abdicated(); + error AutomaticallyTimelocked(); + error DataAlreadyPending(); + error DataNotTimelocked(); + error IrmMismatch(); + error LoanAssetMismatch(); + error SharePriceAboveOne(); + error TimelockNotDecreasing(); + error TimelockNotExpired(); + error TimelockNotIncreasing(); + error Unauthorized(); + + /* VIEW FUNCTIONS */ + + function factory() external view returns (address); + function parentVault() external view returns (address); + function asset() external view returns (address); + function morpho() external view returns (address); + function marketIds(uint256 index) external view returns (bytes32); + function supplyShares(bytes32 marketId) external view returns (uint256); + function adapterId() external view returns (bytes32); + function skimRecipient() external view returns (address); + function marketIdsLength() external view returns (uint256); + function adaptiveCurveIrm() external view returns (address); + function allocation(MarketParams memory marketParams) external view returns (uint256); + function expectedSupplyAssets(bytes32 marketId) external view returns (uint256); + function ids(MarketParams memory marketParams) external view returns (bytes32[] memory); + function timelock(bytes4 selector) external view returns (uint256); + function abdicated(bytes4 selector) external view returns (bool); + function executableAt(bytes memory data) external view returns (uint256); + + /* NON-VIEW FUNCTIONS */ + + function submit(bytes memory data) external; + function revoke(bytes memory data) external; + function increaseTimelock(bytes4 selector, uint256 newDuration) external; + function decreaseTimelock(bytes4 selector, uint256 newDuration) external; + function abdicate(bytes4 selector) external; + function setSkimRecipient(address newSkimRecipient) external; + function burnShares(bytes32 marketId) external; + function skim(address token) external; +} \ No newline at end of file diff --git a/packages/blue-sdk-viem/src/abis.ts b/packages/blue-sdk-viem/src/abis.ts index ef9b8e77..7c48b901 100644 --- a/packages/blue-sdk-viem/src/abis.ts +++ b/packages/blue-sdk-viem/src/abis.ts @@ -11982,7 +11982,7 @@ export const morphoMarketV1AdapterFactoryAbi = [ stateMutability: "view", type: "function", }, -]; +] as const; /** * @deprecated Use `morphoVaultV1AdapterFactoryAbi` instead. @@ -11993,3 +11993,560 @@ export const vaultV1AdapterFactoryAbi = morphoVaultV1AdapterFactoryAbi; * @deprecated Use `morphoVaultV1AdapterAbi` instead. */ export const vaultV1AdapterAbi = morphoVaultV1AdapterAbi; + +export const morphoMarketV1AdapterV2Abi = [ + { + inputs: [ + { internalType: "address", name: "_parentVault", type: "address" }, + { internalType: "address", name: "_morpho", type: "address" }, + { internalType: "address", name: "_adaptiveCurveIrm", type: "address" }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { inputs: [], name: "Abdicated", type: "error" }, + { inputs: [], name: "ApproveReturnedFalse", type: "error" }, + { inputs: [], name: "ApproveReverted", type: "error" }, + { inputs: [], name: "AutomaticallyTimelocked", type: "error" }, + { inputs: [], name: "DataAlreadyPending", type: "error" }, + { inputs: [], name: "DataNotTimelocked", type: "error" }, + { inputs: [], name: "IrmMismatch", type: "error" }, + { inputs: [], name: "LoanAssetMismatch", type: "error" }, + { inputs: [], name: "NoCode", type: "error" }, + { inputs: [], name: "SharePriceAboveOne", type: "error" }, + { inputs: [], name: "TimelockNotDecreasing", type: "error" }, + { inputs: [], name: "TimelockNotExpired", type: "error" }, + { inputs: [], name: "TimelockNotIncreasing", type: "error" }, + { inputs: [], name: "TransferReturnedFalse", type: "error" }, + { inputs: [], name: "TransferReverted", type: "error" }, + { inputs: [], name: "Unauthorized", type: "error" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + ], + name: "Abdicate", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + { indexed: false, internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "Accept", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "marketId", + type: "bytes32", + }, + { + indexed: false, + internalType: "uint256", + name: "newAllocation", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "mintedShares", + type: "uint256", + }, + ], + name: "Allocate", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "marketId", + type: "bytes32", + }, + { + indexed: false, + internalType: "uint256", + name: "supplyShares", + type: "uint256", + }, + ], + name: "BurnShares", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "marketId", + type: "bytes32", + }, + { + indexed: false, + internalType: "uint256", + name: "newAllocation", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "burnedShares", + type: "uint256", + }, + ], + name: "Deallocate", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + { + indexed: false, + internalType: "uint256", + name: "newDuration", + type: "uint256", + }, + ], + name: "DecreaseTimelock", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + { + indexed: false, + internalType: "uint256", + name: "newDuration", + type: "uint256", + }, + ], + name: "IncreaseTimelock", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: true, + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + { indexed: false, internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "Revoke", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "newSkimRecipient", + type: "address", + }, + ], + name: "SetSkimRecipient", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "token", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "assets", + type: "uint256", + }, + ], + name: "Skim", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + { indexed: false, internalType: "bytes", name: "data", type: "bytes" }, + { + indexed: false, + internalType: "uint256", + name: "executableAt", + type: "uint256", + }, + ], + name: "Submit", + type: "event", + }, + { + inputs: [{ internalType: "bytes4", name: "selector", type: "bytes4" }], + name: "abdicate", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes4", name: "selector", type: "bytes4" }], + name: "abdicated", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "adapterId", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "adaptiveCurveIrm", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes", name: "data", type: "bytes" }, + { internalType: "uint256", name: "assets", type: "uint256" }, + { internalType: "bytes4", name: "", type: "bytes4" }, + { internalType: "address", name: "", type: "address" }, + ], + name: "allocate", + outputs: [ + { internalType: "bytes32[]", name: "", type: "bytes32[]" }, + { internalType: "int256", name: "", type: "int256" }, + ], + 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: "allocation", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "asset", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "marketId", type: "bytes32" }], + name: "burnShares", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes", name: "data", type: "bytes" }, + { internalType: "uint256", name: "assets", type: "uint256" }, + { internalType: "bytes4", name: "", type: "bytes4" }, + { internalType: "address", name: "", type: "address" }, + ], + name: "deallocate", + outputs: [ + { internalType: "bytes32[]", name: "", type: "bytes32[]" }, + { internalType: "int256", name: "", type: "int256" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes4", name: "selector", type: "bytes4" }, + { internalType: "uint256", name: "newDuration", type: "uint256" }, + ], + name: "decreaseTimelock", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes", name: "data", type: "bytes" }], + name: "executableAt", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "marketId", type: "bytes32" }], + name: "expectedSupplyAssets", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "factory", + outputs: [{ internalType: "address", name: "", type: "address" }], + 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", + }, + ], + name: "ids", + outputs: [{ internalType: "bytes32[]", name: "", type: "bytes32[]" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes4", name: "selector", type: "bytes4" }, + { internalType: "uint256", name: "newDuration", type: "uint256" }, + ], + name: "increaseTimelock", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "", type: "uint256" }], + name: "marketIds", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "marketIdsLength", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "morpho", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "parentVault", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "realAssets", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes", name: "data", type: "bytes" }], + name: "revoke", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "newSkimRecipient", type: "address" }, + ], + name: "setSkimRecipient", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "token", type: "address" }], + name: "skim", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "skimRecipient", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes", name: "data", type: "bytes" }], + name: "submit", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "marketId", type: "bytes32" }], + name: "supplyShares", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes4", name: "selector", type: "bytes4" }], + name: "timelock", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, +] as const; + +export const morphoMarketV1AdapterV2FactoryAbi = [ + { + inputs: [ + { internalType: "address", name: "_morpho", type: "address" }, + { internalType: "address", name: "_adaptiveCurveIrm", type: "address" }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "parentVault", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "morphoMarketV1AdapterV2", + type: "address", + }, + ], + name: "CreateMorphoMarketV1AdapterV2", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "morpho", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "adaptiveCurveIrm", + type: "address", + }, + ], + name: "CreateMorphoMarketV1AdapterV2Factory", + type: "event", + }, + { + inputs: [], + name: "adaptiveCurveIrm", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "parentVault", type: "address" }], + name: "createMorphoMarketV1AdapterV2", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "isMorphoMarketV1AdapterV2", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "morpho", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "parentVault", type: "address" }], + name: "morphoMarketV1AdapterV2", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/packages/blue-sdk-viem/src/fetch/vault-v2/VaultV2Adapter.ts b/packages/blue-sdk-viem/src/fetch/vault-v2/VaultV2Adapter.ts index 538558f9..9708efa7 100644 --- a/packages/blue-sdk-viem/src/fetch/vault-v2/VaultV2Adapter.ts +++ b/packages/blue-sdk-viem/src/fetch/vault-v2/VaultV2Adapter.ts @@ -4,11 +4,18 @@ import { } from "@morpho-org/blue-sdk"; import type { Address, Client } from "viem"; import { getChainId, readContract } from "viem/actions"; -import { morphoVaultV1AdapterFactoryAbi } from "../../abis"; +import { + morphoMarketV1AdapterV2FactoryAbi, + morphoVaultV1AdapterFactoryAbi, +} from "../../abis"; import { morphoMarketV1AdapterFactoryAbi } from "../../abis"; import type { DeploylessFetchParameters } from "../../types"; import { fetchVaultV2MorphoMarketV1Adapter } from "./VaultV2MorphoMarketV1Adapter"; import { fetchAccrualVaultV2MorphoMarketV1Adapter } from "./VaultV2MorphoMarketV1Adapter"; +import { + fetchAccrualVaultV2MorphoMarketV1AdapterV2, + fetchVaultV2MorphoMarketV1AdapterV2, +} from "./VaultV2MorphoMarketV1AdapterV2"; import { fetchAccrualVaultV2MorphoVaultV1Adapter, fetchVaultV2MorphoVaultV1Adapter, @@ -22,10 +29,17 @@ export async function fetchVaultV2Adapter( parameters.chainId ??= await getChainId(client); parameters.deployless ??= true; - const { morphoVaultV1AdapterFactory, morphoMarketV1AdapterFactory } = - getChainAddresses(parameters.chainId); + const { + morphoVaultV1AdapterFactory, + morphoMarketV1AdapterFactory, + morphoMarketV1AdapterV2Factory, + } = getChainAddresses(parameters.chainId); - const [isMorphoVaultV1Adapter, isMorphoMarketV1Adapter] = await Promise.all([ + const [ + isMorphoVaultV1Adapter, + isMorphoMarketV1Adapter, + isMorphoMarketV1AdapterV2, + ] = await Promise.all([ morphoVaultV1AdapterFactory ? readContract(client, { ...parameters, @@ -34,6 +48,8 @@ export async function fetchVaultV2Adapter( functionName: "isMorphoVaultV1Adapter", args: [address], }) + // Factory may not have been deployed at requested block tag. + .catch(() => false) : false, morphoMarketV1AdapterFactory ? readContract(client, { @@ -43,6 +59,19 @@ export async function fetchVaultV2Adapter( functionName: "isMorphoMarketV1Adapter", args: [address], }) + // Factory may not have been deployed at requested block tag. + .catch(() => false) + : false, + morphoMarketV1AdapterV2Factory + ? readContract(client, { + ...parameters, + address: morphoMarketV1AdapterV2Factory, + abi: morphoMarketV1AdapterV2FactoryAbi, + functionName: "isMorphoMarketV1AdapterV2", + args: [address], + }) + // Factory may not have been deployed at requested block tag. + .catch(() => false) : false, ]); @@ -52,6 +81,9 @@ export async function fetchVaultV2Adapter( if (isMorphoMarketV1Adapter) return fetchVaultV2MorphoMarketV1Adapter(address, client, parameters); + if (isMorphoMarketV1AdapterV2) + return fetchVaultV2MorphoMarketV1AdapterV2(address, client, parameters); + throw new UnsupportedVaultV2AdapterError(address); } @@ -63,10 +95,17 @@ export async function fetchAccrualVaultV2Adapter( parameters.chainId ??= await getChainId(client); parameters.deployless ??= true; - const { morphoVaultV1AdapterFactory, morphoMarketV1AdapterFactory } = - getChainAddresses(parameters.chainId); + const { + morphoVaultV1AdapterFactory, + morphoMarketV1AdapterFactory, + morphoMarketV1AdapterV2Factory, + } = getChainAddresses(parameters.chainId); - const [isMorphoVaultV1Adapter, isMorphoMarketV1Adapter] = await Promise.all([ + const [ + isMorphoVaultV1Adapter, + isMorphoMarketV1Adapter, + isMorphoMarketV1AdapterV2, + ] = await Promise.all([ morphoVaultV1AdapterFactory ? readContract(client, { ...parameters, @@ -75,6 +114,8 @@ export async function fetchAccrualVaultV2Adapter( functionName: "isMorphoVaultV1Adapter", args: [address], }) + // Factory may not have been deployed at requested block tag. + .catch(() => false) : false, morphoMarketV1AdapterFactory ? readContract(client, { @@ -84,6 +125,19 @@ export async function fetchAccrualVaultV2Adapter( functionName: "isMorphoMarketV1Adapter", args: [address], }) + // Factory may not have been deployed at requested block tag. + .catch(() => false) + : false, + morphoMarketV1AdapterV2Factory + ? readContract(client, { + ...parameters, + address: morphoMarketV1AdapterV2Factory, + abi: morphoMarketV1AdapterV2FactoryAbi, + functionName: "isMorphoMarketV1AdapterV2", + args: [address], + }) + // Factory may not have been deployed at requested block tag. + .catch(() => false) : false, ]); @@ -97,5 +151,12 @@ export async function fetchAccrualVaultV2Adapter( parameters, ); + if (isMorphoMarketV1AdapterV2) + return fetchAccrualVaultV2MorphoMarketV1AdapterV2( + address, + client, + parameters, + ); + throw new UnsupportedVaultV2AdapterError(address); } diff --git a/packages/blue-sdk-viem/src/fetch/vault-v2/VaultV2MorphoMarketV1AdapterV2.ts b/packages/blue-sdk-viem/src/fetch/vault-v2/VaultV2MorphoMarketV1AdapterV2.ts new file mode 100644 index 00000000..51f2f9a0 --- /dev/null +++ b/packages/blue-sdk-viem/src/fetch/vault-v2/VaultV2MorphoMarketV1AdapterV2.ts @@ -0,0 +1,139 @@ +import { + AccrualVaultV2MorphoMarketV1AdapterV2, + type MarketId, + VaultV2MorphoMarketV1AdapterV2, +} from "@morpho-org/blue-sdk"; +import { fromEntries } from "@morpho-org/morpho-ts"; +import type { Address, Client } from "viem"; +import { getChainId, readContract } from "viem/actions"; +import { morphoMarketV1AdapterV2Abi } from "../../abis"; +import { + abi, + code, +} from "../../queries/vault-v2/GetVaultV2MorphoMarketV1AdapterV2"; +import type { DeploylessFetchParameters } from "../../types"; +import { fetchMarket } from "../Market"; + +export async function fetchVaultV2MorphoMarketV1AdapterV2( + address: Address, + client: Client, + { deployless = true, ...parameters }: DeploylessFetchParameters = {}, +) { + parameters.chainId ??= await getChainId(client); + + if (deployless) { + try { + const adapter = await readContract(client, { + ...parameters, + abi, + code, + functionName: "query", + args: [address], + }); + + return new VaultV2MorphoMarketV1AdapterV2({ + ...adapter, + marketIds: [ + ...adapter.marketSupplyShares.map( + ({ marketId }) => marketId as MarketId, + ), + ], + supplyShares: fromEntries( + adapter.marketSupplyShares.map(({ marketId, supplyShares }) => [ + marketId, + supplyShares, + ]), + ), + address, + }); + } catch { + // Fallback to multicall if deployless call fails. + } + } + + const [parentVault, skimRecipient, marketIdsLength, adaptiveCurveIrm] = + await Promise.all([ + readContract(client, { + ...parameters, + address, + abi: morphoMarketV1AdapterV2Abi, + functionName: "parentVault", + }), + readContract(client, { + ...parameters, + address, + abi: morphoMarketV1AdapterV2Abi, + functionName: "skimRecipient", + }), + readContract(client, { + ...parameters, + address, + abi: morphoMarketV1AdapterV2Abi, + functionName: "marketIdsLength", + }), + readContract(client, { + ...parameters, + address, + abi: morphoMarketV1AdapterV2Abi, + functionName: "adaptiveCurveIrm", + }), + ]); + + const marketIds = await Promise.all( + Array.from( + { length: Number(marketIdsLength) }, + (_, i) => + readContract(client, { + ...parameters, + address, + abi: morphoMarketV1AdapterV2Abi, + functionName: "marketIds", + args: [BigInt(i)], + }) as Promise, + ), + ); + + const supplyShares = await Promise.all( + marketIds.map( + async (marketId) => + [ + marketId, + await readContract(client, { + ...parameters, + address, + abi: morphoMarketV1AdapterV2Abi, + functionName: "supplyShares", + args: [marketId], + }), + ] as const, + ), + ).then(fromEntries); + + return new VaultV2MorphoMarketV1AdapterV2({ + parentVault, + skimRecipient, + address, + marketIds, + adaptiveCurveIrm, + supplyShares, + }); +} + +export async function fetchAccrualVaultV2MorphoMarketV1AdapterV2( + address: Address, + client: Client, + parameters: DeploylessFetchParameters = {}, +) { + const adapter = await fetchVaultV2MorphoMarketV1AdapterV2( + address, + client, + parameters, + ); + const markets = await Promise.all( + adapter.marketIds.map((marketId) => + fetchMarket(marketId, client, parameters), + ), + ); + + return new AccrualVaultV2MorphoMarketV1AdapterV2(adapter, markets); +} diff --git a/packages/blue-sdk-viem/src/fetch/vault-v2/index.ts b/packages/blue-sdk-viem/src/fetch/vault-v2/index.ts index cc1d7904..599b1fe5 100644 --- a/packages/blue-sdk-viem/src/fetch/vault-v2/index.ts +++ b/packages/blue-sdk-viem/src/fetch/vault-v2/index.ts @@ -1,3 +1,5 @@ export * from "./VaultV2"; export * from "./VaultV2MorphoVaultV1Adapter"; export * from "./VaultV2Adapter"; +export * from "./VaultV2MorphoMarketV1AdapterV2"; +export * from "./VaultV2MorphoMarketV1Adapter"; diff --git a/packages/blue-sdk-viem/src/queries/vault-v2/GetVaultV2MorphoMarketV1AdapterV2.ts b/packages/blue-sdk-viem/src/queries/vault-v2/GetVaultV2MorphoMarketV1AdapterV2.ts new file mode 100644 index 00000000..0e72beb5 --- /dev/null +++ b/packages/blue-sdk-viem/src/queries/vault-v2/GetVaultV2MorphoMarketV1AdapterV2.ts @@ -0,0 +1,58 @@ +export const abi = [ + { + inputs: [ + { + internalType: "contract IMorphoMarketV1AdapterV2", + name: "adapter", + type: "address", + }, + ], + name: "query", + outputs: [ + { + components: [ + { + internalType: "address", + name: "parentVault", + type: "address", + }, + { + internalType: "address", + name: "skimRecipient", + type: "address", + }, + { + internalType: "address", + name: "adaptiveCurveIrm", + type: "address", + }, + { + components: [ + { + internalType: "bytes32", + name: "marketId", + type: "bytes32", + }, + { + internalType: "uint256", + name: "supplyShares", + type: "uint256", + }, + ], + internalType: "struct MarketSupplyShares[]", + name: "marketSupplyShares", + type: "tuple[]", + }, + ], + internalType: "struct VaultV2MorphoMarketV1AdapterV2Response", + name: "res", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; + +export const code = + "0x608080604052346015576104bb908161001a8239f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63d4fc9fc614610025575f80fd5b346102be5760203660031901126102be576004356001600160a01b03811691908290036102be576080810181811067ffffffffffffffff8211176103d4576040525f815260208101915f8352604082015f81526060830191606083526040516307f1b29b60e11b8152602081600481855afa9081156102ca575f916103b5575b506001600160a01b0316845260405163388af5b560e01b8152602081600481855afa9081156102ca575f91610396575b506001600160a01b03168552604051630399e3a560e41b8152602081600481855afa9081156102ca575f91610367575b506001600160a01b0316825260405163ace48b4560e01b8152602081600481855afa9081156102ca575f91610335575b50610144819695949396610445565b6101516040519182610404565b818152601f1961016083610445565b015f5b81811061030857505083525f5b8181106101fa57505060408051602080825294516001600160a01b039081168683015295518616918101919091529451909316606085015251608080850152805160a0850181905284935060c084019291909101905f5b8181106101d5575050500390f35b82518051855260209081015181860152869550604090940193909201916001016101c7565b60409693949596519063779a968360e01b8252806004830152602082602481875afa9182156102ca575f926102d5575b50604051630dd5aa9b60e31b81526004810183905291602083602481885afa9283156102ca575f93610293575b5081610287916001946040519161026d836103e8565b82526020820152885190610281838361045d565b5261045d565b50019594939295610170565b92506020833d82116102c2575b816102ad60209383610404565b810103126102be5791519181610257565b5f80fd5b3d91506102a0565b6040513d5f823e3d90fd5b9091506020813d8211610300575b816102f060209383610404565b810103126102be5751905f61022a565b3d91506102e3565b602090604099969798995161031c816103e8565b5f81525f83820152828286010152019796959497610163565b90506020813d60201161035f575b8161035060209383610404565b810103126102be57515f610135565b3d9150610343565b610389915060203d60201161038f575b6103818183610404565b810190610426565b5f610105565b503d610377565b6103af915060203d60201161038f576103818183610404565b5f6100d5565b6103ce915060203d60201161038f576103818183610404565b5f6100a5565b634e487b7160e01b5f52604160045260245ffd5b6040810190811067ffffffffffffffff8211176103d457604052565b90601f8019910116810190811067ffffffffffffffff8211176103d457604052565b908160209103126102be57516001600160a01b03811681036102be5790565b67ffffffffffffffff81116103d45760051b60200190565b80518210156104715760209160051b010190565b634e487b7160e01b5f52603260045260245ffdfea26469706673582212201631f5626073bb74cf2ff59c21445d1e923ad78225ae358bbea9739c8f886f0464736f6c634300081b0033"; diff --git a/packages/blue-sdk-viem/src/queries/vault-v2/index.ts b/packages/blue-sdk-viem/src/queries/vault-v2/index.ts index 9c9ad78f..2db6a902 100644 --- a/packages/blue-sdk-viem/src/queries/vault-v2/index.ts +++ b/packages/blue-sdk-viem/src/queries/vault-v2/index.ts @@ -1,2 +1,4 @@ export * as GetVaultV2 from "./GetVaultV2"; export * as GetVaultV2MorphoVaultV1Adapter from "./GetVaultV2MorphoVaultV1Adapter"; +export * as GetVaultV2MorphoMarketV1AdapterV2 from "./GetVaultV2MorphoMarketV1AdapterV2"; +export * as GetVaultV2MorphoMarketV1Adapter from "./GetVaultV2MorphoMarketV1Adapter"; diff --git a/packages/blue-sdk-viem/test/VaultV2.test.ts b/packages/blue-sdk-viem/test/VaultV2.test.ts index cb8a9dc1..e4a6e156 100644 --- a/packages/blue-sdk-viem/test/VaultV2.test.ts +++ b/packages/blue-sdk-viem/test/VaultV2.test.ts @@ -103,7 +103,7 @@ describe("AccrualVaultV2", () => { address: vaultV2Address, asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", decimals: 18, - lastUpdate: 1758881557n, + lastUpdate: 1763994035n, liquidityAdapter: "0x2C32fF5E1d976015AdbeA8cC73c7Da3A6677C25F", liquidityData: "0x", liquidityAllocations: [ @@ -113,7 +113,7 @@ describe("AccrualVaultV2", () => { ), absoluteCap: 1000000000000n, relativeCap: 1000000000000000000n, - allocation: 16980381n, + allocation: 16624313n, }, ], managementFee: 0n, @@ -123,9 +123,9 @@ describe("AccrualVaultV2", () => { performanceFee: 0n, performanceFeeRecipient: zeroAddress, symbol: "tvUSDC", - totalAssets: 16963835n, - _totalAssets: 16963835n, - totalSupply: 16963835000000000000n, + totalAssets: 16474000n, + _totalAssets: 16474000n, + totalSupply: 16474000000000000000n, virtualShares: 1000000000000n, }); @@ -142,7 +142,7 @@ describe("AccrualVaultV2", () => { address: vaultV2Address, asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", decimals: 18, - lastUpdate: 1758881557n, + lastUpdate: 1763994035n, liquidityAdapter: "0x2C32fF5E1d976015AdbeA8cC73c7Da3A6677C25F", liquidityData: "0x", liquidityAllocations: [ @@ -152,7 +152,7 @@ describe("AccrualVaultV2", () => { ), absoluteCap: 1000000000000n, relativeCap: 1000000000000000000n, - allocation: 16980381n, + allocation: 16624313n, }, ], managementFee: 0n, @@ -162,9 +162,9 @@ describe("AccrualVaultV2", () => { performanceFee: 0n, performanceFeeRecipient: zeroAddress, symbol: "tvUSDC", - totalAssets: 16963835n, - _totalAssets: 16963835n, - totalSupply: 16963835000000000000n, + totalAssets: 16474000n, + _totalAssets: 16474000n, + totalSupply: 16474000000000000000n, virtualShares: 1000000000000n, }); diff --git a/packages/blue-sdk-viem/test/VaultV2Adapter.test.ts b/packages/blue-sdk-viem/test/VaultV2Adapter.test.ts index 3985343b..7a8857fb 100644 --- a/packages/blue-sdk-viem/test/VaultV2Adapter.test.ts +++ b/packages/blue-sdk-viem/test/VaultV2Adapter.test.ts @@ -1,10 +1,14 @@ import { AccrualVaultV2, CapacityLimitReason, + MarketParams, MathLib, VaultV2MorphoMarketV1Adapter, + VaultV2MorphoMarketV1AdapterV2, VaultV2MorphoVaultV1Adapter, + addressesRegistry, } from "@morpho-org/blue-sdk"; +import type { AnvilTestClient } from "@morpho-org/test"; import { encodeAbiParameters, encodeFunctionData, @@ -16,6 +20,7 @@ import { readContract } from "viem/actions"; import { describe, expect } from "vitest"; import { fetchAccrualVaultV2, fetchVaultV2Adapter, vaultV2Abi } from "../src"; import { vaultV2Test } from "./setup"; +import { deployMorphoMarketV1Adapter, deployVaultV2 } from "./utils"; // VaultV2 with liquidity adapter vaultV1 const vaultV2Address = "0xfDE48B9B8568189f629Bc5209bf5FA826336557a"; @@ -31,16 +36,12 @@ const expectedDataVaultV1Adapter = new VaultV2MorphoVaultV1Adapter({ skimRecipient: zeroAddress, }); -// VaultV2 with liquidity adapter marketV1 -const vaultV2Address2 = "0x678b8851DFcA08E40F3e31C8ABd08dE3E8E14b64"; -const vaultV2AdapterMarketV1Address = - "0x83831b31f225B3DD0e96C69D683606bE399Dc757"; - -const expectedDataMarketV1Adapter = new VaultV2MorphoMarketV1Adapter({ - address: vaultV2AdapterMarketV1Address, - parentVault: vaultV2Address2, - skimRecipient: zeroAddress, - marketParamsList: [], +const marketParams = new MarketParams({ + collateralToken: "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", + irm: "0x46415998764C29aB2a25CbeA6254146D50D22687", + lltv: 860000000000000000n, + loanToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + oracle: "0x663BECd10daE6C4A3Dcd89F1d76c1174199639B9", }); describe("VaultV2Adapter", () => { @@ -72,27 +73,123 @@ describe("VaultV2Adapter", () => { describe("should fetch marketV1 adapter", () => { vaultV2Test("with deployless reads", async ({ client }) => { - const value = await fetchVaultV2Adapter( - vaultV2AdapterMarketV1Address, - client, + const { usdc } = addressesRegistry[client.chain.id]; + + const vaultAddress = await deployVaultV2(client as AnvilTestClient, usdc); + const { address: adapterAddress } = await deployMorphoMarketV1Adapter( + client as AnvilTestClient, + vaultAddress, + "1", { - deployless: true, + marketParams, + deposit: parseUnits("1000", 6), }, ); - expect(value).toStrictEqual(expectedDataMarketV1Adapter); + const value = await fetchVaultV2Adapter(adapterAddress, client, { + deployless: "force", + }); + expect(value).toStrictEqual( + new VaultV2MorphoMarketV1Adapter({ + address: adapterAddress, + parentVault: vaultAddress, + skimRecipient: zeroAddress, + marketParamsList: [marketParams], + }), + ); }); vaultV2Test("with multicall", async ({ client }) => { - const value = await fetchVaultV2Adapter( - vaultV2AdapterMarketV1Address, - client, + const { usdc } = addressesRegistry[client.chain.id]; + + const vaultAddress = await deployVaultV2(client as AnvilTestClient, usdc); + const { address: adapterAddress } = await deployMorphoMarketV1Adapter( + client as AnvilTestClient, + vaultAddress, + "1", { - deployless: false, + marketParams, + deposit: parseUnits("1000", 6), }, ); - expect(value).toStrictEqual(expectedDataMarketV1Adapter); + const value = await fetchVaultV2Adapter(adapterAddress, client, { + deployless: "force", + }); + expect(value).toStrictEqual( + new VaultV2MorphoMarketV1Adapter({ + address: adapterAddress, + parentVault: vaultAddress, + skimRecipient: zeroAddress, + marketParamsList: [marketParams], + }), + ); + }); + }); + + describe("should fetch marketV1 adapter V2", () => { + vaultV2Test("with deployless reads", async ({ client }) => { + const { usdc } = addressesRegistry[client.chain.id]; + + const vaultAddress = await deployVaultV2(client as AnvilTestClient, usdc); + const { address: adapterAddress, supplyShares } = + await deployMorphoMarketV1Adapter( + client as AnvilTestClient, + vaultAddress, + "2", + { + marketParams, + deposit: parseUnits("1000", 6), + }, + ); + + const value = await fetchVaultV2Adapter(adapterAddress, client, { + deployless: "force", + }); + expect(value).toStrictEqual( + new VaultV2MorphoMarketV1AdapterV2({ + address: adapterAddress, + parentVault: vaultAddress, + skimRecipient: zeroAddress, + marketIds: [marketParams.id], + adaptiveCurveIrm: marketParams.irm, + supplyShares: { + [marketParams.id]: supplyShares, + }, + }), + ); + }); + + vaultV2Test("with multicall", async ({ client }) => { + const { usdc } = addressesRegistry[client.chain.id]; + + const vaultAddress = await deployVaultV2(client as AnvilTestClient, usdc); + const { address: adapterAddress, supplyShares } = + await deployMorphoMarketV1Adapter( + client as AnvilTestClient, + vaultAddress, + "2", + { + marketParams, + deposit: parseUnits("1000", 6), + }, + ); + + const value = await fetchVaultV2Adapter(adapterAddress, client, { + deployless: false, + }); + expect(value).toStrictEqual( + new VaultV2MorphoMarketV1AdapterV2({ + address: adapterAddress, + parentVault: vaultAddress, + skimRecipient: zeroAddress, + marketIds: [marketParams.id], + adaptiveCurveIrm: marketParams.irm, + supplyShares: { + [marketParams.id]: supplyShares, + }, + }), + ); }); }); }); @@ -204,7 +301,7 @@ describe("LiquidityAdapter vaultV1", () => { const result = accrualVaultV2.maxDeposit(MathLib.MAX_UINT_256); expect(result).toStrictEqual({ - value: 1000301472035887388n, + value: 1000725557277232788n, limiter: CapacityLimitReason.cap, }); }); @@ -223,7 +320,7 @@ describe("LiquidityAdapter vaultV1", () => { const result = accrualVaultV2.maxWithdraw(shares); expect(result).toStrictEqual({ - value: 17023088n, + value: 16667544n, limiter: CapacityLimitReason.liquidity, }); }, @@ -252,40 +349,10 @@ describe("LiquidityAdapter vaultV1", () => { describe("LiquidityAdapter marketV1", () => { describe("maxDeposit function", () => { vaultV2Test("should be limited by balance", async ({ client }) => { - await client.writeContract({ - account: "0x707D44b65BA91C42f212e8bB61f71cc69fBf8fd7", - address: vaultV2Address2, - abi: vaultV2Abi, - functionName: "setCurator", - args: [curator], - }); - const data = encodeFunctionData({ - abi: vaultV2Abi, - functionName: "setIsAllocator", - args: [allocator, true], - }); - await client.writeContract({ - account: curator, - address: vaultV2Address2, - abi: vaultV2Abi, - functionName: "submit", - args: [data], - }); - await client.writeContract({ - account: "0x707D44b65BA91C42f212e8bB61f71cc69fBf8fd7", - address: vaultV2Address2, - abi: vaultV2Abi, - functionName: "setIsAllocator", - args: [allocator, true], - }); - await client.writeContract({ - account: allocator, - address: vaultV2Address2, - abi: vaultV2Abi, - functionName: "setLiquidityAdapterAndData", - args: [vaultV2AdapterMarketV1Address, "0x"], - }); - const accrualVaultV2 = await fetchAccrualVaultV2(vaultV2Address, client); + const { usdc } = addressesRegistry[client.chain.id]; + const vaultAddress = await deployVaultV2(client as AnvilTestClient, usdc); + + const accrualVaultV2 = await fetchAccrualVaultV2(vaultAddress, client); const assets = parseUnits("100000", 6); // 100K assets const result = accrualVaultV2.maxDeposit(assets); @@ -298,40 +365,10 @@ describe("LiquidityAdapter marketV1", () => { describe("maxWithdraw function", () => { vaultV2Test("should be limited by balance", async ({ client }) => { - await client.writeContract({ - account: "0x707D44b65BA91C42f212e8bB61f71cc69fBf8fd7", - address: vaultV2Address2, - abi: vaultV2Abi, - functionName: "setCurator", - args: [curator], - }); - const data = encodeFunctionData({ - abi: vaultV2Abi, - functionName: "setIsAllocator", - args: [allocator, true], - }); - await client.writeContract({ - account: curator, - address: vaultV2Address2, - abi: vaultV2Abi, - functionName: "submit", - args: [data], - }); - await client.writeContract({ - account: "0x707D44b65BA91C42f212e8bB61f71cc69fBf8fd7", - address: vaultV2Address2, - abi: vaultV2Abi, - functionName: "setIsAllocator", - args: [allocator, true], - }); - await client.writeContract({ - account: allocator, - address: vaultV2Address2, - abi: vaultV2Abi, - functionName: "setLiquidityAdapterAndData", - args: [vaultV2AdapterMarketV1Address, "0x"], - }); - const accrualVaultV2 = await fetchAccrualVaultV2(vaultV2Address, client); + const { usdc } = addressesRegistry[client.chain.id]; + const vaultAddress = await deployVaultV2(client as AnvilTestClient, usdc); + + const accrualVaultV2 = await fetchAccrualVaultV2(vaultAddress, client); const shares = parseUnits("100000", 6); // 100K shares const result = accrualVaultV2.maxWithdraw(shares); diff --git a/packages/blue-sdk-viem/test/setup.ts b/packages/blue-sdk-viem/test/setup.ts index 3e229d85..1c333570 100644 --- a/packages/blue-sdk-viem/test/setup.ts +++ b/packages/blue-sdk-viem/test/setup.ts @@ -27,5 +27,5 @@ export const preLiquidationTest = createViemTest(mainnet, { export const vaultV2Test = createViemTest(base, { forkUrl: process.env.BASE_RPC_URL, - forkBlockNumber: 36_870_000, + forkBlockNumber: 39_586_444, }); diff --git a/packages/blue-sdk-viem/test/utils.ts b/packages/blue-sdk-viem/test/utils.ts new file mode 100644 index 00000000..085aa14a --- /dev/null +++ b/packages/blue-sdk-viem/test/utils.ts @@ -0,0 +1,275 @@ +import { + type MarketParams, + MathLib, + getChainAddresses, + marketParamsAbi, +} from "@morpho-org/blue-sdk"; +import { isDefined } from "@morpho-org/morpho-ts"; +import type { AnvilTestClient } from "@morpho-org/test"; +import { + type Abi, + type Address, + type ContractFunctionName, + type EncodeFunctionDataParameters, + decodeEventLog, + encodeAbiParameters, + encodeFunctionData, + maxUint128, + parseEther, + zeroHash, +} from "viem"; +import { + blueAbi, + morphoMarketV1AdapterFactoryAbi, + morphoMarketV1AdapterV2FactoryAbi, + readContractRestructured, + vaultV2Abi, + vaultV2FactoryAbi, +} from "../src"; + +export const submitAndAccept = async < + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName | undefined = undefined, +>( + client: AnvilTestClient, + { + address, + ...data + }: EncodeFunctionDataParameters & { address: Address }, +) => { + // @ts-expect-error - safe typing + const encoded = encodeFunctionData(data); + await client.writeContract({ + address, + abi: vaultV2Abi, + functionName: "submit", + args: [encoded], + }); + const txHash = await client.sendTransaction({ + to: address, + data: encoded, + }); + await client.waitForTransactionReceipt({ hash: txHash }); +}; + +export const deployVaultV2 = async ( + client: AnvilTestClient, + loanToken: Address, +) => { + const { vaultV2Factory } = getChainAddresses(client.chain.id); + + await client.deal({ amount: parseEther("1") }); + + const vaultV2TxHash = await client.writeContract({ + address: vaultV2Factory!, + abi: vaultV2FactoryAbi, + functionName: "createVaultV2", + args: [client.account.address, loanToken, zeroHash], + }); + + const vaultV2TxReceipt = await client.waitForTransactionReceipt({ + hash: vaultV2TxHash, + }); + + const vaultAddress = vaultV2TxReceipt.logs + .map((log) => { + try { + return decodeEventLog({ + abi: vaultV2FactoryAbi.filter( + (abi) => abi.type === "event" && abi.name === "CreateVaultV2", + ), + data: log.data, + topics: log.topics, + }); + } catch { + return null; + } + }) + .filter(isDefined)[0]?.args.newVaultV2; + + if (vaultAddress == null) throw new Error("No CreateVaultV2 event found."); + + await client.writeContract({ + address: vaultAddress, + abi: vaultV2Abi, + functionName: "setCurator", + args: [client.account.address], + }); + await submitAndAccept(client, { + address: vaultAddress, + abi: vaultV2Abi, + functionName: "setIsAllocator", + args: [client.account.address, true], + }); + + return vaultAddress; +}; + +export async function deployMorphoMarketV1Adapter( + client: AnvilTestClient, + vaultAddress: Address, + version: "1" | "2", +): Promise<{ address: Address }>; +export async function deployMorphoMarketV1Adapter( + client: AnvilTestClient, + vaultAddress: Address, + version: "1" | "2", + initialSetup: { marketParams: MarketParams; deposit: bigint }, +): Promise<{ address: Address; supplyShares: bigint }>; +export async function deployMorphoMarketV1Adapter( + client: AnvilTestClient, + vaultAddress: Address, + version: "1" | "2", + initialSetup?: { marketParams: MarketParams; deposit: bigint }, +): Promise<{ address: Address; supplyShares?: bigint }> { + const { + morphoMarketV1AdapterV2Factory, + morphoMarketV1AdapterFactory, + morpho, + } = getChainAddresses(client.chain.id); + + const txHash = await (version === "2" + ? client.writeContract({ + address: morphoMarketV1AdapterV2Factory!, + abi: morphoMarketV1AdapterV2FactoryAbi, + functionName: "createMorphoMarketV1AdapterV2", + args: [vaultAddress], + }) + : client.writeContract({ + address: morphoMarketV1AdapterFactory!, + abi: morphoMarketV1AdapterFactoryAbi, + functionName: "createMorphoMarketV1Adapter", + args: [vaultAddress, morpho], + })); + + const receipt = await client.waitForTransactionReceipt({ hash: txHash }); + + const adapterAddress = + version === "2" + ? receipt.logs + .map((log) => { + try { + return decodeEventLog({ + abi: morphoMarketV1AdapterV2FactoryAbi.filter( + (abi) => + abi.type === "event" && + abi.name === "CreateMorphoMarketV1AdapterV2", + ), + data: log.data, + topics: log.topics, + }); + } catch { + return null; + } + }) + .filter(isDefined)[0]?.args.morphoMarketV1AdapterV2 + : receipt.logs + .map((log) => { + try { + return decodeEventLog({ + abi: morphoMarketV1AdapterFactoryAbi.filter( + (abi) => + abi.type === "event" && + abi.name === "CreateMorphoMarketV1Adapter", + ), + data: log.data, + topics: log.topics, + }); + } catch { + return null; + } + }) + .filter(isDefined)[0]?.args.morphoMarketV1Adapter; + + if (adapterAddress == null) + throw new Error("No CreateMorphoMarketV1Adapter(V2) event found."); + + await submitAndAccept(client, { + address: vaultAddress, + abi: vaultV2Abi, + functionName: "addAdapter", + args: [adapterAddress], + }); + + if (!initialSetup) return { address: adapterAddress }; + + const { marketParams, deposit } = initialSetup; + + await client.writeContract({ + address: vaultAddress, + abi: vaultV2Abi, + functionName: "setLiquidityAdapterAndData", + args: [ + adapterAddress, + encodeAbiParameters( + [ + { + type: "tuple", + components: [ + { type: "address", name: "loanToken" }, + { type: "address", name: "collateralToken" }, + { type: "address", name: "oracle" }, + { type: "address", name: "irm" }, + { type: "uint256", name: "lltv" }, + ], + }, + ], + [marketParams], + ), + ], + }); + + const ids = [ + encodeAbiParameters( + [{ type: "string" }, { type: "address" }], + ["this", adapterAddress], + ), + encodeAbiParameters( + [{ type: "string" }, { type: "address" }], + ["collateralToken", marketParams.collateralToken], + ), + encodeAbiParameters( + [{ type: "string" }, { type: "address" }, marketParamsAbi], + ["this/marketParams", adapterAddress, marketParams], + ), + ]; + + for (const id of ids) { + await submitAndAccept(client, { + address: vaultAddress, + abi: vaultV2Abi, + functionName: "increaseAbsoluteCap", + args: [id, maxUint128], + }); + await submitAndAccept(client, { + address: vaultAddress, + abi: vaultV2Abi, + functionName: "increaseRelativeCap", + args: [id, MathLib.WAD], + }); + } + + await client.deal({ + erc20: marketParams.loanToken, + amount: deposit, + }); + await client.approve({ + address: marketParams.loanToken, + args: [vaultAddress, deposit], + }); + await client.writeContract({ + address: vaultAddress, + abi: vaultV2Abi, + functionName: "deposit", + args: [deposit, client.account.address], + }); + + const { supplyShares } = await readContractRestructured(client, { + address: morpho, + abi: blueAbi, + functionName: "position", + args: [marketParams.id, adapterAddress], + }); + + return { address: adapterAddress, supplyShares }; +} diff --git a/packages/blue-sdk/src/addresses.ts b/packages/blue-sdk/src/addresses.ts index fe9b6b17..baff32ac 100644 --- a/packages/blue-sdk/src/addresses.ts +++ b/packages/blue-sdk/src/addresses.ts @@ -37,6 +37,7 @@ export interface ChainAddresses { metaMorphoFactory?: Address; vaultV2Factory?: Address; morphoMarketV1AdapterFactory?: Address; + morphoMarketV1AdapterV2Factory?: Address; morphoVaultV1AdapterFactory?: Address; registryList?: Address; chainlinkOracleFactory?: Address; @@ -79,6 +80,8 @@ const _addressesRegistry = { metaMorphoFactory: "0x1897A8997241C1cD4bD0698647e4EB7213535c24", vaultV2Factory: "0xA1D94F746dEfa1928926b84fB2596c06926C0405", morphoMarketV1AdapterFactory: "0xb049465969ac6355127cDf9E88deE63d25204d5D", + morphoMarketV1AdapterV2Factory: + "0x32BB1c0D48D8b1B3363e86eeB9A0300BAd61ccc1", morphoVaultV1AdapterFactory: "0xD1B8E2dee25c2b89DCD2f98448a7ce87d6F63394", registryList: "0x3696c5eAe4a7Ffd04Ea163564571E9CD8Ed9364e", chainlinkOracleFactory: "0x3A7bB36Ee3f3eE32A60e9f2b33c1e5f2E83ad766", @@ -138,6 +141,8 @@ const _addressesRegistry = { metaMorphoFactory: "0xFf62A7c278C62eD665133147129245053Bbf5918", vaultV2Factory: "0x4501125508079A99ebBebCE205DeC9593C2b5857", morphoMarketV1AdapterFactory: "0x133baC94306B99f6dAD85c381a5be851d8DD717c", + morphoMarketV1AdapterV2Factory: + "0x9a1B378C43BA535cDB89934230F0D3890c51C0EB", morphoVaultV1AdapterFactory: "0xF42D9c36b34c9c2CF3Bc30eD2a52a90eEB604642", registryList: "0x5C2531Cbd2cf112Cf687da3Cd536708aDd7DB10a", chainlinkOracleFactory: "0x2DC205F24BCb6B311E5cdf0745B0741648Aebd3d", @@ -165,6 +170,8 @@ const _addressesRegistry = { metaMorphoFactory: "0xa9c87daB340631C34BB738625C70499e29ddDC98", vaultV2Factory: "0xC11a53eE9B1eCc7a068D8e40F8F17926584F97Cf", morphoMarketV1AdapterFactory: "0xD1A0C86F28ecD1657Ad06415c2B230cC89D9b6dd", + morphoMarketV1AdapterV2Factory: + "0xc0006f52B38625C283dd2f972dD9B779A5851Dd0", morphoVaultV1AdapterFactory: "0xEb174FEA51Da241eB3B516959B216e013de2888a", registryList: "0xb70a43821d2707fA9d0EDd9511CC499F468Ba564", chainlinkOracleFactory: "0x1ff7895Eb842794c5d07C4c547b6730e61295215", @@ -189,6 +196,8 @@ const _addressesRegistry = { metaMorphoFactory: "0x878988f5f561081deEa117717052164ea1Ef0c82", vaultV2Factory: "0x6b46fa3cc9EBF8aB230aBAc664E37F2966Bf7971", morphoMarketV1AdapterFactory: "0x96456Bf888D4de607Bf3ca0b3C8e4DF9b0d0Ad47", + morphoMarketV1AdapterV2Factory: + "0xeF84b1ecEbe43283ec5AF95D7a5c4D7dE0a9859b", morphoVaultV1AdapterFactory: "0xD8Fc8a85779551e78B516da9f74061cb3b086793", registryList: "0xc00eb3c7aD1aE986A7f05F5A9d71aCa39c763C65", chainlinkOracleFactory: "0x98Ce5D183DC0c176f54D37162F87e7eD7f2E41b5", @@ -211,6 +220,8 @@ const _addressesRegistry = { metaMorphoFactory: "0x3Bb6A6A0Bc85b367EFE0A5bAc81c5E52C892839a", vaultV2Factory: "0x6128b680b277Bf4Df80DFE9D8c55A498660870ef", morphoMarketV1AdapterFactory: "0x65956d5Ba4974983ecCe111612FC0A0c22650A11", + morphoMarketV1AdapterV2Factory: + "0x71B299bDb52b6396429cd1E11c418324502CB434", morphoVaultV1AdapterFactory: "0xEe9F7C64dD827ED7b5CAA2272936366FAca00CF3", registryList: "0xD1346be260cd22Eab9E6163010b0D5CbfAAAD32b", chainlinkOracleFactory: "0x1ec408D4131686f727F3Fd6245CF85Bc5c9DAD70", @@ -231,6 +242,8 @@ const _addressesRegistry = { metaMorphoFactory: "0x4DBB3a642a2146d5413750Cca3647086D9ba5F12", vaultV2Factory: "0x6846EA318B6B987Ee6b28eBFd87c3409F1d13108", morphoMarketV1AdapterFactory: "0xAf93F2d8508053432659d509b0210fdF1472493D", + morphoMarketV1AdapterV2Factory: + "0xEd0b06fcdDB6dD0985e2de9D22ad034d313b7dBd", morphoVaultV1AdapterFactory: "0xbF7DEa3756668C7E396C655D646C039826ba8416", registryList: "0x06A47994B4890dcA28C076969cedE1151d86EFCF", chainlinkOracleFactory: "0xd706690BA1Fe26b70c4AD89e60ff62cEB3A2eD02", @@ -298,6 +311,8 @@ const _addressesRegistry = { metaMorphoFactory: "0xe9EdE3929F43a7062a007C3e8652e4ACa610Bdc0", vaultV2Factory: "0xC9b34c108014B44e5a189A830e7e04c56704a0c9", morphoMarketV1AdapterFactory: "0x117b92Ab1C025B175ED38a0CDe5A067a745224a0", + morphoMarketV1AdapterV2Factory: + "0x9a13bdA35F98811fbAcf097966b2C838f3F9c58C", morphoVaultV1AdapterFactory: "0xf1Ab9e885C0faa0cbCEd407498BBA895537aD754", registryList: "0xB9130D2A87d7c60ED7E7e4b25bdA6e3E6841becB", chainlinkOracleFactory: "0x43269546e1D586a1f7200a0AC07e26f9631f7539", @@ -378,6 +393,8 @@ const _addressesRegistry = { metaMorphoFactory: "0x2525D453D9BA13921D5aB5D8c12F9202b0e19456", vaultV2Factory: "0x4f0a370bb367843CFd914c4d9972523aD2f8FCc9", morphoMarketV1AdapterFactory: "0x1675357fdA9e6784DdAD7AD5b3C3DF1fdD4dc4C9", + morphoMarketV1AdapterV2Factory: + "0xB7c243AfACb25870775ADFdAe9D0EAc2324dD152", morphoVaultV1AdapterFactory: "0x5935fFcD1C5D269840ae7c685bC957A73E04AEDB", registryList: "0x60d3184BDD31BAE7De973894B3bA0b3B6900B79a", chainlinkOracleFactory: "0x133F742c0D36864F37e15C33a18bA6fdc950ED0f", @@ -411,6 +428,8 @@ const _addressesRegistry = { metaMorphoFactory: "0x1c8De6889acee12257899BFeAa2b7e534de32E16", vaultV2Factory: "0xFcb8b57E56787bB29e130Fca67f3c5a1232975D1", morphoMarketV1AdapterFactory: "0x2e6BE3a3A27fb45c6AbA2D1833eeA48E8788538e", + morphoMarketV1AdapterV2Factory: + "0x6d6A3ba62836d6B40277767dCAc8fd390d4BcedC", morphoVaultV1AdapterFactory: "0xc8D22B1adD3D176600E9952e7876e9249254cAAF", registryList: "0xA9132a09838fD20304dF2B2892679d06A4cc6371", chainlinkOracleFactory: "0x7D047fB910Bc187C18C81a69E30Fa164f8c536eC", @@ -473,6 +492,8 @@ const _addressesRegistry = { metaMorphoFactory: "0xec051b19d654C48c357dC974376DeB6272f24e53", vaultV2Factory: "0xD7217E5687FF1071356C780b5fe4803D9D967da7", morphoMarketV1AdapterFactory: "0xc6b8B565C715134b0Ca3D6fa3D29B25759D0b9e2", + morphoMarketV1AdapterV2Factory: + "0xaEff6Ef4B7bbfbAadB18b634A8F11392CBeB72Be", morphoVaultV1AdapterFactory: "0xdf5202e29654e02011611A086f15477880580CAc", registryList: "0x857B55cEb57dA0C2A83EE08a8dB529B931089aee", chainlinkOracleFactory: "0xeb476f124FaD625178759d13557A72394A6f9aF5", @@ -613,6 +634,7 @@ const _deployments = { metaMorphoFactory: 21439510n, vaultV2Factory: 23375073n, morphoMarketV1AdapterFactory: 23375073n, + morphoMarketV1AdapterV2Factory: 23981459n, morphoVaultV1AdapterFactory: 23375073n, registryList: 23375119n, chainlinkOracleFactory: 19375066n, @@ -634,6 +656,7 @@ const _deployments = { metaMorphoFactory: 23928808n, vaultV2Factory: 35615206n, morphoMarketV1AdapterFactory: 35615206n, + morphoMarketV1AdapterV2Factory: 39285528n, morphoVaultV1AdapterFactory: 35615206n, registryList: 35615358n, chainlinkOracleFactory: 13978286n, @@ -655,6 +678,7 @@ const _deployments = { metaMorphoFactory: 66931042n, vaultV2Factory: 77371907n, morphoMarketV1AdapterFactory: 77371907n, + morphoMarketV1AdapterV2Factory: 80128162n, morphoVaultV1AdapterFactory: 77371907n, registryList: 77372020n, chainlinkOracleFactory: 66931042n, @@ -675,6 +699,7 @@ const _deployments = { metaMorphoFactory: 296447195n, vaultV2Factory: 387016724n, morphoMarketV1AdapterFactory: 387016724n, + morphoMarketV1AdapterV2Factory: 409152917n, morphoVaultV1AdapterFactory: 387016724n, registryList: 387017701n, chainlinkOracleFactory: 296447195n, @@ -693,6 +718,7 @@ const _deployments = { metaMorphoFactory: 130770189n, vaultV2Factory: 142122059n, morphoMarketV1AdapterFactory: 142122059n, + morphoMarketV1AdapterV2Factory: 144881071n, morphoVaultV1AdapterFactory: 142122059n, registryList: 142122170n, chainlinkOracleFactory: 130770189n, @@ -709,6 +735,7 @@ const _deployments = { metaMorphoFactory: 9025733n, vaultV2Factory: 20253005n, morphoMarketV1AdapterFactory: 20253005n, + morphoMarketV1AdapterV2Factory: 23013012n, morphoVaultV1AdapterFactory: 20253005n, registryList: 20253132n, chainlinkOracleFactory: 9025733n, @@ -764,6 +791,7 @@ const _deployments = { metaMorphoFactory: 9316789n, vaultV2Factory: 29092109n, morphoMarketV1AdapterFactory: 29092109n, + morphoMarketV1AdapterV2Factory: 34613548n, morphoVaultV1AdapterFactory: 29092109n, registryList: 29092328n, chainlinkOracleFactory: 9316789n, @@ -828,6 +856,7 @@ const _deployments = { metaMorphoFactory: 766078n, vaultV2Factory: 32235414n, morphoMarketV1AdapterFactory: 32235414n, + morphoMarketV1AdapterV2Factory: 41965167n, morphoVaultV1AdapterFactory: 32235414n, registryList: 32235782n, chainlinkOracleFactory: 766078n, @@ -856,6 +885,7 @@ const _deployments = { metaMorphoFactory: 2741420n, vaultV2Factory: 13096629n, morphoMarketV1AdapterFactory: 13096629n, + morphoMarketV1AdapterV2Factory: 18619527n, morphoVaultV1AdapterFactory: 13096629n, registryList: 13096853n, chainlinkOracleFactory: 2741420n, @@ -908,6 +938,7 @@ const _deployments = { metaMorphoFactory: 1988677n, vaultV2Factory: 14188393n, morphoMarketV1AdapterFactory: 14188393n, + morphoMarketV1AdapterV2Factory: 21460330n, morphoVaultV1AdapterFactory: 14188393n, registryList: 14188698n, chainlinkOracleFactory: 1988677n, diff --git a/packages/blue-sdk/src/vault/v2/VaultV2MorphoMarketV1AdapterV2.ts b/packages/blue-sdk/src/vault/v2/VaultV2MorphoMarketV1AdapterV2.ts new file mode 100644 index 00000000..a3b0ff07 --- /dev/null +++ b/packages/blue-sdk/src/vault/v2/VaultV2MorphoMarketV1AdapterV2.ts @@ -0,0 +1,122 @@ +import { type Address, type Hex, encodeAbiParameters, keccak256 } from "viem"; +import { type Market, MarketParams, marketParamsAbi } from "../../market"; +import type { BigIntish, MarketId } from "../../types"; +import { CapacityLimitReason } from "../../utils"; +import { VaultV2Adapter } from "./VaultV2Adapter"; +import type { IAccrualVaultV2Adapter, IVaultV2Adapter } from "./VaultV2Adapter"; + +export interface IVaultV2MorphoMarketV1AdapterV2 + extends Omit { + marketIds: MarketId[]; + adaptiveCurveIrm: Address; + supplyShares: Record; +} + +export class VaultV2MorphoMarketV1AdapterV2 + extends VaultV2Adapter + implements IVaultV2MorphoMarketV1AdapterV2 +{ + static adapterId(address: Address) { + return keccak256( + encodeAbiParameters( + [{ type: "string" }, { type: "address" }], + ["this", address], + ), + ); + } + + static collateralId(address: Address) { + return keccak256( + encodeAbiParameters( + [{ type: "string" }, { type: "address" }], + ["collateralToken", address], + ), + ); + } + + static marketParamsId(address: Address, params: MarketParams) { + return keccak256( + encodeAbiParameters( + [{ type: "string" }, { type: "address" }, marketParamsAbi], + ["this/marketParams", address, params], + ), + ); + } + + public marketIds: MarketId[]; + public adaptiveCurveIrm: Address; + public supplyShares: Record; + + constructor({ + marketIds, + adaptiveCurveIrm, + supplyShares, + ...vaultV2Adapter + }: IVaultV2MorphoMarketV1AdapterV2) { + super({ + ...vaultV2Adapter, + adapterId: VaultV2MorphoMarketV1AdapterV2.adapterId( + vaultV2Adapter.address, + ), + }); + + this.marketIds = marketIds; + this.adaptiveCurveIrm = adaptiveCurveIrm; + this.supplyShares = supplyShares; + } + + public ids(params: MarketParams) { + return [ + this.adapterId, + VaultV2MorphoMarketV1AdapterV2.collateralId(params.collateralToken), + VaultV2MorphoMarketV1AdapterV2.marketParamsId(this.address, params), + ]; + } +} + +export interface IAccrualVaultV2MorphoMarketV1AdapterV2 + extends IVaultV2MorphoMarketV1AdapterV2 {} + +export class AccrualVaultV2MorphoMarketV1AdapterV2 + extends VaultV2MorphoMarketV1AdapterV2 + implements IAccrualVaultV2MorphoMarketV1AdapterV2, IAccrualVaultV2Adapter +{ + constructor( + adapter: IAccrualVaultV2MorphoMarketV1AdapterV2, + public markets: Market[], + ) { + super(adapter); + } + + realAssets(timestamp?: BigIntish) { + return this.markets.reduce( + (total, market) => + total + + market + .accrueInterest(timestamp) + .toSupplyAssets(this.supplyShares[market.id] ?? 0n), + 0n, + ); + } + + maxDeposit(_data: Hex, assets: BigIntish) { + return { + value: BigInt(assets), + limiter: CapacityLimitReason.balance, + }; + } + + maxWithdraw(data: Hex) { + const marketId = MarketParams.fromHex(data).id; + const market = this.markets.find((market) => market.id === marketId); + + return ( + market?.getWithdrawCapacityLimit({ + supplyShares: this.supplyShares[marketId] ?? 0n, + }) ?? { + value: 0n, + limiter: CapacityLimitReason.position, + } + ); + } +} diff --git a/packages/blue-sdk/src/vault/v2/index.ts b/packages/blue-sdk/src/vault/v2/index.ts index b3750f3e..dc5d2707 100644 --- a/packages/blue-sdk/src/vault/v2/index.ts +++ b/packages/blue-sdk/src/vault/v2/index.ts @@ -1,4 +1,5 @@ export * from "./VaultV2.js"; export * from "./VaultV2Adapter.js"; export * from "./VaultV2MorphoMarketV1Adapter.js"; +export * from "./VaultV2MorphoMarketV1AdapterV2.js"; export * from "./VaultV2MorphoVaultV1Adapter.js";