Files
zodiac/contracts/core/GuardableModule.sol
zeekayandHanzo Dev 6b1084e587 core: GuardableModule, IAvatar and IGuard, from the DAO repo
Zodiac code that had been sitting in luxdao/contracts under an AGPL header.
It is Gnosis Guild's, LGPL-3.0, and belongs in our Zodiac fork. Source in
luxdao/contracts: 11f600c2 "LuxDAO governance contracts" —
contracts/base/GuardableModule.sol, contracts/interfaces/dao/IAvatar.sol and
contracts/interfaces/dao/IGuard.sol. The layout follows zodiac-core
(contracts/core, contracts/interfaces).

Our GuardableModule deliberately differs from zodiac-core 4.x: setAvatar,
setTarget and setGuard are gated on onlyAvatar rather than onlyOwner, setGuard
does not run upstream's ERC-165 check, guards are called through
IGuard.checkTransaction rather than IModuleGuard.checkModuleTransaction, and
execAndReturnData is absent. That version is what is deployed behind the live
ModuleGovernorV1 and ModuleFractalV1 masters on Zoo 200200 and Pars 494949, so
the divergence is kept as-is here rather than reconciled. The code is the DAO
repo's byte for byte; only headers, imports and natspec changed.

Safe's Enum comes from luxfi/safe (contracts/interfaces/Enum.sol) through the
@luxfi/safe/ remapping, resolved to a checkout beside this repo the same way
luxdao/contracts reaches luxfi/standard. foundry.toml builds the three files.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 14:24:03 -07:00

165 lines
6.0 KiB
Solidity

// SPDX-License-Identifier: LGPL-3.0-only
// Copyright (c) Gnosis Guild (Zodiac); modifications (c) 2026 Lux Industries Inc.
pragma solidity ^0.8.30;
import {Enum} from "@luxfi/safe/interfaces/Enum.sol";
import {IAvatar} from "../interfaces/IAvatar.sol";
import {IGuard} from "../interfaces/IGuard.sol";
/**
* @title GuardableModule
* @author Gnosis Guild (Zodiac); modified by Lux Industries Inc
* @notice Base contract for modules that can execute transactions through a Safe (avatar)
* @dev Zodiac's GuardableModule as deployed behind the Lux DAO module masters. It differs
* from zodiac-core 4.x on purpose: setAvatar, setTarget and setGuard are gated on the
* avatar rather than an owner, setGuard performs no ERC-165 check, guards are called
* through IGuard.checkTransaction rather than IModuleGuard.checkModuleTransaction, and
* there is no execAndReturnData. The live masters depend on this shape; do not
* reconcile it with upstream without a migration plan for them.
*
* The module pattern allows for flexible execution of transactions through a Safe,
* with optional guard functionality for pre/post transaction checks.
*/
abstract contract GuardableModule {
// ======================================================================
// EVENTS
// ======================================================================
event AvatarSet(address indexed previousAvatar, address indexed newAvatar);
event TargetSet(address indexed previousTarget, address indexed newTarget);
event GuardSet(address indexed guard);
// ======================================================================
// STATE VARIABLES
// ======================================================================
/// @notice Address of the avatar (Safe) that this module is attached to
address public avatar;
/// @notice Address of the target that transactions will be executed on
/// @dev In most cases, target == avatar, but they can be different for complex setups
address public target;
/// @notice Optional guard contract that can implement pre/post transaction checks
address public guard;
// ======================================================================
// ERRORS
// ======================================================================
error GuardRejected();
error ModuleTransactionFailed();
// ======================================================================
// MODIFIERS
// ======================================================================
modifier onlyAvatar() {
require(msg.sender == avatar, "GuardableModule: caller is not the avatar");
_;
}
// ======================================================================
// EXTERNAL FUNCTIONS
// ======================================================================
/**
* @notice Sets the avatar (Safe) address for this module
* @param _avatar The address of the avatar/Safe
* @dev Can only be called by the current avatar
*/
function setAvatar(address _avatar) external onlyAvatar {
address previousAvatar = avatar;
avatar = _avatar;
emit AvatarSet(previousAvatar, _avatar);
}
/**
* @notice Sets the target address for transactions
* @param _target The address that transactions will be executed on
* @dev Can only be called by the avatar
*/
function setTarget(address _target) external onlyAvatar {
address previousTarget = target;
target = _target;
emit TargetSet(previousTarget, _target);
}
/**
* @notice Sets an optional guard contract
* @param _guard The address of the guard contract (or address(0) to disable)
* @dev Can only be called by the avatar
*/
function setGuard(address _guard) external onlyAvatar {
guard = _guard;
emit GuardSet(_guard);
}
/**
* @notice Gets the current guard address
* @return The address of the current guard (or address(0) if no guard is set)
*/
function getGuard() external view returns (address) {
return guard;
}
// ======================================================================
// INTERNAL FUNCTIONS
// ======================================================================
/**
* @notice Executes a transaction through the avatar
* @param to Destination address
* @param value Ether value
* @param data Data payload
* @param operation Operation type (0: Call, 1: DelegateCall)
* @return success True if the transaction succeeded
* @dev This function handles guard checks and transaction execution
*/
function exec(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation
) internal virtual returns (bool success) {
// Pre-transaction guard check
if (guard != address(0)) {
IGuard(guard).checkTransaction(
to,
value,
data,
operation,
// Additional parameters for guard interface
0, // safeTxGas
0, // baseGas
0, // gasPrice
address(0), // gasToken
payable(address(0)), // refundReceiver
"", // signatures
msg.sender
);
}
// Execute transaction through avatar
success = IAvatar(target).execTransactionFromModule(
to,
value,
data,
operation
);
// Post-transaction guard check
if (guard != address(0)) {
IGuard(guard).checkAfterExecution(bytes32(0), success);
}
return success;
}
/**
* @notice Alternative initialization function following Zodiac pattern
* @param initializeParams ABI encoded initialization parameters
* @dev Must be implemented by inheriting contracts
*/
function setUp(bytes memory initializeParams) public virtual;
}