This EIP standardizes interfaces for composable and interoperable tooling for programmable Ethereum accounts. These interfaces separate contract accounts ("avatars") from their authentication and execution logic ("guards" and "modules"). Avatars implement the IAvatar
interface, and guards implement the IGuard
interface. Modules may take any form.
Currently, most programmable accounts (like DAO tools and frameworks) are built as monolithic systems where the authorization and execution logic are coupled, either within the same contract or in a tightly integrated system of contracts. This needlessly inhibits the flexibility of these tools and encourages platform lock-in via high switching costs.
By using the this EIP standard to separate concerns (decoupling authentication and execution logic), users are able to:
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
This EIP consists of four key concepts:
IAvatar
interface.IAvatar
interface.IGuard
interface. Modules, modifiers, and avatars that wish to be guardable MUST inherit Guardable
, MUST call checkTransaction()
before triggering execution on their target, and MUST call checkAfterExecution()
after execution is complete./// @title Avatar - A contract that manages modules that can execute transactions via this contract.
pragma solidity >=0.7.0 <0.9.0;
import "./Enum.sol";
interface IAvatar {
event EnabledModule(address module);
event DisabledModule(address module);
event ExecutionFromModuleSuccess(address indexed module);
event ExecutionFromModuleFailure(address indexed module);
/// @dev Enables a module on the avatar.
/// @notice Can only be called by the avatar.
/// @notice Modules should be stored as a linked list.
/// @notice Must emit EnabledModule(address module) if successful.
/// @param module Module to be enabled.
function enableModule(address module) external;
/// @dev Disables a module on the avatar.
/// @notice Can only be called by the avatar.
/// @notice Must emit DisabledModule(address module) if successful.
/// @param prevModule Address that pointed to the module to be removed in the linked list
/// @param module Module to be removed.
function disableModule(address prevModule, address module) external;
/// @dev Allows a Module to execute a transaction.
/// @notice Can only be called by an enabled module.
/// @notice Must emit ExecutionFromModuleSuccess(address module) if successful.
/// @notice Must emit ExecutionFromModuleFailure(address module) if unsuccessful.
/// @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: 0 == call, 1 == delegate call.
function execTransactionFromModule(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation
) external returns (bool success);
/// @dev Allows a Module to execute a transaction and return data
/// @notice Can only be called by an enabled module.
/// @notice Must emit ExecutionFromModuleSuccess(address module) if successful.
/// @notice Must emit ExecutionFromModuleFailure(address module) if unsuccessful.
/// @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: 0 == call, 1 == delegate call.
function execTransactionFromModuleReturnData(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation
) external returns (bool success, bytes memory returnData);
/// @dev Returns if an module is enabled
/// @return True if the module is enabled
function isModuleEnabled(address module) external view returns (bool);
/// @dev Returns array of modules.
/// @param start Start of the page.
/// @param pageSize Maximum number of modules that should be returned.
/// @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);
}
pragma solidity >=0.7.0 <0.9.0;
import "./Enum.sol";
interface IGuard {
function checkTransaction(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures,
address msgSender
) external;
function checkAfterExecution(bytes32 txHash, bool success) external;
}
pragma solidity >=0.7.0 <0.9.0;
import "./Enum.sol";
import "./BaseGuard.sol";
/// @title Guardable - A contract that manages fallback calls made to this contract
contract Guardable {
address public guard;
event ChangedGuard(address guard);
/// `guard_` does not implement IERC165.
error NotIERC165Compliant(address guard_);
/// @dev Set a guard that checks transactions before execution.
/// @param _guard The address of the guard to be used or the 0 address to disable the guard.
function setGuard(address _guard) external {
if (_guard != address(0)) {
if (!BaseGuard(_guard).supportsInterface(type(IGuard).interfaceId))
revert NotIERC165Compliant(_guard);
}
guard = _guard;
emit ChangedGuard(guard);
}
function getGuard() external view returns (address _guard) {
return guard;
}
}
pragma solidity >=0.7.0 <0.9.0;
import "./Enum.sol";
import "./IERC165.sol";
import "./IGuard.sol";
abstract contract BaseGuard is IERC165 {
function supportsInterface(bytes4 interfaceId)
external
pure
override
returns (bool)
{
return
interfaceId == type(IGuard).interfaceId || // 0xe6d7a83a
interfaceId == type(IERC165).interfaceId; // 0x01ffc9a7
}
/// @dev Module transactions only use the first four parameters: to, value, data, and operation.
/// Module.sol hardcodes the remaining parameters as 0 since they are not used for module transactions.
function checkTransaction(
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures,
address msgSender
) external virtual;
function checkAfterExecution(bytes32 txHash, bool success) external virtual;
}
pragma solidity >=0.7.0 <0.9.0;
/// @title Enum - Collection of enums
contract Enum {
enum Operation {Call, DelegateCall}
}
The interface defined in this standard is designed to be mostly compatible with most popular programmable accounts in use right now, to minimize the need for changes to existing tooling.
No backward compatibility issues are introduced by this standard.
There are some considerations that module developers and users should take into account: 1. Modules have absolute control: Modules have absolute control over any avatar on which they are enabled, so any module implementation should be treated as security critical and users should be vary cautious about enabling new modules. ONLY ENABLE MODULES THAT YOU TRUST WITH THE FULL VALUE OF THE AVATAR. 2. Race conditions: A given avatar may have any number of modules enabled, each with unilateral control over the safe. In such cases, there may be race conditions between different modules and/or other control mechanisms. 3. Don't brick your avatar: There are no safeguards to stop you adding or removing modules. If you remove all of the modules that let you control an avatar, the avatar will cease to function and all funds will be stuck.
Copyright and related rights waived via CC0.