diff --git a/contracts/Nexus.sol b/contracts/Nexus.sol index 2f33516e..16d15fc8 100644 --- a/contracts/Nexus.sol +++ b/contracts/Nexus.sol @@ -228,7 +228,9 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra function isValidSignature(bytes32 hash, bytes calldata signature) external view virtual override returns (bytes4) { // Handle potential ERC7739 support detection request if (signature.length == 0) { - if (checkERC7739Support(hash, signature)) return SUPPORTS_ERC7739; + if (uint256(hash) == (~signature.length / 0xffff) * 0x7739) { + return checkERC7739Support(hash, signature); + } } // else proceed with normal signature verification @@ -336,19 +338,21 @@ contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgra /// If no validator supports ERC-7739, this function returns false /// thus the account will proceed with normal signature verification /// and return 0xffffffff as a result. - function checkERC7739Support(bytes32 hash, bytes calldata signature) public view virtual returns (bool) { + function checkERC7739Support(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4) { + bytes4 result; unchecked { // Forces the compiler to optimize for smaller bytecode size. - if (uint256(hash) == (~signature.length / 0xffff) * 0x7739) { - SentinelListLib.SentinelList storage validators = _getAccountStorage().validators; - address next = validators.entries[SENTINEL]; - while (next != ZERO_ADDRESS && next != SENTINEL) { - if (IValidator(next).isValidSignatureWithSender(msg.sender, hash, signature) == SUPPORTS_ERC7739) return true; - next = validators.getNext(next); + SentinelListLib.SentinelList storage validators = _getAccountStorage().validators; + address next = validators.entries[SENTINEL]; + while (next != ZERO_ADDRESS && next != SENTINEL) { + bytes4 support = IValidator(next).isValidSignatureWithSender(msg.sender, hash, signature); + if (bytes2(support) == bytes2(SUPPORTS_ERC7739) && support > result) { + result = support; } + next = validators.getNext(next); } } - return false; + return result == bytes4(0) ? bytes4(0xffffffff) : result; } /// @dev Ensures that only authorized callers can upgrade the smart contract implementation. diff --git a/contracts/mocks/MockValidator_7739v2.sol b/contracts/mocks/MockValidator_7739v2.sol new file mode 100644 index 00000000..acfc86f5 --- /dev/null +++ b/contracts/mocks/MockValidator_7739v2.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { IModule } from "../interfaces/modules/IModule.sol"; +import { IModuleManager } from "../interfaces/base/IModuleManager.sol"; +import { VALIDATION_SUCCESS, VALIDATION_FAILED, MODULE_TYPE_VALIDATOR, ERC1271_MAGICVALUE, ERC1271_INVALID } from "../types/Constants.sol"; +import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol"; +import { ECDSA } from "solady/utils/ECDSA.sol"; +import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol"; +import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import { ERC7739Validator } from "erc7739Validator/ERC7739Validator.sol"; + +contract MockValidator_7739v2 is ERC7739Validator { + mapping(address => address) public smartAccountOwners; + + function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external view returns (uint256 validation) { + address owner = smartAccountOwners[msg.sender]; + return _validateSignatureForOwner(owner, userOpHash, userOp.signature) ? VALIDATION_SUCCESS : VALIDATION_FAILED; + } + + function isValidSignatureWithSender( + address sender, + bytes32 hash, + bytes calldata signature + ) external view virtual returns (bytes4) { + return 0x77390002; + } + + // ISessionValidator interface for smart session + function validateSignatureWithData(bytes32 hash, bytes calldata sig, bytes calldata data) external view returns (bool validSig) { + address owner = address(bytes20(data[0:20])); + return _validateSignatureForOwner(owner, hash, sig); + } + + function _validateSignatureForOwner(address owner, bytes32 hash, bytes calldata signature) internal view returns (bool) { + if (SignatureCheckerLib.isValidSignatureNowCalldata(owner, hash, signature)) { + return true; + } + if (SignatureCheckerLib.isValidSignatureNowCalldata(owner, MessageHashUtils.toEthSignedMessageHash(hash), signature)) { + return true; + } + return false; + } + + /// @dev Returns whether the `hash` and `signature` are valid. + /// Obtains the authorized signer's credentials and calls some + /// module's specific internal function to validate the signature + /// against credentials. + function _erc1271IsValidSignatureNowCalldata(bytes32 hash, bytes calldata signature) internal view override returns (bool) { + // obtain credentials + address owner = smartAccountOwners[msg.sender]; + + // call custom internal function to validate the signature against credentials + return _validateSignatureForOwner(owner, hash, signature); + } + + /// @dev Returns whether the `sender` is considered safe, such + /// that we don't need to use the nested EIP-712 workflow. + /// See: https://mirror.xyz/curiousapple.eth/pFqAdW2LiJ-6S4sg_u1z08k4vK6BCJ33LcyXpnNb8yU + // The canonical `MulticallerWithSigner` at 0x000000000000D9ECebf3C23529de49815Dac1c4c + // is known to include the account in the hash to be signed. + // msg.sender = Smart Account + // sender = 1271 og request sender + function _erc1271CallerIsSafe(address sender) internal view virtual override returns (bool) { + return (sender == 0x000000000000D9ECebf3C23529de49815Dac1c4c || // MulticallerWithSigner + sender == msg.sender); + } + + function onInstall(bytes calldata data) external { + require(IModuleManager(msg.sender).isModuleInstalled(MODULE_TYPE_VALIDATOR, address(this), ""), "Validator is still installed"); + smartAccountOwners[msg.sender] = address(bytes20(data)); + } + + function onUninstall(bytes calldata data) external { + data; + require(!IModuleManager(msg.sender).isModuleInstalled(MODULE_TYPE_VALIDATOR, address(this), ""), "Validator is still installed"); + delete smartAccountOwners[msg.sender]; + } + + function isModuleType(uint256 moduleTypeId) external pure returns (bool) { + return moduleTypeId == MODULE_TYPE_VALIDATOR; + } + + function isOwner(address account, address owner) external view returns (bool) { + return smartAccountOwners[account] == owner; + } + + function isInitialized(address) external pure returns (bool) { + return false; + } + + function getOwner(address account) external view returns (address) { + return smartAccountOwners[account]; + } +} diff --git a/contracts/types/Constants.sol b/contracts/types/Constants.sol index 6371a154..22e00ba0 100644 --- a/contracts/types/Constants.sol +++ b/contracts/types/Constants.sol @@ -46,4 +46,5 @@ bytes32 constant MODULE_ENABLE_MODE_TYPE_HASH = keccak256(bytes(MODULE_ENABLE_MO bytes1 constant MODE_VALIDATION = 0x00; bytes1 constant MODE_MODULE_ENABLE = 0x01; -bytes4 constant SUPPORTS_ERC7739 = 0x77390001; +bytes4 constant SUPPORTS_ERC7739 = 0x77390000; +bytes4 constant SUPPORTS_ERC7739_V1 = 0x77390001; \ No newline at end of file diff --git a/test/foundry/unit/concrete/erc1271/TestERC1271Account_IsValidSignature.t.sol b/test/foundry/unit/concrete/erc1271/TestERC1271Account_IsValidSignature.t.sol index a8713ad5..e7142002 100644 --- a/test/foundry/unit/concrete/erc1271/TestERC1271Account_IsValidSignature.t.sol +++ b/test/foundry/unit/concrete/erc1271/TestERC1271Account_IsValidSignature.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.27; import "../../../utils/Imports.sol"; import "../../../utils/NexusTest_Base.t.sol"; +import "contracts/mocks/MockValidator_7739v2.sol"; /// @title TestERC1271Account_IsValidSignature /// @notice This contract tests the ERC1271 signature validation functionality. @@ -126,12 +127,16 @@ contract TestERC1271Account_IsValidSignature is NexusTest_Base { /// @notice Tests the ERC7739 support detection request. function test_ERC7739SupportDetectionRequest() public { + MockValidator_7739v2 validator_7739v2 = new MockValidator_7739v2(); + vm.prank(address(ENTRYPOINT)); + ALICE_ACCOUNT.installModule(MODULE_TYPE_VALIDATOR, address(validator_7739v2), abi.encodePacked(ALICE_ADDRESS)); + assertTrue(ALICE_ACCOUNT.isModuleInstalled(MODULE_TYPE_VALIDATOR, address(validator_7739v2), "")); assertEq( ALICE_ACCOUNT.isValidSignature( 0x7739773977397739773977397739773977397739773977397739773977397739, "" ), - SUPPORTS_ERC7739 + bytes4(0x77390002) // SUPPORTS_ERC7739_V2 ); } diff --git a/test/foundry/utils/TestHelper.t.sol b/test/foundry/utils/TestHelper.t.sol index 5419eb08..e6154aa6 100644 --- a/test/foundry/utils/TestHelper.t.sol +++ b/test/foundry/utils/TestHelper.t.sol @@ -23,7 +23,7 @@ import { NexusBootstrap, BootstrapConfig } from "../../../contracts/utils/NexusB import { BiconomyMetaFactory } from "../../../contracts/factory/BiconomyMetaFactory.sol"; import { NexusAccountFactory } from "../../../contracts/factory/NexusAccountFactory.sol"; import { BootstrapLib } from "../../../contracts/lib/BootstrapLib.sol"; -import { MODE_VALIDATION } from "../../../contracts/types/Constants.sol"; +import { MODE_VALIDATION, SUPPORTS_ERC7739_V1 } from "../../../contracts/types/Constants.sol"; import { MockRegistry } from "../../../contracts/mocks/MockRegistry.sol"; import { HelperConfig } from "../../../scripts/foundry/HelperConfig.s.sol";