Description:
Decentralized Finance (DeFi) protocol contract providing Mintable, Swap, Liquidity, Factory functionality.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/adapters/ERC4626MerklAdapterFactory.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 [Byzantine Finance]
// The implementation of this contract was inspired by Morpho Vault V2, developed by the Morpho Association in 2025.
pragma solidity 0.8.28;
import {ERC4626MerklAdapter} from "./ERC4626MerklAdapter.sol";
import {IERC4626MerklAdapterFactory} from "./interfaces/IERC4626MerklAdapterFactory.sol";
contract ERC4626MerklAdapterFactory is IERC4626MerklAdapterFactory {
/* STORAGE */
mapping(address parentVault => mapping(address erc4626Vault => address)) public erc4626MerklAdapter;
mapping(address account => bool) public isERC4626MerklAdapter;
/* FUNCTIONS */
/// @dev Returns the address of the deployed ERC4626MerklAdapter.
function createERC4626MerklAdapter(address parentVault, address erc4626Vault) external returns (address) {
address _erc4626Adapter = address(new ERC4626MerklAdapter{salt: bytes32(0)}(parentVault, erc4626Vault));
erc4626MerklAdapter[parentVault][erc4626Vault] = _erc4626Adapter;
isERC4626MerklAdapter[_erc4626Adapter] = true;
emit CreateERC4626MerklAdapter(parentVault, erc4626Vault, _erc4626Adapter);
return _erc4626Adapter;
}
}
"
},
"src/adapters/ERC4626MerklAdapter.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 [Byzantine Finance]
// The implementation of this contract was inspired by Morpho Vault V2, developed by the Morpho Association in 2025.
pragma solidity 0.8.28;
import {IVaultV2} from "../interfaces/IVaultV2.sol";
import {IERC4626} from "../interfaces/IERC4626.sol";
import {IERC20} from "../interfaces/IERC20.sol";
import {IERC4626MerklAdapter} from "./interfaces/IERC4626MerklAdapter.sol";
import {IMerklDistributor} from "../interfaces/IMerklDistributor.sol";
import {SafeERC20Lib} from "../libraries/SafeERC20Lib.sol";
/// @dev Generic ERC4626 adapter with Merkl rewards claiming functionality
/// @dev Designed for integration with ERC4626-compliant vaults like Stata (AAVE wrapper)
/// @dev This adapter must be used with ERC4626 vaults that are protected against inflation attacks
/// @dev Must not be used with an ERC4626 vault which can re-enter the parent vault
contract ERC4626MerklAdapter is IERC4626MerklAdapter {
/* IMMUTABLES */
address public immutable factory;
address public immutable parentVault;
address public immutable erc4626Vault;
bytes32 public immutable adapterId;
/* CONSTANTS */
/// @dev Merkl distributor address on the vast majority of chains
address public constant MERKL_DISTRIBUTOR = 0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae;
/* STORAGE */
address public skimRecipient;
address public claimer;
/* FUNCTIONS */
constructor(address _parentVault, address _erc4626Vault) {
factory = msg.sender;
parentVault = _parentVault;
erc4626Vault = _erc4626Vault;
adapterId = keccak256(abi.encode("this", address(this)));
address asset = IVaultV2(_parentVault).asset();
require(asset == IERC4626(_erc4626Vault).asset(), AssetMismatch());
SafeERC20Lib.safeApprove(asset, _parentVault, type(uint256).max);
SafeERC20Lib.safeApprove(asset, _erc4626Vault, type(uint256).max);
}
function setClaimer(address newClaimer) external {
if (msg.sender != IVaultV2(parentVault).curator()) revert NotAuthorized();
claimer = newClaimer;
emit SetClaimer(newClaimer);
}
function setSkimRecipient(address newSkimRecipient) external {
require(msg.sender == IVaultV2(parentVault).owner(), NotAuthorized());
skimRecipient = newSkimRecipient;
emit SetSkimRecipient(newSkimRecipient);
}
/// @dev Skims the adapter's balance of `token` and sends it to `skimRecipient`.
/// @dev This is useful to handle rewards that the adapter has earned.
function skim(address token) external {
require(msg.sender == skimRecipient, NotAuthorized());
require(token != erc4626Vault, CannotSkimERC4626Shares());
uint256 balance = IERC20(token).balanceOf(address(this));
SafeERC20Lib.safeTransfer(token, skimRecipient, balance);
emit Skim(token, balance);
}
/// @dev Does not log anything because the ids (logged in the parent vault) are enough.
/// @dev Returns the ids of the allocation and the change in allocation.
function allocate(bytes memory data, uint256 assets, bytes4, address) external returns (bytes32[] memory, int256) {
require(data.length == 0, InvalidData());
require(msg.sender == parentVault, NotAuthorized());
if (assets > 0) IERC4626(erc4626Vault).deposit(assets, address(this));
uint256 oldAllocation = allocation();
uint256 newAllocation = IERC4626(erc4626Vault).previewRedeem(IERC4626(erc4626Vault).balanceOf(address(this)));
// Safe casts because ERC4626 vaults bound the total supply, and allocation is less than the
// max total assets of the vault.
return (ids(), int256(newAllocation) - int256(oldAllocation));
}
/// @dev Does not log anything because the ids (logged in the parent vault) are enough.
/// @dev Returns the ids of the deallocation and the change in allocation.
function deallocate(bytes memory data, uint256 assets, bytes4, address)
external
returns (bytes32[] memory, int256)
{
require(data.length == 0, InvalidData());
require(msg.sender == parentVault, NotAuthorized());
if (assets > 0) IERC4626(erc4626Vault).withdraw(assets, address(this), address(this));
uint256 oldAllocation = allocation();
uint256 newAllocation = IERC4626(erc4626Vault).previewRedeem(IERC4626(erc4626Vault).balanceOf(address(this)));
// Safe casts because ERC4626 vaults bound the total supply, and allocation is less than the
// max total assets of the vault.
return (ids(), int256(newAllocation) - int256(oldAllocation));
}
/// @dev Claims rewards from Merkl distributor contract and swap it to parent vault's asset
/// @dev Only the claimer can call this function
/// @param data Encoded ClaimParams struct containing merkl params and swap params
function claim(bytes calldata data) external {
require(msg.sender == claimer, NotAuthorized());
// Decode the claim data
(MerklParams memory merklParams, SwapParams[] memory swapParams) = abi.decode(data, (MerklParams, SwapParams[]));
// Claim data checks
require(swapParams.length == merklParams.tokens.length, InvalidData());
// Call the Merkl distributor
IMerklDistributor(MERKL_DISTRIBUTOR).claim(
merklParams.users, merklParams.tokens, merklParams.amounts, merklParams.proofs
);
IERC20 parentVaultAsset = IERC20(IVaultV2(parentVault).asset());
for (uint256 i; i < swapParams.length; ++i) {
// Verify the swapping data
require(
swapParams[i].swapper != erc4626Vault && swapParams[i].swapper != parentVault
&& swapParams[i].swapper != MERKL_DISTRIBUTOR,
SwapperCannotBeTiedContract()
);
require(swapParams[i].minAmountOut > 0, InvalidData());
// Snapshot for sanity check
uint256 parentVaultBalanceBefore = parentVaultAsset.balanceOf(parentVault);
uint256 rewardTokenBalanceBefore = IERC20(merklParams.tokens[i]).balanceOf(address(this));
// Swap the rewards
SafeERC20Lib.safeApprove(merklParams.tokens[i], swapParams[i].swapper, merklParams.amounts[i]);
(bool success,) = swapParams[i].swapper.call(swapParams[i].swapData);
require(success, SwapReverted());
uint256 swappedAmount = rewardTokenBalanceBefore - IERC20(merklParams.tokens[i]).balanceOf(address(this));
// Check if the parent vault received them
uint256 parentVaultBalanceAfter = parentVaultAsset.balanceOf(parentVault);
require(parentVaultBalanceAfter >= parentVaultBalanceBefore + swapParams[i].minAmountOut, SlippageTooHigh());
emit ClaimRewards(merklParams.tokens[i], merklParams.amounts[i]);
emit SwapRewards(swapParams[i].swapper, merklParams.tokens[i], swappedAmount, swapParams[i].swapData);
}
}
/// @dev Returns adapter's ids.
function ids() public view returns (bytes32[] memory) {
bytes32[] memory ids_ = new bytes32[](1);
ids_[0] = adapterId;
return ids_;
}
function allocation() public view returns (uint256) {
return IVaultV2(parentVault).allocation(adapterId);
}
function realAssets() external view returns (uint256) {
return allocation() != 0
? IERC4626(erc4626Vault).previewRedeem(IERC4626(erc4626Vault).balanceOf(address(this)))
: 0;
}
}
"
},
"src/adapters/interfaces/IERC4626MerklAdapterFactory.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 [Byzantine Finance]
// The implementation of this contract was inspired by Morpho Vault V2, developed by the Morpho Association in 2025.
pragma solidity >=0.5.0;
interface IERC4626MerklAdapterFactory {
/* EVENTS */
event CreateERC4626MerklAdapter(
address indexed parentVault, address indexed erc4626Vault, address indexed erc4626MerklAdapter
);
/* FUNCTIONS */
function erc4626MerklAdapter(address parentVault, address erc4626Vault) external view returns (address);
function isERC4626MerklAdapter(address account) external view returns (bool);
function createERC4626MerklAdapter(address parentVault, address erc4626Vault)
external
returns (address erc4626Adapter);
}
"
},
"src/interfaces/IVaultV2.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
import {IERC20} from "./IERC20.sol";
import {IERC4626} from "./IERC4626.sol";
import {IERC2612} from "./IERC2612.sol";
struct Caps {
uint256 allocation;
uint128 absoluteCap;
uint128 relativeCap;
}
interface IVaultV2 is IERC4626, IERC2612 {
// State variables
function virtualShares() external view returns (uint256);
function owner() external view returns (address);
function curator() external view returns (address);
function receiveSharesGate() external view returns (address);
function sendSharesGate() external view returns (address);
function receiveAssetsGate() external view returns (address);
function sendAssetsGate() external view returns (address);
function adapterRegistry() external view returns (address);
function isSentinel(address account) external view returns (bool);
function isAllocator(address account) external view returns (bool);
function firstTotalAssets() external view returns (uint256);
function _totalAssets() external view returns (uint128);
function lastUpdate() external view returns (uint64);
function maxRate() external view returns (uint64);
function adapters(uint256 index) external view returns (address);
function adaptersLength() external view returns (uint256);
function isAdapter(address account) external view returns (bool);
function allocation(bytes32 id) external view returns (uint256);
function absoluteCap(bytes32 id) external view returns (uint256);
function relativeCap(bytes32 id) external view returns (uint256);
function forceDeallocatePenalty(address adapter) external view returns (uint256);
function liquidityAdapter() external view returns (address);
function liquidityData() external view returns (bytes 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);
function performanceFee() external view returns (uint96);
function performanceFeeRecipient() external view returns (address);
function managementFee() external view returns (uint96);
function managementFeeRecipient() external view returns (address);
// Gating
function canSendShares(address account) external view returns (bool);
function canReceiveShares(address account) external view returns (bool);
function canSendAssets(address account) external view returns (bool);
function canReceiveAssets(address account) external view returns (bool);
// Multicall
function multicall(bytes[] memory data) external;
// Owner functions
function setOwner(address newOwner) external;
function setCurator(address newCurator) external;
function setIsSentinel(address account, bool isSentinel) external;
function setName(string memory newName) external;
function setSymbol(string memory newSymbol) external;
// Timelocks for curator functions
function submit(bytes memory data) external;
function revoke(bytes memory data) external;
// Curator functions
function setIsAllocator(address account, bool newIsAllocator) external;
function setReceiveSharesGate(address newReceiveSharesGate) external;
function setSendSharesGate(address newSendSharesGate) external;
function setReceiveAssetsGate(address newReceiveAssetsGate) external;
function setSendAssetsGate(address newSendAssetsGate) external;
function setAdapterRegistry(address newAdapterRegistry) external;
function addAdapter(address account) external;
function removeAdapter(address account) external;
function increaseTimelock(bytes4 selector, uint256 newDuration) external;
function decreaseTimelock(bytes4 selector, uint256 newDuration) external;
function abdicate(bytes4 selector) external;
function setPerformanceFee(uint256 newPerformanceFee) external;
function setManagementFee(uint256 newManagementFee) external;
function setPerformanceFeeRecipient(address newPerformanceFeeRecipient) external;
function setManagementFeeRecipient(address newManagementFeeRecipient) external;
function increaseAbsoluteCap(bytes memory idData, uint256 newAbsoluteCap) external;
function decreaseAbsoluteCap(bytes memory idData, uint256 newAbsoluteCap) external;
function increaseRelativeCap(bytes memory idData, uint256 newRelativeCap) external;
function decreaseRelativeCap(bytes memory idData, uint256 newRelativeCap) external;
function setMaxRate(uint256 newMaxRate) external;
function setForceDeallocatePenalty(address adapter, uint256 newForceDeallocatePenalty) external;
// Allocator functions
function allocate(address adapter, bytes memory data, uint256 assets) external;
function deallocate(address adapter, bytes memory data, uint256 assets) external;
function setLiquidityAdapterAndData(address newLiquidityAdapter, bytes memory newLiquidityData) external;
// Exchange rate
function accrueInterest() external;
function accrueInterestView()
external
view
returns (uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares);
// Force deallocate
function forceDeallocate(address adapter, bytes memory data, uint256 assets, address onBehalf)
external
returns (uint256 penaltyShares);
}
"
},
"src/interfaces/IERC4626.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
import {IERC20} from "./IERC20.sol";
interface IERC4626 is IERC20 {
function asset() external view returns (address);
function totalAssets() external view returns (uint256);
function convertToAssets(uint256 shares) external view returns (uint256 assets);
function convertToShares(uint256 assets) external view returns (uint256 shares);
function deposit(uint256 assets, address onBehalf) external returns (uint256 shares);
function mint(uint256 shares, address onBehalf) external returns (uint256 assets);
function withdraw(uint256 assets, address onBehalf, address receiver) external returns (uint256 shares);
function redeem(uint256 shares, address onBehalf, address receiver) external returns (uint256 assets);
function previewDeposit(uint256 assets) external view returns (uint256 shares);
function previewMint(uint256 shares) external view returns (uint256 assets);
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
function previewRedeem(uint256 shares) external view returns (uint256 assets);
function maxDeposit(address onBehalf) external view returns (uint256 assets);
function maxMint(address onBehalf) external view returns (uint256 shares);
function maxWithdraw(address onBehalf) external view returns (uint256 assets);
function maxRedeem(address onBehalf) external view returns (uint256 shares);
}
"
},
"src/interfaces/IERC20.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
interface IERC20 {
function decimals() external view returns (uint8);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 shares) external returns (bool success);
function transferFrom(address from, address to, uint256 shares) external returns (bool success);
function approve(address spender, uint256 shares) external returns (bool success);
function allowance(address owner, address spender) external view returns (uint256);
}
"
},
"src/adapters/interfaces/IERC4626MerklAdapter.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 [Byzantine Finance]
// The implementation of this contract was inspired by Morpho Vault V2, developed by the Morpho Association in 2025.
pragma solidity >= 0.5.0;
import {IAdapter} from "../../interfaces/IAdapter.sol";
interface IERC4626MerklAdapter is IAdapter {
/* STRUCTS */
struct MerklParams {
address[] users;
address[] tokens;
uint256[] amounts;
bytes32[][] proofs;
}
struct SwapParams {
address swapper;
uint256 minAmountOut;
bytes swapData;
}
/* EVENTS */
event SetSkimRecipient(address indexed newSkimRecipient);
event SetClaimer(address indexed newClaimer);
event Skim(address indexed token, uint256 assets);
event ClaimRewards(address indexed token, uint256 amount);
event SwapRewards(address indexed swapper, address indexed token, uint256 amount, bytes swapData);
/* ERRORS */
error AssetMismatch();
error CannotSkimERC4626Shares();
error InvalidData();
error NotAuthorized();
error SwapperCannotBeTiedContract();
error SwapReverted();
error SlippageTooHigh();
/* FUNCTIONS */
function factory() external view returns (address);
function parentVault() external view returns (address);
function erc4626Vault() external view returns (address);
function adapterId() external view returns (bytes32);
function skimRecipient() external view returns (address);
function claimer() external view returns (address);
function allocation() external view returns (uint256);
function ids() external view returns (bytes32[] memory);
function setSkimRecipient(address newSkimRecipient) external;
function setClaimer(address newClaimer) external;
function skim(address token) external;
function claim(bytes calldata data) external;
}
"
},
"src/interfaces/IMerklDistributor.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 [Byzantine Finance]
pragma solidity >= 0.5.0;
/// @notice Interface for Merkl distributor contract
interface IMerklDistributor {
function claim(
address[] calldata users,
address[] calldata tokens,
uint256[] calldata amounts,
bytes32[][] calldata proofs
) external;
}
"
},
"src/libraries/SafeERC20Lib.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity ^0.8.0;
import {IERC20} from "../interfaces/IERC20.sol";
import {ErrorsLib} from "./ErrorsLib.sol";
library SafeERC20Lib {
function safeTransfer(address token, address to, uint256 value) internal {
require(token.code.length > 0, ErrorsLib.NoCode());
(bool success, bytes memory returndata) = token.call(abi.encodeCall(IERC20.transfer, (to, value)));
require(success, ErrorsLib.TransferReverted());
require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.TransferReturnedFalse());
}
function safeTransferFrom(address token, address from, address to, uint256 value) internal {
require(token.code.length > 0, ErrorsLib.NoCode());
(bool success, bytes memory returndata) = token.call(abi.encodeCall(IERC20.transferFrom, (from, to, value)));
require(success, ErrorsLib.TransferFromReverted());
require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.TransferFromReturnedFalse());
}
function safeApprove(address token, address spender, uint256 value) internal {
require(token.code.length > 0, ErrorsLib.NoCode());
(bool success, bytes memory returndata) = token.call(abi.encodeCall(IERC20.approve, (spender, value)));
require(success, ErrorsLib.ApproveReverted());
require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.ApproveReturnedFalse());
}
}
"
},
"src/interfaces/IERC2612.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
interface IERC2612 {
function permit(address owner, address spender, uint256 shares, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
"
},
"src/interfaces/IAdapter.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity >=0.5.0;
/// @dev See VaultV2 NatSpec comments for more details on adapter's spec.
interface IAdapter {
/// @dev Returns the market' ids and the change in assets on this market.
function allocate(bytes memory data, uint256 assets, bytes4 selector, address sender)
external
returns (bytes32[] memory ids, int256 change);
/// @dev Returns the market' ids and the change in assets on this market.
function deallocate(bytes memory data, uint256 assets, bytes4 selector, address sender)
external
returns (bytes32[] memory ids, int256 change);
/// @dev Returns the current value of the investments of the adapter (in underlying asset).
function realAssets() external view returns (uint256 assets);
}
"
},
"src/libraries/ErrorsLib.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Morpho Association
pragma solidity ^0.8.0;
library ErrorsLib {
error Abdicated();
error AbsoluteCapExceeded();
error AbsoluteCapNotDecreasing();
error AbsoluteCapNotIncreasing();
error ApproveReturnedFalse();
error ApproveReverted();
error CannotReceiveShares();
error CannotReceiveAssets();
error CannotSendShares();
error CannotSendAssets();
error CapExceeded();
error CastOverflow();
error DataAlreadyPending();
error DataNotTimelocked();
error FeeInvariantBroken();
error FeeTooHigh();
error InvalidSigner();
error MaxRateTooHigh();
error NoCode();
error NotAdapter();
error NotInAdapterRegistry();
error PenaltyTooHigh();
error PermitDeadlineExpired();
error RelativeCapAboveOne();
error RelativeCapExceeded();
error RelativeCapNotDecreasing();
error RelativeCapNotIncreasing();
error AutomaticallyTimelocked();
error TimelockNotDecreasing();
error TimelockNotExpired();
error TimelockNotIncreasing();
error TransferFromReturnedFalse();
error TransferFromReverted();
error TransferReturnedFalse();
error TransferReverted();
error Unauthorized();
error ZeroAbsoluteCap();
error ZeroAddress();
error ZeroAllocation();
}
"
}
},
"settings": {
"remappings": [
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"ds-test/=lib/morpho-blue/lib/forge-std/lib/ds-test/src/",
"erc4626-tests/=lib/metamorpho/lib/erc4626-tests/",
"forge-std/=lib/forge-std/src/",
"halmos-cheatcodes/=lib/morpho-blue/lib/halmos-cheatcodes/src/",
"metamorpho-v1.1/=lib/metamorpho-v1.1/",
"metamorpho/=lib/metamorpho/",
"morpho-blue-irm/=lib/metamorpho/lib/morpho-blue-irm/src/",
"morpho-blue/=lib/morpho-blue/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"solmate/=lib/metamorpho/lib/morpho-blue-irm/lib/solmate/src/"
],
"optimizer": {
"enabled": true,
"runs": 100000
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "none",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": true
}
}}
Submitted on: 2025-10-08 11:55:58
Comments
Log in to comment.
No comments yet.