Repository: bcnmy/nexus
Pull request #248: Release v1.0.2
Author: login: filmakarov
Created: 2025-03-03T15:31:22Z | Merged: 2025-03-04T17:56:47Z

- [x] Adds hotfix to fallback function
- [x] Adds more detailed testing including complex scenarios

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily updates the versioning of the `Nexus` contract and its associated modules, reflecting a transition from version `1.0.1` to `1.0.2`. It also includes adjustments in tests and mock contracts to ensure consistency with the new version.

### Detailed summary
- Updated `_domainNameAndVersion` in `contracts/Nexus.sol` to version `1.0.2`.
- Changed `version()` function in `K1Validator` to return `1.0.2`.
- Updated expected account IDs in tests to `biconomy.nexus.1.0.2`.
- Modified constant `_ACCOUNT_IMPLEMENTATION_ID` in `BaseAccount` to `1.0.2`.
- Adjusted assertions in multiple test files to verify the new version.
- Fixed typo in test description from `K1Validtor` to `K1Validator`.
- Added new functions in `MockHandler` for complex return data and bytes return.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->


Diff:
diff --git a/contracts/Nexus.sol b/contracts/Nexus.sol
index 265dc1de..d3917a0b 100644
--- a/contracts/Nexus.sol
+++ b/contracts/Nexus.sol
@@ -363,6 +363,6 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra
     /// @dev EIP712 domain name and version.
     function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) {
         name = "Nexus";
-        version = "1.0.1";
+        version = "1.0.2";
     }
 }
