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": {
"src/RolesReceiver.sol": {
"content": "// SPDX-License-Identifier: BSL-1.1
pragma solidity ^0.8.24;
import {OApp, Origin, MessagingFee, MessagingReceipt} from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";
import {VaultConfig} from "./types/StrategyTypes.sol";
import {StrategyManager} from "./libraries/StrategyManager.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title RolesReceiver
* @author Variable Logic Labs, Corp (hello@blend.money)
*
* @notice A LayerZero OApp that receives role update messages from a trusted broadcaster and executes them on a Zodiac role modifier
* @dev This contract acts as a bridge between LayerZero cross-chain messaging and Zodiac role management.
* It validates incoming messages from a trusted source chain and broadcaster before executing role updates
* on a Zodiac role modifier contract. The contract inherits from OApp for LayerZero functionality,
* Ownable for access control, and StrategyManager for strategy configuration management.
*
* @notice Security Model: Owner/Delegate Privilege Mitigation
* @dev This contract's security relies on restricting the `delegate`'s privileges. While several functions are
* overridden to prevent misuse by the `delegate` (e.g., `setPeer`, `_lzSend`), a significant trust assumption remains.
* The `delegate` of this OApp can call `setDefaultReceiveLibrary()` on the LayerZero endpoint, potentially
* installing a malicious library that could bypass this contract's validation logic in `_lzReceive` and spoof
* messages from the trusted broadcaster and chain.
*
* To mitigate this, the `delegate` address (set as `_owner` in the constructor) MUST be a Timelock contract
* or a secure multi-sig wallet. This ensures that any change to the endpoint configuration is subject to a
* time delay and/or a multi-party approval process, providing an opportunity to detect and prevent malicious actions.
*
* Hardened security measures in this contract include:
* - `setPeer()` always reverts - prevents changing trusted peers after deployment.
* - `_lzReceive()` only accepts messages from `TRUSTED_BROADCASTER` on `TRUSTED_CHAIN_ID`.
* - `_lzSend()` is overridden to revert - prevents sending messages.
*/
contract RolesReceiver is OApp, StrategyManager {
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
/// @notice Keccak256 selector for updateStrategyConfig
/// @dev precomputed at compile time to avoid repeated keccak256 at runtime
bytes4 private constant _UPDATE_VAULT_CONFIG_SELECTOR =
bytes4(keccak256("updateVaultConfig(address,(address,(bytes32,address,uint256,bool,bytes)[],(address)[]))"));
/// @notice Keccak256 selector for setExecutor
/// @dev precomputed at compile time to avoid repeated keccak256 at runtime
bytes4 private constant _SET_EXECUTOR_SELECTOR = bytes4(keccak256("setExecutor(address)"));
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
/// @notice The bytes32 representation of the trusted broadcaster that can send role update messages
/// @dev This bytes32 value must match the sender of incoming LayerZero messages for validation
bytes32 public immutable TRUSTED_BROADCASTER;
/// @notice The LayerZero endpoint ID of the trusted source chain
/// @dev Messages from other chains will be rejected during validation
uint32 public immutable TRUSTED_CHAIN_ID;
/// @notice The address of the trusted broadcaster on the same chain for direct calls
/// @dev Used to validate same-chain direct calls from RolesBroadcaster
address public immutable TRUSTED_BROADCASTER_ADDRESS;
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @notice Thrown when a message is received from an untrusted source chain
/// @dev Occurs when origin.srcEid doesn't match trustedChainId
error InvalidSourceChain();
/// @notice Thrown when a message is received from an untrusted broadcaster
/// @dev Occurs when the sender address doesn't match trustedBroadcaster
error InvalidBroadcaster();
/// @notice Thrown when a message is received that is not a valid selector
/// @dev Occurs when the selector is not a valid selector
error InvalidSelector();
/// @notice Thrown when a message is received that is formed incorrectly
/// @dev Occurs when the message is not formed correctly
error InvalidMessagePayload();
/// @notice Thrown when a message is received that is not supported
/// @dev Occurs when the selector is not a valid selector
error NOT_SUPPORTED();
/// @notice Thrown when same-chain call is made by unauthorized caller
/// @dev Occurs when processSameChainCall is called by non-trusted broadcaster
error UnauthorizedSameChainCall();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/**
* @notice Emitted when a LayerZero message is successfully received and processed
* @param messageId The unique identifier of the received message
* @param message The raw message data that was processed
*/
event MessageReceived(bytes32 indexed messageId, bytes message);
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/**
* @notice Initializes the RolesReceiver contract with LayerZero and trusted source configuration
* @param _lzEndpoint The address of the LayerZero endpoint for this chain
* @param _owner The address that will own this contract and also be set as the OApp delegate
* @param _trustedBroadcaster The address of the trusted broadcaster that can send role updates
* @param _trustedChainId The LayerZero endpoint ID of the trusted source chain
* @param _minSecondsBetweenOperations The minimum time in seconds that must elapse between operations on the same safe
* @dev The constructor sets up the OApp with the LayerZero endpoint and establishes the trusted source parameters.
* It is critical that the `_owner` is a Timelock or a secure multi-sig wallet to mitigate risks associated
* with the OApp delegate role. See the contract-level security notice for more details.
*/
constructor(
address _lzEndpoint,
address _owner,
address _trustedBroadcaster,
uint32 _trustedChainId,
uint256 _minSecondsBetweenOperations
) OApp(_lzEndpoint, _owner) Ownable(_owner) StrategyManager(_minSecondsBetweenOperations) {
TRUSTED_BROADCASTER = bytes32(uint256(uint160(_trustedBroadcaster)));
TRUSTED_BROADCASTER_ADDRESS = _trustedBroadcaster;
TRUSTED_CHAIN_ID = _trustedChainId;
_setPeer(_trustedChainId, TRUSTED_BROADCASTER);
}
/*//////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Reverts when a peer is set
* @dev This function is overridden to prevent setting peers
*/
function setPeer(uint32, bytes32) public pure override {
revert NOT_SUPPORTED();
}
/**
* @notice Reverts when a message is sent
* @dev This function is overridden to prevent sending messages
*/
function _lzSend(uint32, bytes memory, bytes memory, MessagingFee memory, address)
internal
virtual
override
returns (MessagingReceipt memory)
{
revert NOT_SUPPORTED();
}
/**
* @notice Processes a same-chain role update call directly from the trusted broadcaster
* @param message The encoded message data containing the role update call
* @dev This function allows the trusted broadcaster on the same chain to directly call
* role update functions without going through LayerZero messaging
* @dev Only the trusted broadcaster address can call this function
* @dev Uses the same validation and processing logic as _lzReceive but for same-chain calls
*/
function processSameChainCall(bytes calldata message) external {
// Validate that the caller is the trusted broadcaster on the same chain
require(msg.sender == TRUSTED_BROADCASTER_ADDRESS, UnauthorizedSameChainCall());
// Process the message using the same logic as _lzReceive
_processRoleUpdateMessage(message, bytes32(0)); // Use empty guid for same-chain calls
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Internal function called by LayerZero when a message is received
* @param origin The origin information including source chain ID and sender address
* @param guid The globally unique identifier for this message
* @param message The encoded message data containing the role update call for the Zodiac role modifier
* @dev This function validates the source chain and broadcaster, then executes the role update on the Zodiac role modifier.
* The message should contain the encoded function call data for the role modifier contract (e.g., assignRoles, revokeRoles).
* The function is protected by whenNotPaused modifier to allow emergency stops.
* @dev Reverts with InvalidSourceChain if the message doesn't come from the trusted chain
* @dev Reverts with InvalidBroadcaster if the sender is not the trusted broadcaster
* @dev Reverts with InvalidSelector if the selector is not a valid selector
* @custom:security Only the trusted broadcaster can send messages to this contract
*/
function _lzReceive(
Origin calldata origin,
bytes32 guid,
bytes calldata message,
address, // executor - unused
bytes calldata // extraData - unused
) internal override {
// Validate source chain
require(origin.srcEid == TRUSTED_CHAIN_ID, InvalidSourceChain());
// Validate broadcaster
require(origin.sender == TRUSTED_BROADCASTER, InvalidBroadcaster());
// Process the message using shared logic
_processRoleUpdateMessage(message, guid);
}
/**
* @notice Internal function to process role update messages from both LayerZero and same-chain calls
* @param message The encoded message data containing the role update call
* @param guid The globally unique identifier for this message (empty for same-chain calls)
* @dev This function contains the shared logic for processing role update messages regardless of source
* @dev Reverts with InvalidSelector if the selector is not supported
* @dev Reverts with InvalidMessagePayload if the message format is incorrect
*/
function _processRoleUpdateMessage(bytes calldata message, bytes32 guid) internal {
bytes4 selector = bytes4(message);
if (selector == _UPDATE_VAULT_CONFIG_SELECTOR) {
// Decode the market configurations update
(address vaultAddress, VaultConfig memory configs) = abi.decode(message[4:], (address, VaultConfig));
// Update strategy with market configs
_updateVaultConfig(vaultAddress, configs);
} else if (selector == _SET_EXECUTOR_SELECTOR) {
// There must be exactly 1 address (20 bytes, padded out to 32 due to function encoding) + 4 bytes
require(message.length == 36, InvalidMessagePayload());
// Decode the executor address
(address executor) = abi.decode(message[4:], (address));
// Set the executor
_setExecutor(executor);
} else {
// We don't support any other selectors
revert InvalidSelector();
}
emit MessageReceived(guid, message);
}
}
"
},
"lib/devtools/packages/oapp-evm/contracts/oapp/OApp.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementers
// solhint-disable-next-line no-unused-import
import { OAppSender, MessagingFee, MessagingReceipt } from "./OAppSender.sol";
// @dev Import the 'Origin' so it's exposed to OApp implementers
// solhint-disable-next-line no-unused-import
import { OAppReceiver, Origin } from "./OAppReceiver.sol";
import { OAppCore } from "./OAppCore.sol";
/**
* @title OApp
* @dev Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality.
*/
abstract contract OApp is OAppSender, OAppReceiver {
/**
* @dev Constructor to initialize the OApp with the provided endpoint and owner.
* @param _endpoint The address of the LOCAL LayerZero endpoint.
* @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
*/
constructor(address _endpoint, address _delegate) OAppCore(_endpoint, _delegate) {}
/**
* @notice Retrieves the OApp version information.
* @return senderVersion The version of the OAppSender.sol implementation.
* @return receiverVersion The version of the OAppReceiver.sol implementation.
*/
function oAppVersion()
public
pure
virtual
override(OAppSender, OAppReceiver)
returns (uint64 senderVersion, uint64 receiverVersion)
{
return (SENDER_VERSION, RECEIVER_VERSION);
}
}
"
},
"src/types/StrategyTypes.sol": {
"content": "// SPDX-License-Identifier: BSL-1.1
pragma solidity ^0.8.24;
import {IVaultController} from "../interfaces/controllers/IVaultController.sol";
import {IMarketAdapterController} from "../interfaces/controllers/IMarketAdapterController.sol";
import {IVaultActionController} from "../interfaces/controllers/IVaultActionController.sol";
/// @notice Configuration parameters for a specific market within a strategy
/// @dev Contains market identifier, adapter contract, and leverage settings
struct MarketConfig {
/// @notice Unique identifier for the market
/// @dev Usually a hash of market parameters like tokens, oracles etc
bytes32 marketId;
/// @notice Controller contract for interacting with the market adapter
/// @dev Handles protocol-specific market interactions and position management
IMarketAdapterController adapter;
/// @notice Target leverage ratio for positions in this market
/// @dev Expressed as a fixed-point number where 1e18 = 1x leverage
uint256 leverage;
/// @notice Boolean indicating if the market is a wrapper
/// @dev If true, the market is a wrapper and the adapter will use the underlying asset
bool isWrapped;
/// @notice Specific bytecode to be deconstructed
bytes data;
}
struct ActionConfig {
/// @notice Controller contract for interacting with the action
IVaultActionController controller;
/// @notice Specific bytecode to be deconstructed
bytes data;
}
/// @notice Configuration parameters for a specific vault within a strategy
/// @dev Contains control contract and series of market configs
struct VaultConfig {
/// @notice Control contract for the lending protocol
IVaultController control;
/// @notice Array of adapters that is designed to be run inside of a flashloan context
MarketConfig[] markets;
/// @notice Set of hooks that are designed to make targeted changes to user positions
ActionConfig[] actions;
}
"
},
"src/libraries/StrategyManager.sol": {
"content": "// SPDX-License-Identifier: BSL-1.1
pragma solidity ^0.8.24;
import {VaultConfig} from "../types/StrategyTypes.sol";
import {RebalanceData} from "../types/RebalanceTypes.sol";
import {IVaultActionController} from "../interfaces/controllers/IVaultActionController.sol";
import {IVaultController} from "../interfaces/controllers/IVaultController.sol";
import {IModuleManager} from "safe-contracts/interfaces/IModuleManager.sol";
import {Enum} from "safe-contracts/libraries/Enum.sol";
/**
* @title StrategyManager
* @author Variable Logic Labs, Corp (hello@blend.money)
* @notice Abstract contract responsible for managing strategy configurations
* @dev This contract provides storage and management functions for strategy configurations,
* separating the concern of strategy management from other contract responsibilities.
* Security: Reentrancy protection is provided by the onlyExecutor modifier and per-vault rate limiting.
*/
abstract contract StrategyManager {
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
/// @notice Mapping to store market configurations for each vault
/// @dev Maps VaultId to their corresponding array of MarketConfigs
mapping(address => VaultConfig) public strategyConfig;
/// @notice A mapping of (safe, vault) pairs to the timestamp of their last completed operation
/// @dev Key is keccak256(abi.encodePacked(safe, vault)). Used to enforce per-vault rate limiting and prevent rapid-fire operations
mapping(bytes32 => uint256) public lastOperationTimestamp;
/// @notice Boolean flag that the `executeRebalance` function has been entered
/// @dev In practice, this will only be set to true if inside the `executeRebalance` function
bool public isRebalanceInitiated;
/// @notice Boolean flag that the `executeVaultAction` function has been entered
/// @dev In practice, this will only be set to true if inside the `executeVaultAction` function
bool public isVaultActionInitiated;
/// @notice This is the address that is able to execute transactions on the strategy manager
address public executor;
/// @notice The minimum time between operations in seconds
uint256 public immutable MIN_SECONDS_BETWEEN_OPERATIONS;
/// @notice Storage slot to avoid collisions for temporary seen items mapping
bytes32 private constant TEMP_SEEN_SLOT = keccak256("blend.temp.seen.items");
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/// @notice Emitted when the executor is set
/// @param executor The address of the executor
event ExecutorSet(address indexed executor);
/// @notice Emitted when a rebalance is executed
/// @param safe The address of the safe that executed the rebalance
/// @param vault The address of the vault that was rebalanced
event RebalanceExecuted(address indexed safe, address indexed vault);
/// @notice Emitted when a vault action is executed
/// @param safe The address of the safe that executed the vault action
/// @param vault The address of the vault that was acted on
event VaultActionExecuted(address indexed safe, address indexed vault);
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @notice Thrown when the caller is not the executor
error NotExecutor();
/// @notice Thrown when the vault is not valid
error InvalidVault();
/// @notice Thrown when the VaultConfig is invalid
error InvalidVaultConfig();
/// @notice Thrown when the safe throws an error
/// @dev This is a generic error that occurs when a safe `execTransactionFromModule` fails
error SafeExecutionError();
/// @notice Thrown when the specified action controller is not approved for the given vault.
error InvalidActionController();
/// @notice Thrown when the `execBalance` function has already been entered
error AlreadyInitiated();
/// @notice Thrown when an operation is performed too frequently
error RateLimited();
/// @notice Thrown when the minimum seconds between operations specified in constructor is zero
error InvalidMinSecondsBetweenOperations();
/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
/// @notice Modifier to ensure the caller is the executor
modifier onlyExecutor() {
require(msg.sender == executor, NotExecutor());
_;
}
/// @notice Modifier to ensure the vault is valid
modifier validVault(address vault) {
require(address(strategyConfig[vault].control) != address(0), InvalidVault());
_;
}
/**
* @notice Ensures that the action controller is valid and approved for the specified vault.
* @dev It checks that the vault is valid and then verifies that the `actionController`
* is present in the list of approved action controllers for that vault.
* @param vault The address of the vault.
* @param actionController The action controller to validate.
*/
modifier validActionController(address vault, IVaultActionController actionController) {
require(vault != address(0), InvalidVault());
VaultConfig memory vaultConfig = strategyConfig[vault];
require(address(vaultConfig.control) != address(0), InvalidVault());
// Check if the action controller is one of the allowed action controllers
bool found = false;
for (uint256 i = 0; i < vaultConfig.actions.length; ++i) {
if (vaultConfig.actions[i].controller == actionController) {
found = true;
break;
}
}
require(found, InvalidActionController());
_;
}
/// @notice Modifier to ensure the `executeRebalance` function has not been entered
modifier initiateRebalance() {
require(!isRebalanceInitiated, AlreadyInitiated());
isRebalanceInitiated = true;
_;
isRebalanceInitiated = false;
}
/// @notice Modifier to ensure the `executeVaultAction` function has not been entered
modifier initiateVaultAction() {
require(!isVaultActionInitiated, AlreadyInitiated());
isVaultActionInitiated = true;
_;
isVaultActionInitiated = false;
}
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/**
* @notice Constructor for the StrategyManager
* @param _minSecondsBetweenOperations The minimum time in seconds that must elapse between operations on the same safe
* @dev Reverts if _minSecondsBetweenOperations is zero or greater than 300 seconds
*/
constructor(uint256 _minSecondsBetweenOperations) {
require(
_minSecondsBetweenOperations > 0 && _minSecondsBetweenOperations <= 300,
InvalidMinSecondsBetweenOperations()
);
MIN_SECONDS_BETWEEN_OPERATIONS = _minSecondsBetweenOperations;
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Gets the temporary seen items mapping from a specific storage slot
* @return m The mapping at the designated storage slot
* @dev Uses assembly to access a specific storage slot to avoid collisions
*/
function _tempSeen() internal pure returns (mapping(bytes32 => bool) storage m) {
bytes32 slot = TEMP_SEEN_SLOT;
assembly {
m.slot := slot
}
}
/**
* @notice Clears the temporary seen items mapping
* @param config The vault configuration containing the items to clear
* @dev Deletes entries from the temporary mapping to avoid storage bloat
*/
function _clearTempSeen(VaultConfig memory config) internal {
mapping(bytes32 => bool) storage seen = _tempSeen();
for (uint256 i = 0; i < config.markets.length; i++) {
delete seen[config.markets[i].marketId];
}
for (uint256 i = 0; i < config.actions.length; i++) {
delete seen[bytes32(uint256(uint160(address(config.actions[i].controller))))];
}
}
/**
* @notice Internal function to set the executor
* @param _executor The address of the executor
* @dev *BE CAREFUL* this function gives the executor the ability to execute txns on behalf of user safes
*/
function _setExecutor(address _executor) internal {
executor = _executor;
emit ExecutorSet(_executor);
}
/**
* @notice Internal function to update a vault configuration
* @param vaultId The vault identifier to update the vault config for
* @param newConfig The vault configuration to set
* @dev This function replaces the entire vault config for a vault.
* It checks for duplicate market IDs, lower bound leverage, non-zero market controller,
* duplicate action controllers, and non-zero action controller.
* It then updates the vault config.
*/
function _updateVaultConfig(address vaultId, VaultConfig memory newConfig) internal {
require(address(newConfig.control) != address(0), InvalidVaultConfig());
mapping(bytes32 => bool) storage seen = _tempSeen();
// Check for duplicate market IDs using O(n) approach
for (uint256 i = 0; i < newConfig.markets.length; i++) {
bytes32 marketId = newConfig.markets[i].marketId;
require(!seen[marketId], InvalidVaultConfig());
seen[marketId] = true;
// Check for lower bound leverage
require(newConfig.markets[i].leverage >= 1e18, InvalidVaultConfig());
// Check to ensure that the market address is not zero
require(address(newConfig.markets[i].adapter) != address(0), InvalidVaultConfig());
}
// Check for duplicate action controllers using O(n) approach
for (uint256 i = 0; i < newConfig.actions.length; i++) {
bytes32 actionKey = bytes32(uint256(uint160(address(newConfig.actions[i].controller))));
require(!seen[actionKey], InvalidVaultConfig());
seen[actionKey] = true;
// Check to ensure that the action controller is not zero
require(address(newConfig.actions[i].controller) != address(0), InvalidVaultConfig());
}
// Clean up temporary storage to avoid storage bloat
_clearTempSeen(newConfig);
// Update the vault config
strategyConfig[vaultId] = newConfig;
}
/**
* @notice Internal function to verify the rate limit for a safe-vault pair
* @param safe The address of the safe
* @param vault The address of the vault
* @dev This function checks if the minimum seconds between operations has elapsed since the last operation on this safe-vault pair
* @custom:reverts RateLimited if insufficient time has passed since the last operation on this safe-vault pair
*/
function _verifyRateLimit(address safe, address vault) internal {
bytes32 safeVaultKey = keccak256(abi.encodePacked(safe, vault));
require(block.timestamp - lastOperationTimestamp[safeVaultKey] > MIN_SECONDS_BETWEEN_OPERATIONS, RateLimited());
lastOperationTimestamp[safeVaultKey] = block.timestamp;
}
/*//////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Executes a rebalance on a vault
* @param safe The address of the safe that will execute the rebalance
* @param vault The address of the vault that will be rebalanced
* @param rebalanceData The data for the rebalance
* @dev Enforces rate limiting per (safe, vault) pair by checking that MIN_SECONDS_BETWEEN_OPERATIONS has elapsed since the last operation
* @custom:reverts RateLimited if insufficient time has passed since the last operation on this safe-vault pair
* @custom:reverts SafeExecutionError if the safe's execTransactionFromModule call fails
*/
function executeRebalance(address safe, address vault, RebalanceData[] calldata rebalanceData)
public
onlyExecutor
validVault(vault)
initiateRebalance
{
_verifyRateLimit(safe, vault);
bool success = IModuleManager(safe).execTransactionFromModule(
address(strategyConfig[vault].control),
0,
abi.encodeWithSelector(IVaultController.executeRebalance.selector, vault, rebalanceData),
Enum.Operation.DelegateCall
);
require(success, SafeExecutionError());
emit RebalanceExecuted(safe, vault);
}
/**
* @notice Executes a generic vault action through a designated controller.
* @dev This function enables the executor to trigger a vault action on behalf of a user's Gnosis Safe.
* It constructs a call to the safe's `execTransactionFromModule`, targeting the specified
* `actionController`. This ensures actions are executed within the safe's context.
* The function is protected by modifiers to ensure the caller is the executor, the vault is valid,
* and the action controller is approved for the given vault.
* Enforces rate limiting per (safe, vault) pair by checking that MIN_SECONDS_BETWEEN_OPERATIONS has elapsed since the last operation.
* @param safe The address of the user's Gnosis Safe that will execute the transaction.
* @param vault The address of the vault being acted upon. Used for validation purposes.
* @param actionController The controller contract responsible for executing the specific action.
* @param data The ABI-encoded calldata for the action to be executed by the `actionController`.
* @custom:reverts RateLimited if insufficient time has passed since the last operation on this safe-vault pair
* @custom:reverts SafeExecutionError if the safe's execTransactionFromModule call fails
*/
function executeVaultAction(
address safe,
address vault,
IVaultActionController actionController,
bytes calldata data
) external onlyExecutor validVault(vault) validActionController(vault, actionController) initiateVaultAction {
_verifyRateLimit(safe, vault);
VaultConfig memory vaultConfig = strategyConfig[vault];
bytes memory strategyData;
for (uint256 i = 0; i < vaultConfig.actions.length; i++) {
if (vaultConfig.actions[i].controller == actionController) {
strategyData = vaultConfig.actions[i].data;
break;
}
}
bool success = IModuleManager(safe).execTransactionFromModule(
address(actionController),
0,
abi.encodeWithSelector(IVaultActionController.executeAction.selector, vault, strategyData, data),
Enum.Operation.DelegateCall
);
require(success, SafeExecutionError());
emit VaultActionExecuted(safe, vault);
}
/**
* @notice Returns the vault configuration for a given vault
* @param vault The address of the vault to get the vault configuration for
* @return The vault configuration for the given vault
*/
function getVaultConfig(address vault) public view returns (VaultConfig memory) {
return strategyConfig[vault];
}
}
"
},
"lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"lib/devtools/packages/oapp-evm/contracts/oapp/OAppSender.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { MessagingParams, MessagingFee, MessagingReceipt } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
import { OAppCore } from "./OAppCore.sol";
/**
* @title OAppSender
* @dev Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.
*/
abstract contract OAppSender is OAppCore {
using SafeERC20 for IERC20;
// Custom error messages
error NotEnoughNative(uint256 msgValue);
error LzTokenUnavailable();
// @dev The version of the OAppSender implementation.
// @dev Version is bumped when changes are made to this contract.
uint64 internal constant SENDER_VERSION = 1;
/**
* @notice Retrieves the OApp version information.
* @return senderVersion The version of the OAppSender.sol contract.
* @return receiverVersion The version of the OAppReceiver.sol contract.
*
* @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.
* ie. this is a SEND only OApp.
* @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions
*/
function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {
return (SENDER_VERSION, 0);
}
/**
* @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.
* @param _dstEid The destination endpoint ID.
* @param _message The message payload.
* @param _options Additional options for the message.
* @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.
* @return fee The calculated MessagingFee for the message.
* - nativeFee: The native fee for the message.
* - lzTokenFee: The LZ token fee for the message.
*/
function _quote(
uint32 _dstEid,
bytes memory _message,
bytes memory _options,
bool _payInLzToken
) internal view virtual returns (MessagingFee memory fee) {
return
endpoint.quote(
MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),
address(this)
);
}
/**
* @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.
* @param _dstEid The destination endpoint ID.
* @param _message The message payload.
* @param _options Additional options for the message.
* @param _fee The calculated LayerZero fee for the message.
* - nativeFee: The native fee.
* - lzTokenFee: The lzToken fee.
* @param _refundAddress The address to receive any excess fee values sent to the endpoint.
* @return receipt The receipt for the sent message.
* - guid: The unique identifier for the sent message.
* - nonce: The nonce of the sent message.
* - fee: The LayerZero fee incurred for the message.
*/
function _lzSend(
uint32 _dstEid,
bytes memory _message,
bytes memory _options,
MessagingFee memory _fee,
address _refundAddress
) internal virtual returns (MessagingReceipt memory receipt) {
// @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.
uint256 messageValue = _payNative(_fee.nativeFee);
if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);
return
// solhint-disable-next-line check-send-result
endpoint.send{ value: messageValue }(
MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),
_refundAddress
);
}
/**
* @dev Internal function to pay the native fee associated with the message.
* @param _nativeFee The native fee to be paid.
* @return nativeFee The amount of native currency paid.
*
* @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,
* this will need to be overridden because msg.value would contain multiple lzFees.
* @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.
* @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.
* @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.
*/
function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {
if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);
return _nativeFee;
}
/**
* @dev Internal function to pay the LZ token fee associated with the message.
* @param _lzTokenFee The LZ token fee to be paid.
*
* @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.
* @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().
*/
function _payLzToken(uint256 _lzTokenFee) internal virtual {
// @dev Cannot cache the token because it is not immutable in the endpoint.
address lzToken = endpoint.lzToken();
if (lzToken == address(0)) revert LzTokenUnavailable();
// Pay LZ token fee by sending tokens to the endpoint.
IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);
}
}
"
},
"lib/devtools/packages/oapp-evm/contracts/oapp/OAppReceiver.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { IOAppReceiver, Origin } from "./interfaces/IOAppReceiver.sol";
import { OAppCore } from "./OAppCore.sol";
/**
* @title OAppReceiver
* @dev Abstract contract implementing the ILayerZeroReceiver interface and extending OAppCore for OApp receivers.
*/
abstract contract OAppReceiver is IOAppReceiver, OAppCore {
// Custom error message for when the caller is not the registered endpoint/
error OnlyEndpoint(address addr);
// @dev The version of the OAppReceiver implementation.
// @dev Version is bumped when changes are made to this contract.
uint64 internal constant RECEIVER_VERSION = 2;
/**
* @notice Retrieves the OApp version information.
* @return senderVersion The version of the OAppSender.sol contract.
* @return receiverVersion The version of the OAppReceiver.sol contract.
*
* @dev Providing 0 as the default for OAppSender version. Indicates that the OAppSender is not implemented.
* ie. this is a RECEIVE only OApp.
* @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions.
*/
function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {
return (0, RECEIVER_VERSION);
}
/**
* @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.
* @dev _origin The origin information containing the source endpoint and sender address.
* - srcEid: The source chain endpoint ID.
* - sender: The sender address on the src chain.
* - nonce: The nonce of the message.
* @dev _message The lzReceive payload.
* @param _sender The sender address.
* @return isSender Is a valid sender.
*
* @dev Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.
* @dev The default sender IS the OAppReceiver implementer.
*/
function isComposeMsgSender(
Origin calldata /*_origin*/,
bytes calldata /*_message*/,
address _sender
) public view virtual returns (bool) {
return _sender == address(this);
}
/**
* @notice Checks if the path initialization is allowed based on the provided origin.
* @param origin The origin information containing the source endpoint and sender address.
* @return Whether the path has been initialized.
*
* @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.
* @dev This defaults to assuming if a peer has been set, its initialized.
* Can be overridden by the OApp if there is other logic to determine this.
*/
function allowInitializePath(Origin calldata origin) public view virtual returns (bool) {
return peers[origin.srcEid] == origin.sender;
}
/**
* @notice Retrieves the next nonce for a given source endpoint and sender address.
* @dev _srcEid The source endpoint ID.
* @dev _sender The sender address.
* @return nonce The next nonce.
*
* @dev The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.
* @dev Is required by the off-chain executor to determine the OApp expects msg execution is ordered.
* @dev This is also enforced by the OApp.
* @dev By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.
*/
function nextNonce(uint32 /*_srcEid*/, bytes32 /*_sender*/) public view virtual returns (uint64 nonce) {
return 0;
}
/**
* @dev Entry point for receiving messages or packets from the endpoint.
* @param _origin The origin information containing the source endpoint and sender address.
* - srcEid: The source chain endpoint ID.
* - sender: The sender address on the src chain.
* - nonce: The nonce of the message.
* @param _guid The unique identifier for the received LayerZero message.
* @param _message The payload of the received message.
* @param _executor The address of the executor for the received message.
* @param _extraData Additional arbitrary data provided by the corresponding executor.
*
* @dev Entry point for receiving msg/packet from the LayerZero endpoint.
*/
function lzReceive(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) public payable virtual {
// Ensures that only the endpoint can attempt to lzReceive() messages to this OApp.
if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender);
// Ensure that the sender matches the expected peer for the source endpoint.
if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender);
// Call the internal OApp implementation of lzReceive.
_lzReceive(_origin, _guid, _message, _executor, _extraData);
}
/**
* @dev Internal function to implement lzReceive logic without needing to copy the basic parameter validation.
*/
function _lzReceive(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) internal virtual;
}
"
},
"lib/devtools/packages/oapp-evm/contracts/oapp/OAppCore.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IOAppCore, ILayerZeroEndpointV2 } from "./interfaces/IOAppCore.sol";
/**
* @title OAppCore
* @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations.
*/
abstract contract OAppCore is IOAppCore, Ownable {
// The LayerZero endpoint associated with the given OApp
ILayerZeroEndpointV2 public immutable endpoint;
// Mapping to store peers associated with corresponding endpoints
mapping(uint32 eid => bytes32 peer) public peers;
/**
* @dev Constructor to initialize the OAppCore with the provided endpoint and delegate.
* @param _endpoint The address of the LOCAL Layer Zero endpoint.
* @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
*
* @dev The delegate typically should be set as the owner of the contract.
*/
constructor(address _endpoint, address _delegate) {
endpoint = ILayerZeroEndpointV2(_endpoint);
if (_delegate == address(0)) revert InvalidDelegate();
endpoint.setDelegate(_delegate);
}
/**
* @notice Sets the peer address (OApp instance) for a corresponding endpoint.
* @param _eid The endpoint ID.
* @param _peer The address of the peer to be associated with the corresponding endpoint.
*
* @dev Only the owner/admin of the OApp can call this function.
* @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.
* @dev Set this to bytes32(0) to remove the peer address.
* @dev Peer is a bytes32 to accommodate non-evm chains.
*/
function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {
_setPeer(_eid, _peer);
}
/**
* @notice Sets the peer address (OApp instance) for a corresponding endpoint.
* @param _eid The endpoint ID.
* @param _peer The address of the peer to be associated with the corresponding endpoint.
*
* @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.
* @dev Set this to bytes32(0) to remove the peer address.
* @dev Peer is a bytes32 to accommodate non-evm chains.
*/
function _setPeer(uint32 _eid, bytes32 _peer) internal virtual {
peers[_eid] = _peer;
emit PeerSet(_eid, _peer);
}
/**
* @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.
* ie. the peer is set to bytes32(0).
* @param _eid The endpoint ID.
* @return peer The address of the peer associated with the specified endpoint.
*/
function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {
bytes32 peer = peers[_eid];
if (peer == bytes32(0)) revert NoPeer(_eid);
return peer;
}
/**
* @notice Sets the delegate address for the OApp.
* @param _delegate The address of the delegate to be set.
*
* @dev Only the owner/admin of the OApp can call this function.
* @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.
*/
function setDelegate(address _delegate) public onlyOwner {
endpoint.setDelegate(_delegate);
}
}
"
},
"src/interfaces/controllers/IVaultController.sol": {
"content": "// SPDX-License-Identifier: BSL-1.1
pragma solidity ^0.8.24;
import {RebalanceData} from "../../types/RebalanceTypes.sol";
/**
* @title IVaultController
* @author Variable Logic Labs, Corp (hello@blend.money)
* @notice Defines the interface for a vault controller, which is responsible for executing actions on a vault.
* @dev This interface is intended to be implemented by a contract that can manage and interact with various vault functionalities,
* such as rebalancing and other vault-specific actions.
*/
interface IVaultController {
/**
* @notice Executes a rebalance action on the specified vault.
* @dev This function is called to trigger a rebalancing of the vault's assets according to the provided data.
* @param vault The address of the vault to be rebalanced.
* @param rebalanceData An array of `RebalanceData` structs, each containing the necessary information for a rebalance operation.
*/
function executeRebalance(address vault, RebalanceData[] calldata rebalanceData) external;
}
"
},
"src/interfaces/controllers/IMarketAdapterController.sol": {
"content": "// SPDX-License-Identifier: BSL-1.1
pragma solidity ^0.8.24;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title IMarketAdapterController
* @author Blend.Money (hello@blend.money)
* @notice This interface defines the functions for swapping between loan and collateral tokens.
*/
interface IMarketAdapterController {
/**
* @notice Swaps a given amount of loan token for collateral token.
* @dev The balance amount will be the entire balance of the loan token on the adapter contract.
* @param loanToken The loan token to swap from.
* @param collateralToken The collateral token to swap to.
* @param recipient The recipient of the swapped collateral token.
* @param strategyData Additional data for the strategy.
* @param extraData Additional data for the swap.
*/
function swapToCollateral(
IERC20 loanToken,
IERC20 collateralToken,
address recipient,
bytes calldata strategyData,
bytes calldata extraData
) external;
/**
* @notice Swaps a given amount of collateral token for loan token.
* @dev The balance amount will be the entire balance of the collateral token on the adapter contract.
* @param loanToken The loan token to swap to.
* @param collateralToken The collateral token to swap from.
* @param recipient The recipient of the swapped loan token.
* @param strategyData Additional data for the strategy.
* @param extraData Additional data for the swap.
*/
function swapToLoanToken(
IERC20 loanToken,
IERC20 collateralToken,
address recipient,
bytes calldata strategyData,
bytes calldata extraData
) external;
}
"
},
"src/interfaces/controllers/IVaultActionController.sol": {
"content": "// SPDX-License-Identifier: BSL-1.1
pragma solidity ^0.8.24;
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
/**
* @title IVaultActionController
* @author Variable Logic Labs, Corp (hello@blend.money)
* @notice This interface allows an authorized address to perform targeted actions against user accounts.
*/
interface IVaultActionController {
/**
* @notice Performs a targeted action on behalf of a user.
* @param vault The vault to be acted upon.
* @param strategyData The encoded data for the strategy to be executed (dictated by the `RolesReceiver`)
* @param extraData The encoded data for the action to be performed.
*/
function executeAction(IERC4626 vault, bytes calldata strategyData, bytes calldata extraData) external;
}
"
},
"src/types/RebalanceTypes.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @title RebalanceData
* @notice Struct containing data required for rebalancing operations in vault strategies
* @dev This struct is used to encapsulate all necessary information for executing
* rebalance operations, including whether to increase or decrease positions,
* the amount to rebalance, and any additional data needed for the operation.
*/
struct RebalanceData {
/**
* @notice The expected market ID to rebalance
* @dev This will be used as a sanity check to ensure that
* the rebalance is being executed on the correct market
*/
bytes32 marketId;
/**
* @notice Flag indicating whether this is an increase or decrease operation
* @dev true = increase position, false = decrease position
*/
bool isIncrease;
/**
* @notice The amount to rebalance in base units (e.g., wei for ETH)
* @dev Must be greater than 0 for valid operations
*/
uint256 amount;
/**
* @notice Additional data required for the rebalance operation
* @dev This field can contain protocol-specific parameters, slippage settings,
* or any other data needed by the target adapter or strategy
*/
bytes extraData;
}
/**
* @title RebalanceAssetsPerShare
* @notice Struct containing assets per share data required for rebalancing operations
* @dev This struct is used to encapsulate all necessary assets per share data for executing
* rebalance operations, including the initial, final, and withdraw assets per share for the vault
* and collateral tokens.
*/
struct RebalanceAssetsPerShare {
uint256 vaultDepositAssetsPerShare;
uint256 vaultWithdrawAssetsPerShare;
uint256 morphoBorrowAssetsPerShare;
uint256 morphoRepayAssetsPerShare;
uint256 wrappedCollateralAssetsPerShare;
}
/**
* @title StoredRebalanceAssetsPerShare
* @notice Parameters for vault and morpho market assets per share
* @param vaultDepositAssetsPerShare Assets per share of the vault after Safe deposit
* @param vaultWithdrawAssetsPerShare Assets per share of the vault after the withdraw
* @param morphoBorrowAssetsPerShare Assets per share of the morpho market after the borrow
* @param morphoRepayAssetsPerShare Assets per share of the morpho market after the repay
* @param wrappedCollateralAssetsPerShare Assets per share of the collateral
*/
struct StoredRebalanceAssetsPerShare {
uint256 vaultDepositAssetsPerShare;
uint256 vaultWithdrawAssetsPerShare;
uint256 morphoBorrowAssetsPerShare;
uint256 morphoRepayAssetsPerShare;
uint256 wrappedCollateralAssetsPerShare;
}
"
},
"lib/safe-smart-account/contracts/interfaces/IModuleManager.sol": {
"content": "// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
import {Enum} from "../libraries/Enum.sol";
/**
* @title IModuleManager - An interface of contract managing Safe modules
* @notice Modules are extensions with unlimited access to a Safe that can be added to a Safe by its owners.
⚠️ WARNING: Modules are a security risk since they can execute arbitrary transactions,
so only trusted and audited modules should be added to a Safe. A malicious module can
completely takeover a Safe.
* @author @safe-global/safe-protocol
*/
interface IModuleManager {
event EnabledModule(address indexed module);
event DisabledModule(address indexed module);
event ExecutionFromModuleSuccess(address indexed module);
event ExecutionFromModuleFailure(address indexed module);
event ChangedModuleGuard(address indexed moduleGuard);
/**
* @notice Enables the module `module` for the Safe.
* @dev This can only be done via a Safe transaction.
* @param module Module to be whitelisted.
*/
function enableModule(address module) external;
/**
* @notice Disables the module `module` for the Safe.
* @dev This can only be done via a Safe transaction.
* @param prevModule Previous module in the modules linked list.
* @param module Module to be removed.
*/
function disableModule(address prevModule, address module) external;
/**
* @notice Execute `operation` (0: Call, 1: DelegateCall) to `to` with `value` (Native Token)
* @param to Destination address of module transaction.
* @param value Ether value of module transaction.
* @param data Data payload of module transaction.
* @param operation Operation type of module transaction.
* @return success Boolean flag indicating if the call succeeded.
*/
function execTransactionFromModule(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation
) external returns (bool success);
/**
* @notice Execute `operation` (0: Call, 1: DelegateCall) to `to` with `value` (Native Token) and return data
* @param to Destination address of module transaction.
* @param value Ether value of module transaction.
* @param data Data payload of module transaction.
* @param operation Operation type of module transaction.
* @return success Boolean flag indicating if the call succeeded.
* @return returnData Data returned by the call.
*/
function execTransactionFromModuleReturnData(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation
) external returns (bool success, bytes memory returnData);
/**
* @notice Returns if a module is enabled
* @return True if the module is enabled
*/
function isModuleEnabled(address module) external view returns (bool);
/**
* @notice Returns an array of modules.
* If all entries fit into a single page, the next pointer will be 0x1.
* If another page is present, next will be the last element of the returned array.
* @param start Start of the page. Has to be a module or start pointer (0x1 address)
* @param pageSize Maximum number of modules that should be returned. Has to be > 0
* @return array Array of modules.
* @return next Start of the next page.
*/
function getModulesPaginated(address start, uint256 pageSize) external view returns (address[] memory array, address next);
/**
* @dev Set a module guard that checks transactions initiated by the module before execution
* This can only be done via a Safe transaction.
* ⚠️ IMPORTANT: Since a module guard has full power to block Safe transaction execution initiated via a module,
* a broken module guard can cause a denial of service for the Safe modules. Make sure to carefully
* audit the module guard code and design recovery mechanisms.
* @notice Set Module Guard `moduleGuard` for the Safe. Make sure you trust the module guard.
* @param moduleGuard The address of the module guard to be used or the zero address to disable the module guard.
*/
function setModuleGuard(address moduleGuard) external;
}
"
},
"lib/safe-smart-account/contracts/libraries/Enum.sol": {
"content": "// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
/**
* @title Enum - Collection of enums used in Safe Smart Account contracts.
* @author @safe-global/safe-protocol
*/
library Enum {
enum Operation {
Call,
DelegateCall
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Context.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
"
},
"lib/openzeppelin-contracts/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`
Submitted on: 2025-10-12 16:46:07
Comments
Log in to comment.
No comments yet.