Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"node_modules/@openzeppelin/contracts/interfaces/IERC1363.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)
pragma solidity >=0.6.2;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
"
},
"node_modules/@openzeppelin/contracts/interfaces/IERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)
pragma solidity >=0.4.16;
import {IERC165} from "../utils/introspection/IERC165.sol";
"
},
"node_modules/@openzeppelin/contracts/interfaces/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)
pragma solidity >=0.4.16;
import {IERC20} from "../token/ERC20/IERC20.sol";
"
},
"node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
},
"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
"
},
"node_modules/@openzeppelin/contracts/utils/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
"
},
"node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
},
"src/interfaces/IGenesisStrategy.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title IGenesisStrategy
* @notice Interface for Genesis Vaults yield generation strategies
* @dev All strategies must implement this interface
*/
interface IGenesisStrategy {
/**
* @notice Deposit assets into the strategy
* @param amount Amount of assets to deposit
* @return The amount actually deposited
*/
function deposit(uint256 amount) external returns (uint256);
/**
* @notice Withdraw specific amount from strategy
* @param amount Amount to withdraw
* @return The amount actually withdrawn
*/
function withdraw(uint256 amount) external returns (uint256);
/**
* @notice Withdraw all assets from strategy
* @return principal total amount withdrawn
* @return yield total amount of yield withdrawn
*/
function withdrawAll() external returns (uint256 principal, uint256 yield);
/**
* @notice Get total assets under management
* @return Total assets in the strategy
*/
function totalAssets() external view returns (uint256);
/**
* @notice Claim rewards from the strategy (yield)
* @return amount of rewards claimed (returned in liquidity token)
*/
function claimRewards() external returns (uint256);
/**
* @notice Claim rewards from the strategy (non-yield external incentives)
* @return rewardTokens array of reward token addresses
* @return amounts array of reward amounts claimed
*/
function claimIncentiveRewards()
external
returns (address[] memory rewardTokens, uint256[] memory amounts);
/**
* @notice Get unclaimed incentive rewards balance (non-yield external incentives)
* @return rewardTokens array of reward token addresses
* @return amounts array of unclaimed amounts
*/
function getUnclaimedIncentiveRewards()
external
view
returns (address[] memory rewardTokens, uint256[] memory amounts);
/**
* @notice Check if strategy supports given asset
* @param token Token address to check
* @return True if asset is supported
*/
function supportsAsset(address token) external view returns (bool);
/**
* @notice Get strategy name
* @return Strategy name string
*/
function name() external view returns (string memory);
/**
* @notice Get strategy version
* @return Version string
*/
function version() external view returns (string memory);
/**
* @notice Get the asset this strategy manages
* @return Asset token address
*/
function asset() external view returns (address);
}
"
},
"src/interfaces/IGenesisVaultEvents.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title IGenesisVaultEvents
* @notice Consolidated events interface for Genesis Vault system
* @dev Contains all events emitted by the vault system modules
*/
interface IGenesisVaultEvents {
/// @notice Emitted when a new vault is created
event VaultCreated(uint256 indexed vaultId, address indexed liquidityToken, string name);
/// @notice Emitted when a vault is updated
event VaultUpdated(
uint256 indexed vaultId,
string name,
uint256 minDeposit,
uint256 minWithdraw,
uint256 maxTotalDeposits,
uint256 withdrawalDelay
);
/// @notice Emitted when ownership transfer is initiated
event OwnershipTransferInitiated(address indexed currentOwner, address indexed newOwner);
/// @notice Emitted when ownership is transferred
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/// @notice Emitted when global pause state changes
event GlobalPauseStateChanged(uint8 indexed oldState, uint8 indexed newState);
/// @notice Emitted when user deposits into vault
event Deposit(uint256 indexed vaultId, address indexed user, uint256 amount, uint256 shares);
/// @notice Emitted when user withdraws from vault
event Withdraw(
uint256 indexed vaultId,
address indexed user,
uint256 shares,
uint256 assets,
uint256 remainingShares
);
/// @notice Emitted when strategy is changed
event StrategyChanged(
uint256 indexed vaultId,
address indexed oldStrategy,
address indexed newStrategy
);
/// @notice Emitted when performance fee configuration is set
event PerformanceFeeSet(uint256 indexed vaultId, uint256 rate, address indexed recipient);
/// @notice Emitted when performance fee is collected
event PerformanceFeeCollected(
uint256 indexed vaultId,
uint256 amount,
address indexed recipient
);
/// @notice Emitted when yield is harvested and fees are collected
event YieldHarvested(uint256 indexed vaultId, uint256 claimedYield, uint256 performanceFee);
/// @notice Emitted when SY token is created for a vault
event SYTokenCreated(
uint256 indexed vaultId,
address indexed syToken,
string name,
string symbol
);
/// @notice Emitted when a specific token incentive reward is claimed
/// @dev One event is emitted per token with a non-zero claimed amount
event StrategyIncentivesClaimed(
uint256 indexed vaultId,
address token,
uint256 amount,
address indexed recipient
);
/// @notice Emitted when tokens are emergency recovered from SY token
event SYTokenEmergencyRecovered(
uint256 indexed vaultId,
address indexed syToken,
address indexed token,
address to,
uint256 amount
);
/// @notice Emitted when migration is initiated
event MigrationInitiated(address indexed migrationProxy, uint256 timestamp);
/// @notice Emitted when a vault is migrated
event VaultMigrated(
uint256 indexed vaultId,
address indexed migrationProxy,
uint256 assetsMigrated
);
/// @notice Emitted when withdrawal penalty is set
event WithdrawalPenaltySet(uint256 penaltyBps, address recipient);
/// @notice Emitted when withdrawal penalty is applied
event WithdrawalPenaltyApplied(
uint256 indexed vaultId,
address indexed user,
uint256 penalty,
address recipient
);
}
"
},
"src/interfaces/IMigrationModule.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./IGenesisVaultEvents.sol";
/**
* @title IMigrationModule
* @notice Interface for vault migration operations
*/
interface IMigrationModule is IGenesisVaultEvents {
/**
* @notice Initiate migration process
* @param migrationProxy The address of the migration proxy contract
* @param migrationDelay The delay in seconds before migration can be executed (0 = default 1 day, min 1 day)
*/
function initiateMigration(address migrationProxy, uint256 migrationDelay) external;
/**
* @notice Migrate a specific vault's assets to the migration proxy
* @param vaultId The vault ID to migrate
* @return transferredAmount The amount of assets transferred to the migration proxy
*/
function migrateVault(uint256 vaultId) external returns (uint256 transferredAmount);
/**
* @notice Get migration timestamp
* @return The timestamp when migration was initiated (0 if not initiated)
*/
function getMigrationTimestamp() external view returns (uint256);
/**
* @notice Get migration proxy address
* @return The migration proxy contract address
*/
function getMigrationProxy() external view returns (address);
/**
* @notice Check if migration has been initiated
* @return True if migration is initiated
*/
function isMigrationInitiated() external view returns (bool);
/**
* @notice Check if a vault has been migrated
* @param vaultId The vault ID to check
* @return True if the vault has been migrated
*/
function isVaultMigrated(uint256 vaultId) external view returns (bool);
/**
* @notice Get migration delay
* @return The migration delay in seconds
*/
function getMigrationDelay() external view returns (uint256);
}
"
},
"src/libraries/Errors.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title Errors
* @notice Custom error library for Genesis Vaults
* @dev All validation errors are defined here as custom errors for gas efficiency
*/
library Errors {
// Vault Module Errors
error ZeroAmount();
error BelowMinimumDeposit(uint256 minDeposit, uint256 provided);
error BelowMinimumWithdraw(uint256 minWithdraw, uint256 attemptedWithdraw);
error ExceedsCapacity(uint256 capacity, uint256 requested);
error ZeroShares();
error InsufficientShares(uint256 available, uint256 requested);
error ZeroAssets();
error WithdrawalDelayNotMet(uint256 requiredTime, uint256 currentTime);
// Configuration Module Errors
error InvalidOwner(address provided);
error AlreadyInitialized();
error InvalidToken(address token);
error EmptyName();
error InvalidLimits(uint256 minDeposit, uint256 maxTotalDeposits);
error OnlyOwner(address caller, address owner);
error NotPendingOwner(address caller, address pendingOwner);
error VaultNotFound(uint256 vaultId);
error NoSYToken(uint256 vaultId);
error InvalidPauseState(uint8 pauseState);
// Strategy Module Errors
error InvalidStrategy(address strategy);
error UnsupportedAsset(address asset, address strategy);
error RateTooHigh(uint256 rate, uint256 maxRate);
error InvalidRecipient(address recipient);
error NoStrategy();
error StrategyAlreadyInUse(address strategy);
// Global State Errors
error DepositsDisabled();
error SystemFrozen();
// Migration Errors
error InvalidMigrationProxy(address proxy);
error InvalidMigrationDelay(uint256 delay);
error MigrationAlreadyInitiated();
error MigrationNotInitiated();
error MigrationDelayNotMet(uint256 requiredTime, uint256 currentTime);
error VaultAlreadyMigrated(uint256 vaultId);
error NoAssetsToMigrate(uint256 vaultId);
// Withdrawal penalty errors
error InvalidPenaltyRate(uint256 rate);
}
"
},
"src/modules/MigrationModule.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../interfaces/IMigrationModule.sol";
import "../storage/GlobalStorage.sol";
import "../storage/Vault.sol";
import "../libraries/Errors.sol";
/**
* @title MigrationModule
* @notice Module for vault migration operations
* @dev Handles migration initiation and vault asset migration
*/
contract MigrationModule is IMigrationModule, ReentrancyGuard {
using SafeERC20 for IERC20;
using GlobalStorage for GlobalStorage.Data;
using Vault for Vault.Data;
uint256 private constant MIN_MIGRATION_DELAY = 1 days;
/**
* @notice Initiate migration process
* @param migrationProxy The address of the migration proxy contract
* @param migrationDelay The delay in seconds before migration can be executed (0 = default 3 days)
*/
function initiateMigration(
address migrationProxy,
uint256 migrationDelay
) external override nonReentrant {
GlobalStorage.Data storage gs = GlobalStorage.load();
gs.validateOwner(msg.sender);
if (migrationProxy == address(0)) {
revert Errors.InvalidMigrationProxy(migrationProxy);
}
if (gs.migrationInitiatedAt != 0) {
revert Errors.MigrationAlreadyInitiated();
}
uint256 delay = migrationDelay == 0 ? MIN_MIGRATION_DELAY : migrationDelay;
if (delay < MIN_MIGRATION_DELAY) {
revert Errors.InvalidMigrationDelay(delay);
}
gs.migrationProxy = migrationProxy;
gs.migrationInitiatedAt = block.timestamp;
gs.migrationDelay = delay;
gs.pauseState = GlobalStorage.PauseState.DEPOSITS_PAUSED;
emit MigrationInitiated(migrationProxy, block.timestamp);
emit GlobalPauseStateChanged(
uint8(GlobalStorage.PauseState.NONE),
uint8(GlobalStorage.PauseState.DEPOSITS_PAUSED)
);
}
/**
* @notice Migrate a specific vault's assets to the migration proxy
* @param vaultId The vault ID to migrate
* @return transferredAmount The amount of assets transferred to the migration proxy
*/
function migrateVault(
uint256 vaultId
) external override nonReentrant returns (uint256 transferredAmount) {
GlobalStorage.Data storage gs = GlobalStorage.load();
gs.validateOwner(msg.sender);
gs.validateVaultExists(vaultId);
uint256 migrationInitiatedAt = gs.migrationInitiatedAt;
if (migrationInitiatedAt == 0) {
revert Errors.MigrationNotInitiated();
}
uint256 migrationDelay = gs.migrationDelay;
if (block.timestamp < migrationInitiatedAt + migrationDelay) {
revert Errors.MigrationDelayNotMet(
migrationInitiatedAt + migrationDelay,
block.timestamp
);
}
if (gs.vaultMigrated[vaultId]) {
revert Errors.VaultAlreadyMigrated(vaultId);
}
// Freeze all vaults, once we start migration
if (gs.pauseState != GlobalStorage.PauseState.FROZEN) {
gs.pauseState = GlobalStorage.PauseState.FROZEN;
emit GlobalPauseStateChanged(
uint8(GlobalStorage.PauseState.DEPOSITS_PAUSED),
uint8(GlobalStorage.PauseState.FROZEN)
);
}
Vault.Data storage vault = Vault.load(vaultId);
if (vault.strategy != address(0)) {
vault.unsetStrategy();
}
uint256 assetsToMigrate = vault.idleLiquidity;
if (assetsToMigrate == 0) {
revert Errors.NoAssetsToMigrate(vaultId);
}
vault.idleLiquidity = 0;
vault.migratedLiquidity = assetsToMigrate;
gs.vaultMigrated[vaultId] = true;
IERC20(vault.liquidityToken).safeTransfer(gs.migrationProxy, assetsToMigrate);
emit VaultMigrated(vaultId, gs.migrationProxy, assetsToMigrate);
return assetsToMigrate;
}
/**
* @notice Get migration timestamp
* @return The timestamp when migration was initiated (0 if not initiated)
*/
function getMigrationTimestamp() external view override returns (uint256) {
return GlobalStorage.load().migrationInitiatedAt;
}
/**
* @notice Get migration proxy address
* @return The migration proxy contract address
*/
function getMigrationProxy() external view override returns (address) {
return GlobalStorage.load().migrationProxy;
}
/**
* @notice Check if migration has been initiated
* @return True if migration is initiated
*/
function isMigrationInitiated() external view override returns (bool) {
return GlobalStorage.load().migrationInitiatedAt != 0;
}
/**
* @notice Check if a vault has been migrated
* @param vaultId The vault ID to check
* @return True if the vault has been migrated
*/
function isVaultMigrated(uint256 vaultId) external view override returns (bool) {
return GlobalStorage.load().vaultMigrated[vaultId];
}
/**
* @notice Get migration delay
* @return The migration delay in seconds
*/
function getMigrationDelay() external view override returns (uint256) {
return GlobalStorage.load().migrationDelay;
}
}
"
},
"src/storage/GlobalStorage.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../libraries/Errors.sol";
/**
* @title GlobalStorage
* @notice Storage library for system-wide Genesis Vaults state
*/
library GlobalStorage {
enum PauseState {
NONE, // Normal operation
DEPOSITS_PAUSED, // Only deposits paused (allows withdrawals)
FROZEN // Complete freeze after migration
}
struct Data {
/// @notice Contract owner (multisig)
address owner;
/// @notice Pending ownership transfer
address pendingOwner;
/// @notice Next vault ID to assign
uint256 nextVaultId;
/// @notice Global pause state
PauseState pauseState;
/// @notice Migration contract address
mapping(uint256 => bool) vaultExists;
/// @notice Track strategies currently in use (prevents reuse across vaults)
mapping(address => bool) strategyInUse;
/// @notice Migration proxy contract address
address migrationProxy;
/// @notice Timestamp when migration was initiated
uint256 migrationInitiatedAt;
/// @notice Migration delay in seconds (defaults to 1 day)
uint256 migrationDelay;
/// @notice Withdrawal penalty in basis points (0-10000)
uint256 withdrawalPenalty;
/// @notice Withdrawal penalty recipient
address withdrawalPenaltyRecipient;
/// @notice Track which vaults have been migrated
mapping(uint256 => bool) vaultMigrated;
}
/**
* @notice Get the global storage struct
* @dev Uses deterministic storage slot calculation
* @return data The global storage data
*/
function load() internal pure returns (Data storage data) {
bytes32 slot = keccak256("genesis.global");
assembly {
data.slot := slot
}
}
// ===== VALIDATION FUNCTIONS =====
/**
* @notice Validate that caller is the owner
* @param caller The address attempting the action
*/
function validateOwner(Data storage data, address caller) internal view {
if (caller != data.owner) {
revert Errors.OnlyOwner(caller, data.owner);
}
}
/**
* @notice Validate that vault exists
* @param vaultId The vault ID to check
*/
function validateVaultExists(Data storage data, uint256 vaultId) internal view {
if (!data.vaultExists[vaultId]) {
revert Errors.VaultNotFound(vaultId);
}
}
/**
* @notice Validate that deposits are allowed
*/
function validateCanDeposit(Data storage data) internal view {
if (data.pauseState != PauseState.NONE) {
revert Errors.DepositsDisabled();
}
}
/**
* @notice Validate that withdrawals are allowed
*/
function validateCanWithdraw(Data storage data) internal view {
if (data.pauseState == PauseState.FROZEN) {
revert Errors.SystemFrozen();
}
}
/**
* @notice Validate that strategy is not already in use by another vault
* @param strategy The strategy address to check
*/
function validateStrategyAvailable(Data storage data, address strategy) internal view {
if (strategy != address(0) && data.strategyInUse[strategy]) {
revert Errors.StrategyAlreadyInUse(strategy);
}
}
// ===== CRUD OPERATIONS =====
/**
* @notice Initialize the system with initial owner
* @param initialOwner The address to set as initial owner
*/
function initialize(Data storage data, address initialOwner) internal {
if (initialOwner == address(0)) {
revert Errors.InvalidOwner(initialOwner);
}
if (data.owner != address(0)) {
revert Errors.AlreadyInitialized();
}
data.owner = initialOwner;
data.nextVaultId = 1;
data.pauseState = PauseState.NONE;
}
/**
* @notice Transfer ownership (initiate transfer)
* @param newOwner The address to transfer ownership to
*/
function transferOwnership(Data storage data, address newOwner) internal {
if (newOwner == address(0)) {
revert Errors.InvalidOwner(newOwner);
}
data.pendingOwner = newOwner;
}
/**
* @notice Accept pending ownership transfer
* @param caller The address accepting ownership
* @return oldOwner The previous owner address
*/
function acceptOwnership(
Data storage data,
address caller
) internal returns (address oldOwner) {
if (caller != data.pendingOwner) {
revert Errors.NotPendingOwner(caller, data.pendingOwner);
}
oldOwner = data.owner;
data.owner = caller;
delete data.pendingOwner;
}
/**
* @notice Create a new vault
* @return vaultId The new vault ID
*/
function createVault(Data storage data) internal returns (uint256 vaultId) {
vaultId = data.nextVaultId++;
data.vaultExists[vaultId] = true;
}
/**
* @notice Apply withdrawal penalty if set
* @param data The global storage data
* @param assets The gross withdrawal amount
* @return netAssets The amount after penalty deduction
* @return penalty The penalty amount collected
*/
function applyWithdrawPenalty(
Data storage data,
uint256 assets
) internal view returns (uint256 netAssets, uint256 penalty) {
if (data.withdrawalPenalty > 0) {
penalty = (assets * data.withdrawalPenalty) / 10000;
netAssets = assets - penalty;
} else {
penalty = 0;
netAssets = assets;
}
}
}
"
},
"src/storage/Vault.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../libraries/Errors.sol";
import "../interfaces/IGenesisStrategy.sol";
/**
* @title Vault
* @notice Storage library for individual vault state
*/
library Vault {
using SafeERC20 for IERC20;
struct Data {
/// @notice Vault ID
uint256 vaultId;
/// @notice Liquidity token for this vault
address liquidityToken;
/// @notice Current yield strategy contract
address strategy;
/// @notice Human readable vault name
string name;
/// @notice Total shares issued for this vault
uint256 totalShares;
/// @notice Minimum deposit amount (wei protection)
uint256 minDeposit;
/// @notice Minimum withdrawal amount (wei protection)
uint256 minWithdraw;
/// @notice Maximum total deposits allowed
uint256 maxTotalDeposits;
/// @notice Performance fee rate in basis points (0-10000)
uint256 performanceFeeRate;
/// @notice Address to receive performance fees
address feeRecipient;
/// @notice Minimum delay before withdrawal (seconds)
uint256 withdrawalDelay;
/// @notice User share balances
mapping(address => uint256) sharesOf;
/// @notice User last deposit timestamps
mapping(address => uint256) lastDepositTime;
/// @notice SY token address (address(0) if not SY-enabled)
address syToken;
/// @notice Idle liquidity held in factory custody when no strategy is set
uint256 idleLiquidity;
/// @notice Amount of liquidity migrated to preserve share calculations
uint256 migratedLiquidity;
/// @notice Track user's total deposits
mapping(address => uint256) userTotalDeposits;
/// @notice Track user's total withdrawals
mapping(address => uint256) userTotalWithdrawals;
}
/**
* @notice Get storage for specific vault ID
* @dev Uses deterministic storage slot per vault
* @param vaultId The vault identifier
* @return data The vault storage data
*/
function load(uint256 vaultId) internal pure returns (Data storage data) {
bytes32 slot = keccak256(abi.encode("genesis.vault", vaultId));
assembly {
data.slot := slot
}
}
// ===== VALIDATION FUNCTIONS =====
/**
* @notice Validate deposit parameters
* @param amount The deposit amount
* @param totalAssets Current total assets in vault
*/
function validateDeposit(
Data storage vault,
uint256 amount,
uint256 totalAssets
) internal view {
if (amount == 0) {
revert Errors.ZeroAmount();
}
if (amount < vault.minDeposit) {
revert Errors.BelowMinimumDeposit(vault.minDeposit, amount);
}
if (totalAssets + amount > vault.maxTotalDeposits) {
revert Errors.ExceedsCapacity(vault.maxTotalDeposits, totalAssets + amount);
}
}
/**
* @notice Validate withdrawal parameters
* @param user The user withdrawing
* @param shares The number of shares to withdraw
*/
function validateWithdraw(Data storage vault, address user, uint256 shares) internal view {
if (shares == 0) {
revert Errors.ZeroShares();
}
if (vault.sharesOf[user] < shares) {
revert Errors.InsufficientShares(vault.sharesOf[user], shares);
}
// Skip delay for registered SY wrappers; SY enforces per-user cooldown.
if (user != vault.syToken) {
// Check withdrawal delay
uint256 nextWithdrawalTime = vault.lastDepositTime[user] + vault.withdrawalDelay;
if (vault.withdrawalDelay > 0) {
if (nextWithdrawalTime > block.timestamp) {
revert Errors.WithdrawalDelayNotMet(nextWithdrawalTime, block.timestamp);
}
}
}
}
/**
* @notice Validate strategy parameters
* @param strategy The strategy address
*/
function validateStrategy(Data storage vault, address strategy) internal view {
if (strategy == address(0)) {
revert Errors.InvalidStrategy(strategy);
}
if (!IGenesisStrategy(strategy).supportsAsset(vault.liquidityToken)) {
revert Errors.UnsupportedAsset(vault.liquidityToken, strategy);
}
}
/**
* @notice Validate performance fee parameters
* @param vault The vault data
* @param rate The fee rate
* @param recipient The fee recipient
*/
function validatePerformanceFee(
Data storage vault,
uint256 rate,
address recipient
) internal view {
if (vault.strategy == address(0)) {
revert Errors.NoStrategy();
}
if (rate > 10000) {
revert Errors.RateTooHigh(rate, 10000);
}
if (recipient == address(0)) {
revert Errors.InvalidRecipient(recipient);
}
}
// ===== CRUD OPERATIONS =====
/**
* @notice Initialize vault with parameters
* @param vaultId The vault ID
* @param liquidityToken The liquidity token address
* @param name The vault name
* @param minDeposit The minimum deposit amount
* @param minWithdraw The minimum withdrawal amount
* @param maxTotalDeposits The maximum total deposits
* @param withdrawalDelay The minimum delay before withdrawal (seconds)
*/
function initializeVault(
uint256 vaultId,
address liquidityToken,
string memory name,
uint256 minDeposit,
uint256 minWithdraw,
uint256 maxTotalDeposits,
uint256 withdrawalDelay
) internal returns (Data storage vault) {
if (liquidityToken == address(0)) {
revert Errors.InvalidToken(liquidityToken);
}
vault = load(vaultId);
vault.vaultId = vaultId;
vault.liquidityToken = liquidityToken;
vault.totalShares = 0;
vault.performanceFeeRate = 0;
updateVaultConfiguration(
vault,
name,
minDeposit,
minWithdraw,
maxTotalDeposits,
withdrawalDelay
);
}
function updateVaultConfiguration(
Data storage vault,
string memory name,
uint256 minDeposit,
uint256 minWithdraw,
uint256 maxTotalDeposits,
uint256 withdrawalDelay
) internal {
if (bytes(name).length == 0) {
revert Errors.EmptyName();
}
if (maxTotalDeposits < minDeposit) {
revert Errors.InvalidLimits(minDeposit, maxTotalDeposits);
}
vault.name = name;
vault.minDeposit = minDeposit;
vault.minWithdraw = minWithdraw;
vault.maxTotalDeposits = maxTotalDeposits;
vault.withdrawalDelay = withdrawalDelay;
}
/**
* @notice Deposit assets and mint shares
* @param user The user depositing
* @param amount The amount deposited
* @param totalAssets Current total assets before deposit
* @return shares The number of shares minted
*/
function deposit(
Data storage vault,
address user,
uint256 amount,
uint256 totalAssets
) internal returns (uint256 shares) {
shares = convertToShares(vault, amount, totalAssets);
if (shares == 0) {
revert Errors.ZeroShares();
}
address strategy = vault.strategy;
if (strategy != address(0)) {
IERC20(vault.liquidityToken).forceApprove(strategy, amount);
IGenesisStrategy(strategy).deposit(amount);
} else {
// No strategy set, track idle liquidity under this vault
vault.idleLiquidity += amount;
}
// Update vault state
vault.sharesOf[user] += shares;
vault.totalShares += shares;
vault.userTotalDeposits[user] += amount;
vault.lastDepositTime[user] = block.timestamp;
}
/**
* @notice Withdraw shares and calculate assets
* @param vault The vault storage struct
* @param shares The number of shares to burn
* @param user The user withdrawing
* @param totalAssets Current total assets in vault
* @return assetsReceived The number of assets to withdraw
* @return remainingShares The number of shares remaining for the user after withdrawal
*/
function withdraw(
Data storage vault,
uint256 shares,
address user,
uint256 totalAssets
) internal returns (uint256 assetsReceived, uint256 remainingShares) {
// Calculate assets to withdraw
uint256 assets = convertToAssets(vault, shares, totalAssets);
if (assets == 0) {
revert Errors.ZeroAssets();
}
if (address(vault.strategy) == address(0)) {
assetsReceived = assets;
// Reduce idle liquidity since assets will be transferred out by caller
vault.idleLiquidity -= assetsReceived;
} else {
assetsReceived = IGenesisStrategy(vault.strategy).withdraw(assets);
}
remainingShares = vault.sharesOf[user] - shares;
// Update vault state
vault.sharesOf[user] = remainingShares;
vault.totalShares -= shares;
}
/**
* @notice Get total assets in vault
* @return uint256 total assets
*/
function getTotalAssets(Data storage vault) internal view returns (uint256) {
if (vault.migratedLiquidity > 0) {
return vault.migratedLiquidity;
}
if (address(vault.strategy) == address(0)) {
return vault.idleLiquidity;
}
return IGenesisStrategy(vault.strategy).totalAssets();
}
/**
* @notice Convert assets to shares
* @param assets The amount of assets
* @param totalAssets Current total assets
* @return shares The equivalent shares
*/
function convertToShares(
Data storage vault,
uint256 assets,
uint256 totalAssets
) internal view returns (uint256 shares) {
if (vault.totalShares == 0) {
return assets; // 1:1 for empty vault
}
if (totalAssets == 0) {
return assets;
}
return (assets * vault.totalShares) / totalAssets;
}
/**
* @notice Convert shares to assets
* @param shares The number of shares
* @param totalAssets Current total assets
* @return assets The equivalent assets
*/
function convertToAssets(
Data storage vault,
uint256 shares,
uint256 totalAssets
) internal view returns (uint256 assets) {
if (vault.totalShares == 0) {
return 0;
}
return (shares * totalAssets) / vault.totalShares;
}
/**
* @notice Get user share balance
* @param vaultId The vault ID
* @param user The user address
* @return The user's share balance
*/
function getShares(uint256 vaultId, address user) internal view returns (uint256) {
Data storage vault = load(vaultId);
return vault.sharesOf[user];
}
/**
* @notice Set vault strategy
* @param newStrategy The new strategy address
* @return oldStrategy The previous strategy address
*/
function switchStrategy(
Data storage vault,
address newStrategy
) internal returns (address oldStrategy, uint256 yield, uint256 feeCollected) {
oldStrategy = vault.strategy;
if (oldStrategy != address(0)) {
// Withdraw all from old strategy and collect fees
(oldStrategy, yield, feeCollected) = unsetStrategy(vault);
}
// if unsetStrategy was called, idleLiquidity gets set to whatever was returned from the strategy
uint256 totalAssets = vault.idleLiquidity;
// Deposit all assets to new strategy
if (totalAssets > 0) {
IERC20(vault.liquidityToken).forceApprove(newStrategy, totalAssets);
uint256 deposited = IGenesisStrategy(newStrategy).deposit(totalAssets);
vault.idleLiquidity -= deposited;
}
// Set new strategy
vault.strategy = newStrategy;
}
/**
* @notice Unset strategy and reset to no-strategy mode
* @param vault The vault data storage
* @return oldStrategy The previous strategy address
* @return returnedYield The net yield after performance fees
* @return feeCollected The performance fee collected
*/
function unsetStrategy(
Data storage vault
) internal returns (address oldStrategy, uint256 returnedYield, uint256 feeCollected) {
// Only proceed if there's a strategy set
if (vault.strategy == address(0)) {
return (address(0), 0, 0);
}
// Withdraw all assets from current strategy and collect yield/fees
(uint256 returnedAssets, uint256 grossYield) = IGenesisStrategy(vault.strategy)
.withdrawAll();
if (grossYield > 0) {
(feeCollected, returnedYield) = collectPerformanceFee(vault, grossYield);
returnedAssets += returnedYield;
}
// Assets are now held directly by the vault (no strategy)
// Track returned assets as idle liquidity for this vault
if (returnedAssets > 0) {
vault.idleLiquidity += returnedAssets;
}
// Store old strategy for event emission
oldStrategy = vault.strategy;
// Reset strategy-related fields to defaults
vault.strategy = address(0);
vault.performanceFeeRate = 0;
vault.feeRecipient = address(0);
}
/**
* @notice Set performance fee configuration
* @param vault The vault data storage
* @param rate The fee rate in basis points
* @param recipient The fee recipient address
*/
function setPerformanceFee(Data storage vault, uint256 rate, address recipient) internal {
vault.performanceFeeRate = rate;
vault.feeRecipient = recipient;
}
/**
* @notice Get performance fee configuration
* @param vaultId The vault ID
* @return rate The fee rate
* @return recipient The fee recipient
*/
function getPerformanceFee(
uint256 vaultId
) internal view returns (uint256 rate, address recipient) {
Data storage vault = load(vaultId);
return (vault.performanceFeeRate, vault.feeRecipient);
}
/**
* @notice Collect performance fee
* @param vault The vault data
* @param yield The yield to collect fee from
* @return feeCollected The amount of fee collected
* @return returnedYield The amount of yield returned
*/
function collectPerformanceFee(
Data storage vault,
uint256 yield
) internal returns (uint256 feeCollected, uint256 returnedYield) {
uint256 rate = vault.performanceFeeRate;
address recipient = vault.feeRecipient;
if (recipient == address(0) || rate == 0 || yield == 0) {
return (0, yield);
}
uint256 fee = (yield * rate) / 10000;
feeCollected = fee;
returnedYield = yield - fee;
IERC20(vault.liquidityToken).safeTransfer(recipient, fee);
}
function hasPerformanceFeeConfigured(Data storage vault) internal view returns (bool) {
return vault.performanceFeeRate > 0 && vault.feeRecipient != address(0);
}
function harvestAndCompound(
Data storage vault
) internal returns (uint256 claimedYield, uint256 feeCollected) {
// Return early if no strategy is set or no performance fee is configured
if (vault.strategy == address(0) || !hasPerformanceFeeConfigured(vault)) {
return (0, 0);
}
claimedYield = IGenesisStrategy(vault.strategy).claimRewards();
uint256 returnedYield;
(feeCollected, returnedYield) = collectPerformanceFee(vault, claimedYield);
if (returnedYield > 0) {
IERC20(vault.liquidityToken).forceApprove(vault.strategy, returnedYield);
IGenesisStrategy(vault.strategy).deposit(returnedYield);
}
}
}
"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"evmVersion": "shanghai",
"remappings": [
"@aave/core-v3/=node_modules/@aave/core-v3/",
"@aave/periphery-v3/=node_modules/@aave/periphery-v3/",
"@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/",
"@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/",
"Pendle-SY-Public/=lib/Pendle-SY-Public/contracts/",
"cannon-std/=lib/cannon-std/src/",
"ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/",
"erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
"forge-std/=lib/forge-std/src/",
"openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"openzeppelin/=lib/openzeppelin-contracts-upgradeable/contracts/"
],
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}
}}
Submitted on: 2025-10-10 19:35:26
Comments
Log in to comment.
No comments yet.