Skip to content

Commit

Permalink
add proper 7739 detection
Browse files Browse the repository at this point in the history
  • Loading branch information
Filipp Makarov authored and Filipp Makarov committed Nov 11, 2024
1 parent 792a770 commit f40701f
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 12 deletions.
22 changes: 13 additions & 9 deletions contracts/Nexus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
95 changes: 95 additions & 0 deletions contracts/mocks/MockValidator_7739v2.sol
Original file line number Diff line number Diff line change
@@ -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];
}
}
3 changes: 2 additions & 1 deletion contracts/types/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
);
}

Expand Down
2 changes: 1 addition & 1 deletion test/foundry/utils/TestHelper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down

0 comments on commit f40701f

Please sign in to comment.