Skip to content

Commit

Permalink
implement onRedelegate
Browse files Browse the repository at this point in the history
Filipp Makarov authored and Filipp Makarov committed Dec 23, 2024
1 parent 24a08a2 commit 45d738a
Showing 6 changed files with 87 additions and 12 deletions.
13 changes: 13 additions & 0 deletions contracts/Nexus.sol
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,
@@ -269,6 +270,7 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
_initModuleManager();
(address bootstrap, bytes memory bootstrapCall) = abi.decode(initData, (address, bytes));
(bool success, ) = bootstrap.delegatecall(bootstrapCall);

if (_amIERC7702()) {
_addStorageBase(_NEXUS_STORAGE_LOCATION);
}
@@ -421,6 +423,17 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
/// @param newImplementation The address of the new implementation to upgrade to.
function _authorizeUpgrade(address newImplementation) internal virtual override(UUPSUpgradeable) onlyEntryPointOrSelf { }

/// @dev This function is called when the account is redelegated.
function _onRedelegation() internal virtual override {
AccountStorage storage $ = _getAccountStorage();
$.validators.popAll();
$.executors.popAll();
$.emergencyUninstallTimelock[address($.hook)] = 0;
$.hook = IHook(ZERO_ADDRESS);
// reinitialize the module manager
_initModuleManager();
}

/// @dev EIP712 domain name and version.
function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) {
name = "Nexus";
22 changes: 12 additions & 10 deletions contracts/base/ERC7779Adapter.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

abstract contract ERC7779Adapter {
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));
@@ -43,18 +45,18 @@ abstract contract ERC7779Adapter {

/*
* @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.
* 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());
// this is not implemented at the moment so that the account can preserve state across
// delegations
_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;
}
26 changes: 26 additions & 0 deletions contracts/interfaces/IERC7779.sol
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);

}
4 changes: 2 additions & 2 deletions contracts/interfaces/INexus.sol
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 | filipp.makarov@biconomy.io
/// @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.
4 changes: 4 additions & 0 deletions contracts/mocks/MockERC7779.sol
Original file line number Diff line number Diff line change
@@ -9,4 +9,8 @@ contract MockERC7779 is ERC7779Adapter {
_addStorageBase(storageBase);
}

function _onRedelegation() internal override {
// do nothing
}

}
30 changes: 30 additions & 0 deletions test/foundry/unit/concrete/eip7702/TestEIP7702.t.sol
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import { NexusTest_Base } from "../../../utils/NexusTest_Base.t.sol";
import "../../../utils/Imports.sol";
import { MockTarget } from "contracts/mocks/MockTarget.sol";
import { IExecutionHelper } from "contracts/interfaces/base/IExecutionHelper.sol";
import { IHook } from "contracts/interfaces/modules/IHook.sol";

contract TestEIP7702 is NexusTest_Base {
using ECDSA for bytes32;
@@ -235,4 +236,33 @@ contract TestEIP7702 is NexusTest_Base {
// Assert that the value was set ie that execution was successful
assertTrue(valueTarget.balance == value);
}

function test_erc7702_redelegate() public {
address account = test_initializeAndExecSingle();
assertTrue(INexus(account).isModuleInstalled(MODULE_TYPE_VALIDATOR, address(mockValidator), ""));
assertTrue(INexus(account).isModuleInstalled(MODULE_TYPE_EXECUTOR, address(mockExecutor), ""));

// storage is cleared
vm.prank(address(account));
INexus(account).onRedelegation();
assertFalse(INexus(account).isModuleInstalled(MODULE_TYPE_VALIDATOR, address(mockValidator), ""));
assertFalse(INexus(account).isModuleInstalled(MODULE_TYPE_EXECUTOR, address(mockExecutor), ""));

// account is properly initialized to install modules again
vm.startPrank(address(ENTRYPOINT));
INexus(account).installModule(MODULE_TYPE_VALIDATOR, address(mockValidator), "");
INexus(account).installModule(MODULE_TYPE_EXECUTOR, address(mockExecutor), "");
INexus(account).installModule(MODULE_TYPE_HOOK, address(HOOK_MODULE), "");

vm.stopPrank();
assertTrue(INexus(account).isModuleInstalled(MODULE_TYPE_VALIDATOR, address(mockValidator), ""));
assertTrue(INexus(account).isModuleInstalled(MODULE_TYPE_EXECUTOR, address(mockExecutor), ""));
assertTrue(INexus(account).isModuleInstalled(MODULE_TYPE_HOOK, address(HOOK_MODULE), ""));
}

}

interface IModuleInfo {
function getValidatorsPaginated(address cursor, uint256 maxCount) external view returns (address[] memory validators, address nextValidator);
function getExecutorsPaginated(address cursor, uint256 maxCount) external view returns (address[] memory executors, address nextExecutor);
}

0 comments on commit 45d738a

Please sign in to comment.