Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
715346a
initial commit
sparrowDom Feb 3, 2026
cfe1e16
add deploy file
sparrowDom Feb 3, 2026
7bdb55e
account for owner liquidity and available liquidity
sparrowDom Feb 4, 2026
451bc7b
prettier
sparrowDom Feb 5, 2026
1f943d8
add comment
sparrowDom Feb 6, 2026
9ee8188
add support to redeem only what is available from 4626 Vault
sparrowDom Feb 10, 2026
6e2d329
enforce the expected liquidity adpater when estimating the additional…
sparrowDom Feb 10, 2026
ed8c49a
Cross-chain Strategy (#2715)
shahthepro Feb 10, 2026
18f49da
[Vault] Simplify VaultCore. (#2714)
clement-ux Feb 10, 2026
373a646
[CurvePB] Migrate to new CurvePB. (#2768)
clement-ux Feb 10, 2026
717f819
Reorder deployment script number (#2791)
clement-ux Feb 10, 2026
2618d7d
[CurvePB] Improve CurvePoolBoosterBribesModule (#2789)
clement-ux Feb 11, 2026
d3fd3e3
[CurvePB] Deploy CurvePBBribeModule for Safe. (#2794)
clement-ux Feb 11, 2026
bf7bd0e
[Ethereum] [Base] Deploy Crosschain Strategy (#2793)
shahthepro Feb 12, 2026
904a9a1
[OUSDVault] Enable Async Withdraw. (#2795)
clement-ux Feb 12, 2026
9a91da3
Deploy 171 (#2792)
clement-ux Feb 12, 2026
5c6bd49
Update scripts and runlog (#2801)
shahthepro Feb 16, 2026
4283716
[CurvePB] Update CurvePoolBoosterBribesModule address (#2797)
clement-ux Feb 16, 2026
d2fd6e9
[MerklPB] Transfer PoolBoostCentralRegistry governance to strategist …
clement-ux Feb 16, 2026
fb5dfd3
Run log 17 feb 26 (#2803)
naddison36 Feb 18, 2026
bdffa61
Add scripts to change Crosschain strategy operator (#2802)
shahthepro Feb 18, 2026
8925265
Fix the Native Staking Strategy fork tests for the second SSV cluster…
naddison36 Feb 19, 2026
c2fe983
Repo Cleanup (#2799)
shahthepro Feb 19, 2026
61b357a
Run log - Allocate 100k USDC to the Cross-chain strategy (#2805)
naddison36 Feb 19, 2026
1535963
Document updates and snapVault for OUSD (#2806)
naddison36 Feb 19, 2026
6b14cd6
Run log - 23 Feb withdraw from Morpho v2 OUSD Strategy (#2809)
naddison36 Feb 23, 2026
1dedc0d
Add Auto-Withdrawal Safe Module (#2807)
shahthepro Feb 24, 2026
8162866
Deploy OUSD Auto-Withdrawal Safe Module (#2812)
shahthepro Feb 24, 2026
a9ba0f5
Async withdrawal + mint(uint256) cleanup (#2813)
clement-ux Feb 24, 2026
5b39e8a
rename
sparrowDom Feb 28, 2026
2ff1f59
Merge remote-tracking branch 'origin/master' into sparrowDom/morphoV2…
sparrowDom Mar 2, 2026
350a102
add agents.md to ignore
sparrowDom Mar 2, 2026
09be7d0
Update contracts/test/strategies/ousd-v2-morpho.mainnet.fork-test.js
sparrowDom Mar 4, 2026
0d8fe2e
natspec fix
sparrowDom Mar 4, 2026
464a8ce
include the USDC on the contract as well
sparrowDom Mar 4, 2026
f2a2b91
Sparrow dom/morpho v2 withdraw all library (#2818)
sparrowDom Mar 4, 2026
9e1f685
set harvester to multichain strategist
sparrowDom Mar 4, 2026
425502c
Merge remote-tracking branch 'origin/master' into sparrowDom/morphoV2…
sparrowDom Mar 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,6 @@ unit-coverage

# Certora #
.certora_internal

# Possible Agent.md file
AGENTS.md
10 changes: 10 additions & 0 deletions contracts/contracts/interfaces/morpho/IMorphoV2Adapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

interface IMorphoV2Adapter {
// address of the underlying vault
function morphoVaultV1() external view returns (address);

// address of the parent Morpho V2 vault
function parentVault() external view returns (address);
}
8 changes: 8 additions & 0 deletions contracts/contracts/interfaces/morpho/IVaultV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { IERC4626 } from "../../../lib/openzeppelin/interfaces/IERC4626.sol";

interface IVaultV2 is IERC4626 {
function liquidityAdapter() external view returns (address);
}
14 changes: 14 additions & 0 deletions contracts/contracts/mocks/MockMorphoV1Vault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { MockERC4626Vault } from "./MockERC4626Vault.sol";

contract MockMorphoV1Vault is MockERC4626Vault {
address public liquidityAdapter;

constructor(address _asset) MockERC4626Vault(_asset) {}

function setLiquidityAdapter(address _liquidityAdapter) external {
liquidityAdapter = _liquidityAdapter;
}
}
20 changes: 20 additions & 0 deletions contracts/contracts/mocks/MockMorphoV1VaultLiquidityAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { IMorphoV2Adapter } from "../interfaces/morpho/IMorphoV2Adapter.sol";

contract MockMorphoV1VaultLiquidityAdapter is IMorphoV2Adapter {
address public mockMorphoVault;

function setMockMorphoVault(address _mockMorphoVault) external {
mockMorphoVault = _mockMorphoVault;
}

function morphoVaultV1() external view override returns (address) {
return mockMorphoVault;
}

function parentVault() external view override returns (address) {
return mockMorphoVault;
}
}
11 changes: 8 additions & 3 deletions contracts/contracts/strategies/Generalized4626Strategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ pragma solidity ^0.8.0;
/**
* @title Generalized 4626 Strategy
* @notice Investment strategy for ERC-4626 Tokenized Vaults
* @dev This strategy should not be used for the Morpho V2 Vaults as those are not
* completley ERC-4626 compliant - they don't implement the maxWithdraw() and
* maxRedeem() functions and rather return 0 when any of them is called.
* @author Origin Protocol Inc
*/
import { IERC4626 } from "../../lib/openzeppelin/interfaces/IERC4626.sol";
Expand Down Expand Up @@ -141,11 +144,13 @@ contract Generalized4626Strategy is InitializableAbstractStrategy {
onlyVaultOrGovernor
nonReentrant
{
uint256 shareBalance = shareToken.balanceOf(address(this));
// @dev Don't use for Morpho V2 Vaults as below line will return 0
uint256 sharesToRedeem = IERC4626(platformAddress).maxRedeem(address(this));

uint256 assetAmount = 0;
if (shareBalance > 0) {
if (sharesToRedeem > 0) {
assetAmount = IERC4626(platformAddress).redeem(
shareBalance,
sharesToRedeem,
vaultAddress,
address(this)
);
Expand Down
78 changes: 78 additions & 0 deletions contracts/contracts/strategies/MorphoV2Strategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

/**
* @title Generalized 4626 Strategy when the underlying platform is Morpho V2
* @notice Investment strategy for ERC-4626 Tokenized Vaults for the Morpho V2 platform.
* @author Origin Protocol Inc
*/
import { Generalized4626Strategy } from "./Generalized4626Strategy.sol";
import { MorphoV2VaultUtils } from "./MorphoV2VaultUtils.sol";
import { IVaultV2 } from "../interfaces/morpho/IVaultV2.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";

contract MorphoV2Strategy is Generalized4626Strategy {

/**
* @param _baseConfig Base strategy config with Morpho V2 Vault and
* vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy
* @param _assetToken Address of the ERC-4626 asset token. e.g. USDC
*/
constructor(BaseStrategyConfig memory _baseConfig, address _assetToken)
Generalized4626Strategy(_baseConfig, _assetToken)
{}

/**
* @notice Remove all the liquidity that is available in the Morpho V2 vault.
* Which might not be all of the liquidity owned by the strategy.
* @dev Remove all the liquidity that is available in the Morpho V2 vault
* The particular behaviour of the Morpho V2 vault is that it can hold
* multiple Morpho V1 vaults as adapters but only one liquidity adapter.
* The immediate available funds on the Morpho V2 vault are therfore any
* liquid assets residing on the Vault V2 contract and the maxWithdraw
* amount that the Morpho V1 contract can supply.
*/
function withdrawAll()
external
virtual
override
onlyVaultOrGovernor
nonReentrant
{
uint256 availableMorphoVault = _maxWithdraw();
uint256 balanceToWithdraw = Math.min(
availableMorphoVault,
checkBalance(address(assetToken))
);

if (balanceToWithdraw > 0) {
// slither-disable-next-line unused-return
IVaultV2(platformAddress).withdraw(
balanceToWithdraw,
vaultAddress,
address(this)
);
}

emit Withdrawal(
address(assetToken),
address(shareToken),
balanceToWithdraw
);
}

function maxWithdraw() external view returns (uint256) {
return _maxWithdraw();
}

function _maxWithdraw()
internal
view
returns (uint256 availableAssetLiquidity)
{
availableAssetLiquidity = MorphoV2VaultUtils.maxWithdrawableAssets(
platformAddress,
address(assetToken)
);
}
}
38 changes: 38 additions & 0 deletions contracts/contracts/strategies/MorphoV2VaultUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { IERC20 } from "../utils/InitializableAbstractStrategy.sol";
import { IERC4626 } from "../../lib/openzeppelin/interfaces/IERC4626.sol";
import { IVaultV2 } from "../interfaces/morpho/IVaultV2.sol";
import { IMorphoV2Adapter } from "../interfaces/morpho/IMorphoV2Adapter.sol";

library MorphoV2VaultUtils {
error IncompatibleAdapter(address adapter);

/**
* @notice Return maximum amount that can be safely withdrawn from a Morpho V2 vault.
* @dev Available liquidity is:
* 1) asset balance parked on Morpho V2 vault contract
* 2) additional liquidity from the active adapter if it resolves to a Morpho V1 vault
* and, when provided, matches the expected adapter
*/
function maxWithdrawableAssets(
address platformAddress,
address assetToken
) internal view returns (uint256 availableAssetLiquidity) {
availableAssetLiquidity = IERC20(assetToken).balanceOf(platformAddress);

address liquidityAdapter = IVaultV2(platformAddress).liquidityAdapter();
// this is a sufficient check to ensure the adapter is Morpho V1
try IMorphoV2Adapter(liquidityAdapter).morphoVaultV1() returns (
address underlyingVault
) {
availableAssetLiquidity += IERC4626(underlyingVault).maxWithdraw(
liquidityAdapter
);
} catch {
revert IncompatibleAdapter(liquidityAdapter);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ pragma solidity ^0.8.0;
*/

import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { IERC20 } from "../../utils/InitializableAbstractStrategy.sol";
import { IERC4626 } from "../../../lib/openzeppelin/interfaces/IERC4626.sol";
import { IVaultV2 } from "../../interfaces/morpho/IVaultV2.sol";
import { Generalized4626Strategy } from "../Generalized4626Strategy.sol";
import { AbstractCCTPIntegrator } from "./AbstractCCTPIntegrator.sol";
import { CrossChainStrategyHelper } from "./CrossChainStrategyHelper.sol";
import { InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol";
import { Strategizable } from "../../governance/Strategizable.sol";
import { MorphoV2VaultUtils } from "../MorphoV2VaultUtils.sol";

contract CrossChainRemoteStrategy is
AbstractCCTPIntegrator,
Expand Down Expand Up @@ -140,11 +143,22 @@ contract CrossChainRemoteStrategy is
nonReentrant
{
IERC4626 platform = IERC4626(platformAddress);
_withdraw(
address(this),
usdcToken,
uint256 availableMorphoVault = MorphoV2VaultUtils.maxWithdrawableAssets(
platformAddress,
usdcToken
);
uint256 amountToWithdraw = Math.min(
availableMorphoVault,
platform.previewRedeem(platform.balanceOf(address(this)))
);

if (amountToWithdraw > 0) {
_withdraw(
address(this),
usdcToken,
amountToWithdraw
);
}
}

/// @inheritdoc AbstractCCTPIntegrator
Expand Down
51 changes: 51 additions & 0 deletions contracts/deploy/base/045_crosschain_upgrade_remote.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const { deployOnBase } = require("../../utils/deploy-l2");
const addresses = require("../../utils/addresses");
const {
deployCrossChainRemoteStrategyImpl,
getCreate2ProxyAddress,
} = require("../deployActions");
const { cctpDomainIds } = require("../../utils/cctp");

module.exports = deployOnBase(
{
deployName: "045_crosschain_upgrade_remote",
},
async () => {
const crossChainStrategyProxyAddress = await getCreate2ProxyAddress(
"CrossChainStrategyProxy"
);
console.log(
`CrossChainStrategyProxy address: ${crossChainStrategyProxyAddress}`
);

const implAddress = await deployCrossChainRemoteStrategyImpl(
addresses.base.MorphoOusdV2Vault, // 4626 Vault
crossChainStrategyProxyAddress,
cctpDomainIds.Ethereum,
crossChainStrategyProxyAddress,
addresses.base.USDC,
addresses.mainnet.USDC,
"CrossChainRemoteStrategy",
addresses.CCTPTokenMessengerV2,
addresses.CCTPMessageTransmitterV2,
addresses.base.timelock,
false
);
console.log(`CrossChainRemoteStrategyImpl address: ${implAddress}`);

const cCrossChainStrategyProxy = await ethers.getContractAt(
"CrossChainStrategyProxy",
crossChainStrategyProxyAddress
);

return {
actions: [
{
contract: cCrossChainStrategyProxy,
signature: "upgradeTo(address)",
args: [implAddress],
},
],
};
}
);
2 changes: 1 addition & 1 deletion contracts/deploy/deployActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1131,7 +1131,7 @@ const deployCrossChainUnitTestStrategy = async (usdcAddress) => {
"CCTPMessageTransmitterMock"
);
const tokenMessenger = await ethers.getContract("CCTPTokenMessengerMock");
const c4626Vault = await ethers.getContract("MockERC4626Vault");
const c4626Vault = await ethers.getContract("MockMorphoV1Vault");

await deployCrossChainMasterStrategyImpl(
dMasterProxy.address,
Expand Down
7 changes: 7 additions & 0 deletions contracts/deploy/mainnet/000_mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => {
from: deployerAddr,
args: [usdc.address],
});
await deploy("MockMorphoV1Vault", {
from: deployerAddr,
args: [usdc.address],
});
await deploy("MockMorphoV1VaultLiquidityAdapter", {
from: deployerAddr,
});
// const tokenMessenger = await ethers.getContract("CCTPTokenMessengerMock");
// await messageTransmitter
// .connect(sDeployer)
Expand Down
47 changes: 47 additions & 0 deletions contracts/deploy/mainnet/179_upgrade_ousd_morpho_v2_strategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const addresses = require("../../utils/addresses");
const { deploymentWithGovernanceProposal } = require("../../utils/deploy");

module.exports = deploymentWithGovernanceProposal(
{
deployName: "179_upgrade_ousd_morpho_v2_strategy",
forceDeploy: false,
reduceQueueTime: true,
deployerIsProposer: false,
proposalId: "",
},
async ({ deployWithConfirmation }) => {
const cVaultProxy = await ethers.getContract("VaultProxy");
const cOUSDMorphoV2StrategyProxy = await ethers.getContract(
"OUSDMorphoV2StrategyProxy"
);

const dMorphoV2StrategyImpl = await deployWithConfirmation(
"MorphoV2Strategy",
[
[addresses.mainnet.MorphoOUSDv2Vault, cVaultProxy.address],
addresses.mainnet.USDC
]
);

const cMorphoV2Strategy = await ethers.getContractAt(
"MorphoV2Strategy",
cOUSDMorphoV2StrategyProxy.address
);

return {
name: "Upgrade OUSD Morpho V2 strategy implementation",
actions: [
{
contract: cOUSDMorphoV2StrategyProxy,
signature: "upgradeTo(address)",
args: [dMorphoV2StrategyImpl.address],
},
{
contract: cMorphoV2Strategy,
signature: "setHarvesterAddress(address)",
args: [addresses.multichainStrategist],
},
],
};
}
);
Loading
Loading