-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #231 from bcnmy/feat/erc-7779-support
ERC-7779 support v 0.1
- Loading branch information
Showing
11 changed files
with
316 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ import { IERC7484 } from "./interfaces/IERC7484.sol"; | |
import { ModuleManager } from "./base/ModuleManager.sol"; | ||
import { ExecutionHelper } from "./base/ExecutionHelper.sol"; | ||
import { IValidator } from "./interfaces/modules/IValidator.sol"; | ||
import { IHook } from "./interfaces/modules/IHook.sol"; | ||
import { | ||
MODULE_TYPE_VALIDATOR, | ||
MODULE_TYPE_EXECUTOR, | ||
|
@@ -50,6 +51,8 @@ import { | |
} from "./lib/ModeLib.sol"; | ||
import { NonceLib } from "./lib/NonceLib.sol"; | ||
import { SentinelListLib, SENTINEL, ZERO_ADDRESS } from "sentinellist/SentinelList.sol"; | ||
import { ERC7779Adapter } from "./base/ERC7779Adapter.sol"; | ||
import { ECDSA } from "solady/utils/ECDSA.sol"; | ||
import { Initializable } from "./lib/Initializable.sol"; | ||
import { EmergencyUninstall, Execution } from "./types/DataTypes.sol"; | ||
|
||
|
@@ -61,7 +64,7 @@ import { EmergencyUninstall, Execution } from "./types/DataTypes.sol"; | |
/// @author @filmakarov | Biconomy | [email protected] | ||
/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth | ||
/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady | ||
contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgradeable { | ||
contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgradeable, ERC7779Adapter { | ||
using ModeLib for ExecutionMode; | ||
using ExecLib for *; | ||
using NonceLib for uint256; | ||
|
@@ -126,7 +129,9 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra | |
(userOpHash, userOp.signature) = _withPreValidationHook(userOpHash, op, missingAccountFunds); | ||
validationData = IValidator(validator).validateUserOp(userOp, userOpHash); | ||
} else { | ||
// add 7739 storage base | ||
validationData = _eip7702SignatureValidation(userOpHash, op.signature, validator) ? VALIDATION_SUCCESS : VALIDATION_FAILED; | ||
|
||
} | ||
} | ||
} | ||
|
@@ -298,7 +303,11 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra | |
|
||
_initModuleManager(); | ||
(address bootstrap, bytes memory bootstrapCall) = abi.decode(initData, (address, bytes)); | ||
(bool success,) = bootstrap.delegatecall(bootstrapCall); | ||
(bool success, ) = bootstrap.delegatecall(bootstrapCall); | ||
|
||
if (_amIERC7702()) { | ||
_addStorageBase(_NEXUS_STORAGE_LOCATION); | ||
} | ||
|
||
require(success, NexusInitializationFailed()); | ||
require(_hasValidators(), NoValidatorInstalled()); | ||
|
@@ -514,6 +523,20 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra | |
/// @dev Ensures that only authorized callers can upgrade the smart contract implementation. | ||
/// This is part of the UUPS (Universal Upgradeable Proxy Standard) pattern. | ||
/// @param newImplementation The address of the new implementation to upgrade to. | ||
|
||
/// @dev This function is called when the account is redelegated. | ||
function _onRedelegation() internal virtual override { | ||
AccountStorage storage $ = _getAccountStorage(); | ||
|
||
_tryUninstallValidators(); | ||
_tryUninstallExecutors(); | ||
$.emergencyUninstallTimelock[address($.hook)] = 0; | ||
_tryUninstallHooks(); | ||
|
||
// account should be properly initialized for the new delegate | ||
// use Nexus.initializeAccount() to reinitialize the account | ||
// otherwise modules will not be installed as the module manager is not initialized | ||
|
||
function _authorizeUpgrade(address newImplementation) internal virtual override(UUPSUpgradeable) { | ||
_onlyEntryPointOrSelf(); | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.27; | ||
|
||
import { IERC7779 } from "../interfaces/IERC7779.sol"; | ||
|
||
abstract contract ERC7779Adapter is IERC7779 { | ||
error NonAuthorizedOnRedelegationCaller(); | ||
|
||
// keccak256(abi.encode(uint256(keccak256(bytes("InteroperableDelegatedAccount.ERC.Storage"))) - 1)) & ~bytes32(uint256(0xff)); | ||
bytes32 internal constant ERC7779_STORAGE_BASE = 0xc473de86d0138e06e4d4918a106463a7cc005258d2e21915272bcb4594c18900; | ||
|
||
struct ERC7779Storage { | ||
bytes32[] storageBases; | ||
} | ||
|
||
/* | ||
* @dev Externally shares the storage bases that has been used throughout the account. | ||
* Majority of 7702 accounts will have their distinctive storage base to reduce the | ||
chance of storage collision. | ||
* This allows the external entities to know what the storage base is of the account. | ||
* Wallets willing to redelegate already-delegated accounts should call | ||
accountStorageBase() to check if it confirms with the account it plans to redelegate. | ||
* | ||
* The bytes32 array should be stored at the storage slot: | ||
keccak(keccak('InteroperableDelegatedAccount.ERC.Storage')-1) & ~0xff | ||
* This is an append-only array so newly redelegated accounts should not overwrite the | ||
storage at this slot, but just append their base to the array. | ||
* This append operation should be done during the initialization of the account. | ||
*/ | ||
function accountStorageBases() external view returns (bytes32[] memory) { | ||
ERC7779Storage storage $; | ||
assembly { | ||
$.slot := ERC7779_STORAGE_BASE | ||
} | ||
return $.storageBases; | ||
} | ||
|
||
function _addStorageBase(bytes32 storageBase) internal { | ||
ERC7779Storage storage $; | ||
assembly { | ||
$.slot := ERC7779_STORAGE_BASE | ||
} | ||
$.storageBases.push(storageBase); | ||
} | ||
|
||
/* | ||
* @dev Function called before redelegation. | ||
* This function should prepare the account for a delegation to a different implementation. | ||
* This function could be triggered by the new wallet that wants to redelegate an already delegated EOA. | ||
* It should uninitialize storages if needed and execute wallet-specific logic to prepare for redelegation. | ||
* msg.sender should be the owner of the account. | ||
*/ | ||
function onRedelegation() external returns (bool) { | ||
require(msg.sender == address(this), NonAuthorizedOnRedelegationCaller()); | ||
_onRedelegation(); | ||
return true; | ||
} | ||
|
||
/// @dev This function is called when the account is redelegated. | ||
/// @dev This function should be overridden by the account to implement wallet-specific logic. | ||
function _onRedelegation() internal virtual; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ pragma solidity ^0.8.27; | |
// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. | ||
// Learn more at https://biconomy.io. To report security issues, please contact us at: [email protected] | ||
|
||
import { SentinelListLib } from "sentinellist/SentinelList.sol"; | ||
import { SentinelListLib, SENTINEL } from "sentinellist/SentinelList.sol"; | ||
import { Storage } from "./Storage.sol"; | ||
import { IHook } from "../interfaces/modules/IHook.sol"; | ||
import { IModule } from "../interfaces/modules/IModule.sol"; | ||
|
@@ -113,6 +113,25 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError | |
return _getHook(); | ||
} | ||
|
||
/// @notice Checks if a nonce has been used. | ||
/// @param nonce The nonce to check. | ||
/// @return bool True if the nonce has been used, false otherwise. | ||
function wasNonceUsed(uint256 nonce) external view returns (bool) { | ||
return _getAccountStorage().nonces[nonce]; | ||
} | ||
|
||
/// @notice Fetches the 4337 pre-validation hook. | ||
/// @return hook The address of the 4337 pre-validation hook. | ||
function get4337PreValidationHook() external view returns (address) { | ||
return address(_getAccountStorage().preValidationHookERC4337); | ||
} | ||
|
||
/// @notice Fetches the 1271 pre-validation hook. | ||
/// @return hook The address of the 1271 pre-validation hook. | ||
function get1271PreValidationHook() external view returns (address) { | ||
return address(_getAccountStorage().preValidationHookERC1271); | ||
} | ||
|
||
/// @notice Fetches the fallback handler for a specific selector. | ||
/// @param selector The function selector to query. | ||
/// @return calltype The type of call that the handler manages. | ||
|
@@ -207,6 +226,21 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError | |
validator.excessivelySafeCall(gasleft(), 0, 0, abi.encodeWithSelector(IModule.onUninstall.selector, disableModuleData)); | ||
} | ||
|
||
/// @dev Uninstalls all validators and emits an event if any validator fails to uninstall. | ||
function _tryUninstallValidators() internal { | ||
SentinelListLib.SentinelList storage validators = _getAccountStorage().validators; | ||
address validator = validators.getNext(SENTINEL); | ||
// we do not need excessivelySafeCall here as it prevents reversion | ||
// we want to know if there's revert and emit the event | ||
while (validator != SENTINEL) { | ||
try IValidator(validator).onUninstall("") {} catch (bytes memory reason) { | ||
emit ValidatorUninstallFailed(validator, "", reason); | ||
} | ||
validator = validators.getNext(validator); | ||
} | ||
validators.popAll(); | ||
} | ||
|
||
/// @dev Installs a new executor module after checking if it matches the required module type. | ||
/// @param executor The address of the executor module to be installed. | ||
/// @param data Initialization data to configure the executor upon installation. | ||
|
@@ -225,6 +259,19 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError | |
executor.excessivelySafeCall(gasleft(), 0, 0, abi.encodeWithSelector(IModule.onUninstall.selector, disableModuleData)); | ||
} | ||
|
||
/// @dev Uninstalls all executors and emits an event if any executor fails to uninstall. | ||
function _tryUninstallExecutors() internal { | ||
SentinelListLib.SentinelList storage executors = _getAccountStorage().executors; | ||
address executor = executors.getNext(SENTINEL); | ||
while (executor != SENTINEL) { | ||
try IExecutor(executor).onUninstall("") {} catch (bytes memory reason) { | ||
emit ExecutorUninstallFailed(executor, "", reason); | ||
} | ||
executor = executors.getNext(executor); | ||
} | ||
executors.popAll(); | ||
} | ||
|
||
/// @dev Installs a hook module, ensuring no other hooks are installed before proceeding. | ||
/// @param hook The address of the hook to be installed. | ||
/// @param data Initialization data to configure the hook upon installation. | ||
|
@@ -249,6 +296,31 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError | |
hook.excessivelySafeCall(gasleft(), 0, 0, abi.encodeWithSelector(IModule.onUninstall.selector, data)); | ||
} | ||
|
||
/// @dev Uninstalls the hook and emits an event if the hook fails to uninstall. | ||
function _tryUninstallHooks() internal { | ||
address hook = _getHook(); | ||
if (hook != address(0)) { | ||
try IHook(hook).onUninstall("") {} catch (bytes memory reason) { | ||
emit HookUninstallFailed(hook, "", reason); | ||
} | ||
_setHook(address(0)); | ||
} | ||
hook = address(_getAccountStorage().preValidationHookERC1271); | ||
if (hook != address(0)) { | ||
try IPreValidationHookERC1271(hook).onUninstall("") {} catch (bytes memory reason) { | ||
emit HookUninstallFailed(hook, "", reason); | ||
} | ||
_setPreValidationHook(MODULE_TYPE_PREVALIDATION_HOOK_ERC1271, address(0)); | ||
} | ||
hook = address(_getAccountStorage().preValidationHookERC4337); | ||
if (hook != address(0)) { | ||
try IPreValidationHookERC4337(hook).onUninstall("") {} catch (bytes memory reason) { | ||
emit HookUninstallFailed(hook, "", reason); | ||
} | ||
_setPreValidationHook(MODULE_TYPE_PREVALIDATION_HOOK_ERC4337, address(0)); | ||
} | ||
} | ||
|
||
/// @dev Sets the current hook in the storage to the specified address. | ||
/// @param hook The new hook address. | ||
function _setHook(address hook) internal virtual { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.27; | ||
|
||
interface IERC7779 { | ||
/* | ||
* @dev Externally shares the storage bases that has been used throughout the account. | ||
* Majority of 7702 accounts will have their distinctive storage base to reduce the chance of storage collision. | ||
* This allows the external entities to know what the storage base is of the account. | ||
* Wallets willing to redelegate already-delegated accounts should call accountStorageBase() to check if it confirms with the account it plans to redelegate. | ||
* | ||
* The bytes32 array should be stored at the storage slot: keccak(keccak('InteroperableDelegatedAccount.ERC.Storage')-1) & ~0xff | ||
* This is an append-only array so newly redelegated accounts should not overwrite the storage at this slot, but just append their base to the array. | ||
* This append operation should be done during the initialization of the account. | ||
*/ | ||
function accountStorageBases() external view returns (bytes32[] memory); | ||
|
||
/* | ||
* @dev Function called before redelegation. | ||
* This function should prepare the account for a delegation to a different implementation. | ||
* This function could be triggered by the new wallet that wants to redelegate an already delegated EOA. | ||
* It should uninitialize storages if needed and execute wallet-specific logic to prepare for redelegation. | ||
* msg.sender should be the owner of the account. | ||
*/ | ||
function onRedelegation() external returns (bool); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,7 @@ pragma solidity ^0.8.27; | |
import { IERC4337Account } from "./IERC4337Account.sol"; | ||
import { IERC7579Account } from "./IERC7579Account.sol"; | ||
import { INexusEventsAndErrors } from "./INexusEventsAndErrors.sol"; | ||
|
||
import { IERC7779 } from "./IERC7779.sol"; | ||
/// @title Nexus - INexus Interface | ||
/// @notice Integrates ERC-4337 and ERC-7579 standards to manage smart accounts within the Nexus suite. | ||
/// @dev Consolidates ERC-4337 user operations and ERC-7579 configurations into a unified interface for smart account management. | ||
|
@@ -27,7 +27,7 @@ import { INexusEventsAndErrors } from "./INexusEventsAndErrors.sol"; | |
/// @author @filmakarov | Biconomy | [email protected] | ||
/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth | ||
/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady | ||
interface INexus is IERC4337Account, IERC7579Account, INexusEventsAndErrors { | ||
interface INexus is IERC4337Account, IERC7579Account, INexusEventsAndErrors, IERC7779 { | ||
/// @notice Initializes the smart account with a validator and custom data. | ||
/// @dev This method sets up the account for operation, linking it with a validator and initializing it with specific data. | ||
/// Can be called directly or via a factory. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.27; | ||
|
||
import { ERC7779Adapter } from "../base/ERC7779Adapter.sol"; | ||
|
||
contract MockERC7779 is ERC7779Adapter { | ||
|
||
function addStorageBase(bytes32 storageBase) external { | ||
_addStorageBase(storageBase); | ||
} | ||
|
||
function _onRedelegation() internal override { | ||
// do nothing | ||
} | ||
|
||
} |
Oops, something went wrong.