diff --git a/contracts/base/BaseAccount.sol b/contracts/base/BaseAccount.sol
index 1f15e989..7a2fd52c 100644
--- a/contracts/base/BaseAccount.sol
+++ b/contracts/base/BaseAccount.sol
@@ -25,7 +25,7 @@ import { IBaseAccount } from "../interfaces/base/IBaseAccount.sol";
 /// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady
 contract BaseAccount is IBaseAccount {
     /// @notice Identifier for this implementation on the network
-    string internal constant _ACCOUNT_IMPLEMENTATION_ID = "biconomy.nexus.1.0.0";
+    string internal constant _ACCOUNT_IMPLEMENTATION_ID = "biconomy.nexus.1.0.2";
 
     /// @notice The canonical address for the ERC4337 EntryPoint contract, version 0.7.
     /// This address is consistent across all supported networks.
diff --git a/contracts/base/ModuleManager.sol b/contracts/base/ModuleManager.sol
index a59ca9b3..91971321 100644
--- a/contracts/base/ModuleManager.sol
+++ b/contracts/base/ModuleManager.sol
@@ -64,8 +64,9 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError
     receive() external payable {}
 
     /// @dev Fallback function to manage incoming calls using designated handlers based on the call type.
-    fallback(bytes calldata callData) external payable withHook returns (bytes memory) {
-        return _fallback(callData);
+    /// Hooked manually in the _fallback function
+    fallback() external payable {
+        _fallback(msg.data);
     }
 
     /// @dev Retrieves a paginated list of validator addresses from the linked list.
@@ -424,13 +425,20 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError
         hook = address(_getAccountStorage().hook);
     }
 
-    function _fallback(bytes calldata callData) private returns (bytes memory result) {
+    function _fallback(bytes calldata callData) private {
         bool success;
+        bytes memory result;
         FallbackHandler storage $fallbackHandler = _getAccountStorage().fallbacks[msg.sig];
         address handler = $fallbackHandler.handler;
         CallType calltype = $fallbackHandler.calltype;
 
         if (handler != address(0)) {
+            // hook manually
+            address hook = _getHook();
+            bytes memory hookData;
+            if (hook != address(0)) {
+                hookData = IHook(hook).preCheck(msg.sender, msg.value, msg.data);
+            }
             //if there's a fallback handler, call it
             if (calltype == CALLTYPE_STATIC) {
                 (success, result) = handler.staticcall(ExecLib.get2771CallData(callData));
@@ -441,31 +449,39 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError
             }
 
             // Use revert message from fallback handler if the call was not successful
-            if (!success) {
-                assembly {
+            assembly {
+                if iszero(success) {
                     revert(add(result, 0x20), mload(result))
                 }
             }
-        } else {
-            // If there's no handler, the call can be one of onERCXXXReceived()
-            bytes32 s;
-            /// @solidity memory-safe-assembly
+
+            // hook post check
+            if (hook != address(0)) {
+                IHook(hook).postCheck(hookData);
+            }
+
+            // return the result
             assembly {
-                s := shr(224, calldataload(0))
-                // 0x150b7a02: `onERC721Received(address,address,uint256,bytes)`.
-                // 0xf23a6e61: `onERC1155Received(address,address,uint256,uint256,bytes)`.
-                // 0xbc197c81: `onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)`.
-                if or(eq(s, 0x150b7a02), or(eq(s, 0xf23a6e61), eq(s, 0xbc197c81))) {
-                    success := true // it is one of onERCXXXReceived
-                    result := mload(0x40) //result was set to 0x60 as it was empty, so we need to find a new space for it
-                    mstore(result, 0x04) //store length
-                    mstore(add(result, 0x20), shl(224, s)) //store calldata
-                    mstore(0x40, add(result, 0x24)) //allocate memory
-                }
+                return(add(result, 0x20), mload(result))
+            }
+        }
+        
+        // If there's no handler, the call can be one of onERCXXXReceived()
+        // No need to hook this as no execution is done here
+        bytes32 s;
+        /// @solidity memory-safe-assembly
+        assembly {
+            s := shr(224, calldataload(0))
+            // 0x150b7a02: `onERC721Received(address,address,uint256,bytes)`.
+            // 0xf23a6e61: `onERC1155Received(address,address,uint256,uint256,bytes)`.
+            // 0xbc197c81: `onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)`.
+            if or(eq(s, 0x150b7a02), or(eq(s, 0xf23a6e61), eq(s, 0xbc197c81))) {
+                mstore(0x20, s) // Store `msg.sig`.
+                return(0x3c, 0x20) // Return `msg.sig`.
             }
-            // if there was no handler and it is not the onERCXXXReceived call, revert
-            require(success, MissingFallbackHandler(msg.sig));
         }
+        // if there was no handler and it is not the onERCXXXReceived call, revert
+        revert MissingFallbackHandler(msg.sig);
     }
 
     /// @dev Helper function to paginate entries in a SentinelList.
diff --git a/contracts/mocks/MockHandler.sol b/contracts/mocks/MockHandler.sol
index dd2ff146..ffa105f0 100644
--- a/contracts/mocks/MockHandler.sol
+++ b/contracts/mocks/MockHandler.sol
@@ -6,8 +6,8 @@ import { MODULE_TYPE_FALLBACK } from "..//types/Constants.sol";
 
 contract MockHandler is IFallback {
     uint256 public count;
-    string public constant NAME = "Default Handler";
-    string public constant VERSION = "1.0.0";
+    string constant NAME = "Default Handler";
+    string constant VERSION = "1.0.0";
 
     event GenericFallbackCalled(address sender, uint256 value, bytes data); // Event for generic fallback
     event HandlerOnInstallCalled(bytes32 dataFirstWord);
@@ -24,6 +24,20 @@ contract MockHandler is IFallback {
         return this.onGenericFallback.selector;
     }
 
+    function complexReturnData(string memory input, bytes4 selector) external view returns (uint256, bytes memory, address, uint64, address) {
+        return (
+            uint256(block.timestamp),
+            abi.encode(input, NAME, VERSION, selector),
+            address(this),
+            uint64(block.chainid),
+            _msgSender()
+        );
+    }
+
+    function returnBytes() external view returns (bytes memory) {
+        return abi.encodePacked(NAME, VERSION);
+    }
+
     function onInstall(bytes calldata data) external override {
         if (data.length >= 0x20) {
             emit HandlerOnInstallCalled(bytes32(data[0:32]));
@@ -55,4 +69,22 @@ contract MockHandler is IFallback {
     function getState() external view returns (uint256) {
         return count;
     }
+
+    function getName() external pure returns (string memory) {
+        return NAME;
+    }
+
+    function getVersion() external pure returns (string memory) {
+        return VERSION;
+    }
+
+    function _msgSender() internal pure returns (address sender) {
+        // The assembly code is more direct than the Solidity version using `abi.decode`.
+        /* solhint-disable no-inline-assembly */
+        /// @solidity memory-safe-assembly
+        assembly {
+            sender := shr(96, calldataload(sub(calldatasize(), 20)))
+        }
+        /* solhint-enable no-inline-assembly */
+    }
 }
diff --git a/contracts/mocks/MockNFT.sol b/contracts/mocks/MockNFT.sol
index e96a66bc..33df10de 100644
--- a/contracts/mocks/MockNFT.sol
+++ b/contracts/mocks/MockNFT.sol
@@ -2,6 +2,7 @@
 pragma solidity ^0.8.27;
 
 import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
 
 contract MockNFT is ERC721 {
     constructor(string memory name, string memory symbol) ERC721(name, symbol) {}
@@ -17,4 +18,20 @@ contract MockNFT is ERC721 {
     function safeMint(address to, uint256 tokenId) public {
         _safeMint(to, tokenId);
     }
+
+    function safeTransfer(address to, uint256 tokenId) public {
+        _safeTransfer(msg.sender, to, tokenId);
+    }
+}
+
+contract MockERC1155 is ERC1155 {
+    constructor(string memory uri) ERC1155(uri) {}
+
+    function safeMint(address to, uint256 tokenId, uint256 amount) public {
+        _mint(to, tokenId, amount, "");
+    }
+
+    function safeMintBatch(address to, uint256[] memory tokenIds, uint256[] memory amounts) public {
+        _mintBatch(to, tokenIds, amounts, "");
+    }
 }
diff --git a/contracts/modules/validators/K1Validator.sol b/contracts/modules/validators/K1Validator.sol
index 110fcfc2..14eaec85 100644
--- a/contracts/modules/validators/K1Validator.sol
+++ b/contracts/modules/validators/K1Validator.sol
@@ -192,7 +192,7 @@ contract K1Validator is IValidator, ERC7739Validator {
     /// @notice Returns the version of the module
     /// @return The version of the module
     function version() external pure returns (string memory) {
-        return "1.0.1";
+        return "1.0.2";
     }
 
     /// @notice Checks if the module is of the specified type
diff --git a/test/foundry/fork/arbitrum/ArbitrumSmartAccountUpgradeTest.t.sol b/test/foundry/fork/arbitrum/ArbitrumSmartAccountUpgradeTest.t.sol
index 06b9584f..a50dcdbc 100644
--- a/test/foundry/fork/arbitrum/ArbitrumSmartAccountUpgradeTest.t.sol
+++ b/test/foundry/fork/arbitrum/ArbitrumSmartAccountUpgradeTest.t.sol
@@ -45,7 +45,7 @@ contract ArbitrumSmartAccountUpgradeTest is NexusTest_Base, ArbitrumSettings {
     /// @notice Validates the account ID after the upgrade process.
     function test_AccountIdValidationAfterUpgrade() public {
         test_UpgradeV2ToV3AndInitialize();
-        string memory expectedAccountId = "biconomy.nexus.1.0.0";
+        string memory expectedAccountId = "biconomy.nexus.1.0.2";
         string memory actualAccountId = IAccountConfig(payable(address(smartAccountV2))).accountId();
         assertEq(actualAccountId, expectedAccountId, "Account ID does not match after upgrade.");
     }
diff --git a/test/foundry/unit/concrete/accountconfig/TestAccountConfig_AccountId.t.sol b/test/foundry/unit/concrete/accountconfig/TestAccountConfig_AccountId.t.sol
index c8e13797..d2a346d5 100644
--- a/test/foundry/unit/concrete/accountconfig/TestAccountConfig_AccountId.t.sol
+++ b/test/foundry/unit/concrete/accountconfig/TestAccountConfig_AccountId.t.sol
@@ -19,7 +19,7 @@ contract TestAccountConfig_AccountId is Test {
 
     /// @notice Tests if the account ID returns the expected value
     function test_WhenCheckingTheAccountID() external givenTheAccountConfiguration {
-        string memory expected = "biconomy.nexus.1.0.0";
+        string memory expected = "biconomy.nexus.1.0.2";
         assertEq(accountConfig.accountId(), expected, "AccountConfig should return the expected account ID.");
     }
 }
diff --git a/test/foundry/unit/concrete/factory/TestAccountFactory_Deployments.t.sol b/test/foundry/unit/concrete/factory/TestAccountFactory_Deployments.t.sol
index 6b97f1e4..2845260e 100644
--- a/test/foundry/unit/concrete/factory/TestAccountFactory_Deployments.t.sol
+++ b/test/foundry/unit/concrete/factory/TestAccountFactory_Deployments.t.sol
@@ -72,7 +72,7 @@ contract TestAccountFactory_Deployments is NexusTest_Base {
         userOps[0] = buildUserOpWithInitAndCalldata(user, initCode, "", address(VALIDATOR_MODULE));
         ENTRYPOINT.depositTo{ value: 1 ether }(address(accountAddress));
         ENTRYPOINT.handleOps(userOps, payable(user.addr));
-        assertEq(IAccountConfig(accountAddress).accountId(), "biconomy.nexus.1.0.0", "Not deployed properly");
+        assertEq(IAccountConfig(accountAddress).accountId(), "biconomy.nexus.1.0.2", "Not deployed properly");
     }
 
     /// @notice Tests that deploying an account fails if it already exists.
diff --git a/test/foundry/unit/concrete/factory/TestNexusAccountFactory_Deployments.t.sol b/test/foundry/unit/concrete/factory/TestNexusAccountFactory_Deployments.t.sol
index ce4146d9..932159b2 100644
--- a/test/foundry/unit/concrete/factory/TestNexusAccountFactory_Deployments.t.sol
+++ b/test/foundry/unit/concrete/factory/TestNexusAccountFactory_Deployments.t.sol
@@ -83,7 +83,7 @@ contract TestNexusAccountFactory_Deployments is NexusTest_Base {
         userOps[0] = buildUserOpWithInitAndCalldata(user, initCode, "", address(VALIDATOR_MODULE));
         ENTRYPOINT.depositTo{ value: 1 ether }(address(accountAddress));
         ENTRYPOINT.handleOps(userOps, payable(user.addr));
-        assertEq(IAccountConfig(accountAddress).accountId(), "biconomy.nexus.1.0.0", "Not deployed properly");
+        assertEq(IAccountConfig(accountAddress).accountId(), "biconomy.nexus.1.0.2", "Not deployed properly");
     }
 
     /// @notice Tests that deploying an account fails if it already exists.
diff --git a/test/foundry/unit/concrete/gas/TestGas_NexusAccountFactory.t.sol b/test/foundry/unit/concrete/gas/TestGas_NexusAccountFactory.t.sol
index 628adbd5..9fcca4a6 100644
--- a/test/foundry/unit/concrete/gas/TestGas_NexusAccountFactory.t.sol
+++ b/test/foundry/unit/concrete/gas/TestGas_NexusAccountFactory.t.sol
@@ -71,7 +71,7 @@ contract TestGas_NexusAccountFactory is TestModuleManagement_Base {
     /// @notice Validates the creation of a new account.
     /// @param _account The new account address.
     function assertValidCreation(Nexus _account) internal {
-        string memory expected = "biconomy.nexus.1.0.0";
+        string memory expected = "biconomy.nexus.1.0.2";
         assertEq(_account.accountId(), expected, "AccountConfig should return the expected account ID.");
         assertTrue(
             _account.isModuleInstalled(MODULE_TYPE_VALIDATOR, address(VALIDATOR_MODULE), ""), "Account should have the validation module installed"
diff --git a/test/foundry/unit/concrete/modulemanager/TestModuleManager_FallbackHandler.t.sol b/test/foundry/unit/concrete/modulemanager/TestModuleManager_FallbackHandler.t.sol
index f0b52d25..ed55b3fd 100644
--- a/test/foundry/unit/concrete/modulemanager/TestModuleManager_FallbackHandler.t.sol
+++ b/test/foundry/unit/concrete/modulemanager/TestModuleManager_FallbackHandler.t.sol
@@ -4,13 +4,24 @@ pragma solidity ^0.8.27;
 import "../../../utils/Imports.sol";
 import "../../../shared/TestModuleManagement_Base.t.sol";
 
+interface IHandler {
+    function returnBytes() external returns (bytes memory);
+    function complexReturnData(string memory input, bytes4 selector) external returns (uint256, bytes memory, address, uint64, address);
+}
+
 /// @title TestModuleManager_FallbackHandler
 /// @notice Tests for installing and uninstalling the fallback handler in a smart account.
 contract TestModuleManager_FallbackHandler is TestModuleManagement_Base {
-    /// @notice Sets up the base module management environment and installs the fallback handler.
+
+    MockNFT internal erc721;
+    MockERC1155 internal erc1155;
+    
     function setUp() public {
         init();
 
+        erc721 = new MockNFT("Mock NFT", "MNFT");
+        erc1155 = new MockERC1155("Test");
+
         Execution[] memory execution = new Execution[](2);
 
         // Custom data for installing the MockHandler with call type STATIC
@@ -73,26 +84,14 @@ contract TestModuleManager_FallbackHandler is TestModuleManagement_Base {
         // Prepare UserOperation
         PackedUserOperation[] memory userOps = buildPackedUserOperation(BOB, BOB_ACCOUNT, EXECTYPE_DEFAULT, executions, address(VALIDATOR_MODULE), 0);
 
-        if (!skip) {
-            // Expect the GenericFallbackCalled event from the MockHandler contract
-            vm.expectEmit(true, true, false, true, address(HANDLER_MODULE));
-            emit GenericFallbackCalled(address(this), 123, "Example data");
-        }
+        // Expect the GenericFallbackCalled event from the MockHandler contract
+        vm.expectEmit(true, true, false, true, address(HANDLER_MODULE));
+        emit GenericFallbackCalled(address(this), 123, "Example data");
 
         // Call handleOps, which should trigger the fallback handler and emit the event
         ENTRYPOINT.handleOps(userOps, payable(address(BOB.addr)));
     }
 
-    /// @notice Tests that handleOps triggers the generic fallback handler.
-    function test_HandleOpsTriggersGenericFallback_IsProperlyHooked() public {
-        vm.expectEmit(address(HOOK_MODULE));
-        emit PreCheckCalled();
-        vm.expectEmit(address(HOOK_MODULE));
-        emit PostCheckCalled();
-        // skip fallback emit check as per Matching Sequences section here => https://book.getfoundry.sh/cheatcodes/expect-emit 
-        test_HandleOpsTriggersGenericFallback({skip: true});
-    }
-
     /// @notice Tests installing a fallback handler.
     /// @param selector The function selector for the fallback handler.
     function test_InstallFallbackHandler(bytes4 selector) internal {
@@ -290,20 +289,120 @@ contract TestModuleManager_FallbackHandler is TestModuleManagement_Base {
     }
 
     function test_onTokenReceived_Success() public {
-        vm.startPrank(address(ENTRYPOINT));
+        erc721.safeMint(address(BOB_ACCOUNT), 1);
+        assertEq(erc721.balanceOf(address(BOB_ACCOUNT)), 1);
+
+        erc721.safeMint(ALICE_ADDRESS, 2);
+        vm.prank(ALICE_ADDRESS);
+        erc721.safeTransfer(address(BOB_ACCOUNT), 2);
+        assertEq(erc721.ownerOf(2), address(BOB_ACCOUNT));
+
+        erc1155.safeMint(address(BOB_ACCOUNT), 1, 1);
+        assertEq(erc1155.balanceOf(address(BOB_ACCOUNT), 1), 1);
+
+        uint256[] memory tokenIds = new uint256[](2);
+        tokenIds[0] = 2;
+        tokenIds[1] = 3;
+        uint256[] memory amounts = new uint256[](2);
+        amounts[0] = 10;
+        amounts[1] = 30;
+        erc1155.safeMintBatch(address(BOB_ACCOUNT), tokenIds, amounts);
+
+        assertEq(erc1155.balanceOf(address(BOB_ACCOUNT), 2), 10);
+        assertEq(erc1155.balanceOf(address(BOB_ACCOUNT), 3), 30);
+
         //ERC-721
         (bool success, bytes memory data) = address(BOB_ACCOUNT).call{value: 0}(hex'150b7a02');
         assertTrue(success);
-        assertTrue(keccak256(data) == keccak256(bytes(hex'150b7a02')));
+        assertTrue(keccak256(data) == keccak256((hex'150b7a0200000000000000000000000000000000000000000000000000000000')));
         //ERC-1155 
         (success, data) = address(BOB_ACCOUNT).call{value: 0}(hex'f23a6e61');
         assertTrue(success);
-        assertTrue(keccak256(data) == keccak256(bytes(hex'f23a6e61')));
+        assertTrue(keccak256(data) == keccak256(bytes(hex'f23a6e6100000000000000000000000000000000000000000000000000000000')));
         //ERC-1155 Batch
         (success, data) = address(BOB_ACCOUNT).call{value: 0}(hex'bc197c81');
         assertTrue(success);
-        assertTrue(keccak256(data) == keccak256(bytes(hex'bc197c81')));
+        assertTrue(keccak256(data) == keccak256(bytes(hex'bc197c8100000000000000000000000000000000000000000000000000000000')));
+    }
+
+    function test_ComplexReturnData() public {
+        bytes4 selector = bytes4(keccak256(abi.encodePacked("complexReturnData(string,bytes4)")));
+
+        bytes memory customData = abi.encode(bytes5(abi.encodePacked(selector, CALLTYPE_SINGLE)));
+        bytes memory callData = abi.encodeWithSelector(
+            IModuleManager.installModule.selector,
+            MODULE_TYPE_FALLBACK,
+            address(HANDLER_MODULE),
+            customData
+        );
+        Execution[] memory execution = new Execution[](1);
+        execution[0] = Execution(address(BOB_ACCOUNT), 0, callData);
+        PackedUserOperation[] memory userOps = buildPackedUserOperation(BOB, BOB_ACCOUNT, EXECTYPE_DEFAULT, execution, address(VALIDATOR_MODULE), 0);
+        ENTRYPOINT.handleOps(userOps, payable(address(BOB.addr)));
 
-        vm.stopPrank();
+        // Verify the fallback handler was installed for the given selector
+        assertTrue(BOB_ACCOUNT.isModuleInstalled(MODULE_TYPE_FALLBACK, address(HANDLER_MODULE), customData), "Fallback handler not installed");
+
+        string memory testString = "Hello Complex Return Data that is more than 32 bytes";
+        bytes4 testSelector = bytes4(abi.encode("foo()"));
+
+        bytes memory data = abi.encodeWithSelector(
+            MockHandler(address(0)).complexReturnData.selector,
+            testString,
+            testSelector
+        );
+
+        address expectedSender = address(0x1234567890123456789012345678901234567890);
+        vm.prank(expectedSender);
+        (bool success, bytes memory result) = address(BOB_ACCOUNT).call{value: 0}(data);
+        assertTrue(success);
+        (uint256 timestamp, bytes memory resultData, address addr, uint64 chainId, address sender) = abi.decode(result, (uint256, bytes, address, uint64, address));
+        assertEq(timestamp, block.timestamp);
+        assertEq(addr, address(HANDLER_MODULE));
+        assertEq(chainId, block.chainid);
+        assertEq(sender, expectedSender);
+        bytes memory expectedData = abi.encode(testString, HANDLER_MODULE.getName(), HANDLER_MODULE.getVersion(), testSelector);
+        assertEq(keccak256(resultData), keccak256(expectedData));
+
+        vm.prank(expectedSender); 
+        (timestamp, resultData, addr, chainId, sender) = IHandler(address(BOB_ACCOUNT)).complexReturnData(testString, testSelector);
+        assertEq(timestamp, block.timestamp);
+        assertEq(addr, address(HANDLER_MODULE));
+        assertEq(chainId, block.chainid);
+        assertEq(sender, expectedSender);
+        assertEq(keccak256(resultData), keccak256(expectedData));
+    }
+
+    function test_ReturnBytes_and_Hook_fallback() public {
+        bytes4 selector = bytes4(keccak256(abi.encodePacked("returnBytes()")));
+        bytes memory customData = abi.encode(bytes5(abi.encodePacked(selector, CALLTYPE_SINGLE)));
+        bytes memory callData = abi.encodeWithSelector(
+            IModuleManager.installModule.selector,
+            MODULE_TYPE_FALLBACK,
+            address(HANDLER_MODULE),
+            customData
+        );
+        Execution[] memory execution = new Execution[](1);
+        execution[0] = Execution(address(BOB_ACCOUNT), 0, callData);
+        PackedUserOperation[] memory userOps = buildPackedUserOperation(BOB, BOB_ACCOUNT, EXECTYPE_DEFAULT, execution, address(VALIDATOR_MODULE), 0);
+        ENTRYPOINT.handleOps(userOps, payable(address(BOB.addr)));
+
+        // Verify the fallback handler was installed for the given selector
+        assertTrue(BOB_ACCOUNT.isModuleInstalled(MODULE_TYPE_FALLBACK, address(HANDLER_MODULE), customData), "Fallback handler not installed");
+
+        bytes memory data = abi.encodeWithSelector( 
+            MockHandler(address(0)).returnBytes.selector
+        );
+        (bool success, bytes memory result) = address(BOB_ACCOUNT).call{value: 0}(data);
+        bytes memory expectedResult = abi.encodePacked(HANDLER_MODULE.getName(), HANDLER_MODULE.getVersion());
+        assertTrue(success);
+        assertEq(abi.decode(result, (bytes)), expectedResult);
+
+        vm.expectEmit(true, true, true, true, address(HOOK_MODULE));
+        emit PreCheckCalled();
+        vm.expectEmit(true, true, true, true, address(HOOK_MODULE));
+        emit PostCheckCalled();
+        bytes memory result2 = IHandler(address(BOB_ACCOUNT)).returnBytes();
+        assertEq(result2, expectedResult);
     }
 }
diff --git a/test/foundry/unit/concrete/modules/TestK1Validator.t.sol b/test/foundry/unit/concrete/modules/TestK1Validator.t.sol
index 47e5dad6..4e5c8d12 100644
--- a/test/foundry/unit/concrete/modules/TestK1Validator.t.sol
+++ b/test/foundry/unit/concrete/modules/TestK1Validator.t.sol
@@ -187,7 +187,7 @@ contract TestK1Validator is NexusTest_Base {
     function test_Version() public {
         string memory contractVersion = validator.version();
 
-        assertEq(contractVersion, "1.0.1", "Contract version should be '1.0.1'");
+        assertEq(contractVersion, "1.0.2", "Contract version should be '1.0.2'");
     }
 
     /// @notice Tests the isModuleType function to return the correct module type
@@ -383,7 +383,7 @@ contract TestK1Validator is NexusTest_Base {
             abi.encode(
                 keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                 keccak256("Nexus"),
-                keccak256("1.0.1"),
+                keccak256("1.0.2"),
                 block.chainid,
                 address(BOB_ACCOUNT)
             )
diff --git a/test/hardhat/smart-account/Nexus.Basics.specs.ts b/test/hardhat/smart-account/Nexus.Basics.specs.ts
index 0ac6613c..9dfdc588 100644
--- a/test/hardhat/smart-account/Nexus.Basics.specs.ts
+++ b/test/hardhat/smart-account/Nexus.Basics.specs.ts
@@ -133,7 +133,7 @@ describe("Nexus Basic Specs", function () {
 
   describe("Smart Account Basics", function () {
     it("Should correctly return the Nexus's ID", async function () {
-      expect(await smartAccount.accountId()).to.equal("biconomy.nexus.1.0.0");
+      expect(await smartAccount.accountId()).to.equal("biconomy.nexus.1.0.2");
     });
 
     it("Should get implementation address of smart account", async () => {
diff --git a/test/hardhat/smart-account/Nexus.Module.K1Validator.specs.ts b/test/hardhat/smart-account/Nexus.Module.K1Validator.specs.ts
index 68b4d7fc..45394706 100644
--- a/test/hardhat/smart-account/Nexus.Module.K1Validator.specs.ts
+++ b/test/hardhat/smart-account/Nexus.Module.K1Validator.specs.ts
@@ -61,7 +61,7 @@ describe("K1Validator module tests", () => {
     });
   });
 
-  describe("K1Validtor tests", () => {
+  describe("K1Validator tests", () => {
     it("should check if validator is installed", async () => {
       expect(
         await deployedNexus.isModuleInstalled(
@@ -79,7 +79,7 @@ describe("K1Validator module tests", () => {
 
     it("should get module version", async () => {
       const version = await k1Validator.version();
-      expect(version).to.equal("1.0.1");
+      expect(version).to.equal("1.0.2");
     });
 
     it("should check module type", async () => {


Reviews:
user: login: oxshaman
state: APPROVED
author_association: NONE
submitted_at: 2025-03-03T15:32:26Z
user: login: fichiokaku
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T17:37:28Z
user: login: NoahMarconi
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-03T21:35:26Z
user: login: NoahMarconi
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-03T21:37:59Z
user: login: NoahMarconi
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-03T21:38:40Z
user: login: NoahMarconi
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-03T21:40:28Z
user: login: vnmrtz
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-03T22:17:27Z
user: login: filmakarov
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T07:55:08Z
user: login: filmakarov
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T07:55:13Z
user: login: filmakarov
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T07:55:19Z
user: login: livingrockrises
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T13:44:31Z
user: login: filmakarov
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T17:55:50Z
user: login: livingrockrises
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T12:48:29Z

Review comments:
diff_hunk: @@ -457,10 +473,8 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError
                 // 0xbc197c81: `onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)`.
                 if or(eq(s, 0x150b7a02), or(eq(s, 0xf23a6e61), eq(s, 0xbc197c81))) {
                     success := true // it is one of onERCXXXReceived
path: contracts/base/ModuleManager.sol
user: login: NoahMarconi
body: success assignment appears unused in the revision (can be omitted?)
created_at: 2025-03-03T21:35:26Z
author_association: NONE
diff_hunk: @@ -440,14 +448,22 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError
                 revert UnsupportedCallType(calltype);
             }
 
+            // hook post check
+            if (hook != address(0)) {
+                IHook(hook).postCheck(hookData);
+            }
+
             // Use revert message from fallback handler if the call was not successful
-            if (!success) {
-                assembly {
+            assembly {
+                if iszero(success) {
                     revert(add(result, 0x20), mload(result))
                 }
+                return (add(result, 0x20), mload(result))
path: contracts/base/ModuleManager.sol
user: login: NoahMarconi
body: lint: spacing
created_at: 2025-03-03T21:37:59Z
author_association: NONE
diff_hunk: @@ -440,14 +448,22 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError
                 revert UnsupportedCallType(calltype);
             }
 
+            // hook post check
+            if (hook != address(0)) {
+                IHook(hook).postCheck(hookData);
+            }
+
             // Use revert message from fallback handler if the call was not successful
-            if (!success) {
-                assembly {
+            assembly {
+                if iszero(success) {
                     revert(add(result, 0x20), mload(result))
                 }
+                return (add(result, 0x20), mload(result))
             }
+            
         } else {
path: contracts/base/ModuleManager.sol
user: login: NoahMarconi
body: else block is not needed in revision as 475-462 revert or return
created_at: 2025-03-03T21:38:39Z
author_association: NONE
diff_hunk: @@ -440,14 +448,22 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError
                 revert UnsupportedCallType(calltype);
             }
 
+            // hook post check
+            if (hook != address(0)) {
+                IHook(hook).postCheck(hookData);
+            }
+
             // Use revert message from fallback handler if the call was not successful
-            if (!success) {
-                assembly {
+            assembly {
+                if iszero(success) {
                     revert(add(result, 0x20), mload(result))
                 }
+                return (add(result, 0x20), mload(result))
             }
+            
         } else {
path: contracts/base/ModuleManager.sol
user: login: NoahMarconi
body: i.e. can use assembly block from pre pull #183 L103-L113
created_at: 2025-03-03T21:40:28Z
author_association: NONE
diff_hunk: @@ -457,10 +473,8 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError
                 // 0xbc197c81: `onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)`.
                 if or(eq(s, 0x150b7a02), or(eq(s, 0xf23a6e61), eq(s, 0xbc197c81))) {
                     success := true // it is one of onERCXXXReceived
path: contracts/base/ModuleManager.sol
user: login: vnmrtz
body: Agreed, the early return renders it pointless after the fix.
created_at: 2025-03-03T22:17:27Z
author_association: NONE
diff_hunk: @@ -457,10 +473,8 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError
                 // 0xbc197c81: `onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)`.
                 if or(eq(s, 0x150b7a02), or(eq(s, 0xf23a6e61), eq(s, 0xbc197c81))) {
                     success := true // it is one of onERCXXXReceived
path: contracts/base/ModuleManager.sol
user: login: filmakarov
body: addressed
created_at: 2025-03-04T07:55:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -440,14 +448,22 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError
                 revert UnsupportedCallType(calltype);
             }
 
+            // hook post check
+            if (hook != address(0)) {
+                IHook(hook).postCheck(hookData);
+            }
+
             // Use revert message from fallback handler if the call was not successful
-            if (!success) {
-                assembly {
+            assembly {
+                if iszero(success) {
                     revert(add(result, 0x20), mload(result))
                 }
+                return (add(result, 0x20), mload(result))
path: contracts/base/ModuleManager.sol
user: login: filmakarov
body: addressed
created_at: 2025-03-04T07:55:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -440,14 +448,22 @@ abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndError
                 revert UnsupportedCallType(calltype);
             }
 
+            // hook post check
+            if (hook != address(0)) {
+                IHook(hook).postCheck(hookData);
+            }
+
             // Use revert message from fallback handler if the call was not successful
-            if (!success) {
-                assembly {
+            assembly {
+                if iszero(success) {
                     revert(add(result, 0x20), mload(result))
                 }
+                return (add(result, 0x20), mload(result))
             }
+            
         } else {
path: contracts/base/ModuleManager.sol
user: login: filmakarov
body: addressed
created_at: 2025-03-04T07:55:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -290,20 +289,120 @@ contract TestModuleManager_FallbackHandler is TestModuleManagement_Base {
     }
 
     function test_onTokenReceived_Success() public {
-        vm.startPrank(address(ENTRYPOINT));
+        erc721.safeMint(address(BOB_ACCOUNT), 1);
+        assertEq(erc721.balanceOf(address(BOB_ACCOUNT)), 1);
+
+        erc721.safeMint(ALICE_ADDRESS, 2);
+        vm.prank(ALICE_ADDRESS);
+        erc721.safeTransfer(address(BOB_ACCOUNT), 2);
+        assertEq(erc721.ownerOf(2), address(BOB_ACCOUNT));
+
+        erc1155.safeMint(address(BOB_ACCOUNT), 1, 1);
+        assertEq(erc1155.balanceOf(address(BOB_ACCOUNT), 1), 1);
+
+        uint256[] memory tokenIds = new uint256[](2);
+        tokenIds[0] = 2;
+        tokenIds[1] = 3;
+        uint256[] memory amounts = new uint256[](2);
+        amounts[0] = 10;
+        amounts[1] = 30;
+        erc1155.safeMintBatch(address(BOB_ACCOUNT), tokenIds, amounts);
+
+        assertEq(erc1155.balanceOf(address(BOB_ACCOUNT), 2), 10);
+        assertEq(erc1155.balanceOf(address(BOB_ACCOUNT), 3), 30);
+
         //ERC-721
         (bool success, bytes memory data) = address(BOB_ACCOUNT).call{value: 0}(hex'150b7a02');
         assertTrue(success);
-        assertTrue(keccak256(data) == keccak256(bytes(hex'150b7a02')));
path: test/foundry/unit/concrete/modulemanager/TestModuleManager_FallbackHandler.t.sol
user: login: livingrockrises
body: wondering how did this pass before if the data it was returning was just a free mem pointer
created_at: 2025-03-04T13:44:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -290,20 +289,120 @@ contract TestModuleManager_FallbackHandler is TestModuleManagement_Base {
     }
 
     function test_onTokenReceived_Success() public {
-        vm.startPrank(address(ENTRYPOINT));
+        erc721.safeMint(address(BOB_ACCOUNT), 1);
+        assertEq(erc721.balanceOf(address(BOB_ACCOUNT)), 1);
+
+        erc721.safeMint(ALICE_ADDRESS, 2);
+        vm.prank(ALICE_ADDRESS);
+        erc721.safeTransfer(address(BOB_ACCOUNT), 2);
+        assertEq(erc721.ownerOf(2), address(BOB_ACCOUNT));
+
+        erc1155.safeMint(address(BOB_ACCOUNT), 1, 1);
+        assertEq(erc1155.balanceOf(address(BOB_ACCOUNT), 1), 1);
+
+        uint256[] memory tokenIds = new uint256[](2);
+        tokenIds[0] = 2;
+        tokenIds[1] = 3;
+        uint256[] memory amounts = new uint256[](2);
+        amounts[0] = 10;
+        amounts[1] = 30;
+        erc1155.safeMintBatch(address(BOB_ACCOUNT), tokenIds, amounts);
+
+        assertEq(erc1155.balanceOf(address(BOB_ACCOUNT), 2), 10);
+        assertEq(erc1155.balanceOf(address(BOB_ACCOUNT), 3), 30);
+
         //ERC-721
         (bool success, bytes memory data) = address(BOB_ACCOUNT).call{value: 0}(hex'150b7a02');
         assertTrue(success);
-        assertTrue(keccak256(data) == keccak256(bytes(hex'150b7a02')));
path: test/foundry/unit/concrete/modulemanager/TestModuleManager_FallbackHandler.t.sol
user: login: filmakarov
body: it was not a free memory pointer
it was a correct data just it was returned as bytes memory instead of bytes4
created_at: 2025-03-04T17:55:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -290,20 +289,120 @@ contract TestModuleManager_FallbackHandler is TestModuleManagement_Base {
     }
 
     function test_onTokenReceived_Success() public {
-        vm.startPrank(address(ENTRYPOINT));
+        erc721.safeMint(address(BOB_ACCOUNT), 1);
+        assertEq(erc721.balanceOf(address(BOB_ACCOUNT)), 1);
+
+        erc721.safeMint(ALICE_ADDRESS, 2);
+        vm.prank(ALICE_ADDRESS);
+        erc721.safeTransfer(address(BOB_ACCOUNT), 2);
+        assertEq(erc721.ownerOf(2), address(BOB_ACCOUNT));
+
+        erc1155.safeMint(address(BOB_ACCOUNT), 1, 1);
+        assertEq(erc1155.balanceOf(address(BOB_ACCOUNT), 1), 1);
+
+        uint256[] memory tokenIds = new uint256[](2);
+        tokenIds[0] = 2;
+        tokenIds[1] = 3;
+        uint256[] memory amounts = new uint256[](2);
+        amounts[0] = 10;
+        amounts[1] = 30;
+        erc1155.safeMintBatch(address(BOB_ACCOUNT), tokenIds, amounts);
+
+        assertEq(erc1155.balanceOf(address(BOB_ACCOUNT), 2), 10);
+        assertEq(erc1155.balanceOf(address(BOB_ACCOUNT), 3), 30);
+
         //ERC-721
         (bool success, bytes memory data) = address(BOB_ACCOUNT).call{value: 0}(hex'150b7a02');
         assertTrue(success);
-        assertTrue(keccak256(data) == keccak256(bytes(hex'150b7a02')));
path: test/foundry/unit/concrete/modulemanager/TestModuleManager_FallbackHandler.t.sol
user: login: livingrockrises
body: https://github.com/bcnmy/nexus/blob/deploy-v1.0.1/contracts/base/ModuleManager.sol#L460 
created_at: 2025-03-05T12:48:29Z
author_association: CONTRIBUTOR

PR comments:
user: login: openzeppelin-code[bot]
created_at: 2025-03-03T15:35:40Z
body: # Release v1.0.2
> Generated at commit: [13c250dd16f599c9b46b427e790dea1b61320170](https://github.com/bcnmy/nexus/pull/248/commits/13c250dd16f599c9b46b427e790dea1b61320170)
### 🚨 Report Summary
| | Severity Level | Results |
|:-|:-|:-:|
| **Contracts** | _Critical_ </br> _High_ </br> _Medium_ </br> _Low_ </br> _Note_ </br> **Total** | 1 </br> 1 </br> 0 </br> 6 </br> 27 </br> 35 |
---
<strong>For more details view the full report in [OpenZeppelin Code Inspector](https://defender.openzeppelin.com/v2/#/code/report-details/6433fd50-d916-4566-bfd8-ac8c909a21cb)</strong>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-04T17:56:24Z
body: 
## :robot: Slither Analysis Report :mag_right:

# Slither report
<details>
# Slither report

**THIS CHECKLIST IS NOT COMPLETE**. Use `--show-ignored-findings` to show all the results.
Summary
:yellow_circle:  - [locked-ether](#locked-ether) (1 results) (Medium)
 - [constable-states](#constable-states) (2 results) (Optimization)
## locked-ether
:yellow_circle: Impact: Medium
:red_circle: Confidence: High
 - [ ] ID-0
Contract locking ether found:
	Contract [NexusBootstrap]([utils/NexusBootstrap.sol#L33-L165](https://github.com/bcnmy/nexus/blob/adc9e9443ecef3403f0acd0e3a25f5333d703900/contracts/contracts/utils/NexusBootstrap.sol#L33-L165)) has payable functions:
	 - [ModuleManager.receive()]([base/ModuleManager.sol#L64](https://github.com/bcnmy/nexus/blob/adc9e9443ecef3403f0acd0e3a25f5333d703900/contracts/contracts/base/ModuleManager.sol#L64))
	 - [ModuleManager.fallback()]([base/ModuleManager.sol#L68-L70](https://github.com/bcnmy/nexus/blob/adc9e9443ecef3403f0acd0e3a25f5333d703900/contracts/contracts/base/ModuleManager.sol#L68-L70))
	But does not have a function to withdraw the ether

[utils/NexusBootstrap.sol#L33-L165](https://github.com/bcnmy/nexus/blob/adc9e9443ecef3403f0acd0e3a25f5333d703900/contracts/contracts/utils/NexusBootstrap.sol#L33-L165)


## constable-states
Impact: Optimization
:red_circle: Confidence: High
 - [ ] ID-1
[RegistryAdapter.registry]([base/RegistryAdapter.sol#L10](https://github.com/bcnmy/nexus/blob/adc9e9443ecef3403f0acd0e3a25f5333d703900/contracts/contracts/base/RegistryAdapter.sol#L10)) should be constant 

[base/RegistryAdapter.sol#L10](https://github.com/bcnmy/nexus/blob/adc9e9443ecef3403f0acd0e3a25f5333d703900/contracts/contracts/base/RegistryAdapter.sol#L10)


 - [ ] ID-2
[RegistryFactory.threshold]([factory/RegistryFactory.sol#L39](https://github.com/bcnmy/nexus/blob/adc9e9443ecef3403f0acd0e3a25f5333d703900/contracts/contracts/factory/RegistryFactory.sol#L39)) should be constant 

[factory/RegistryFactory.sol#L39](https://github.com/bcnmy/nexus/blob/adc9e9443ecef3403f0acd0e3a25f5333d703900/contracts/contracts/factory/RegistryFactory.sol#L39)
</details>
_This comment was automatically generated by the GitHub Actions workflow._

author_association: NONE