• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

safe-global / safe-modules / 6447418651

08 Oct 2023 11:52AM UTC coverage: 71.519% (-0.6%) from 72.152%
6447418651

Pull #90

github

rmeissner
Minor refactortings
Pull Request #90: Closes #83: Add integration tests

26 of 48 branches covered (0.0%)

Branch coverage included in aggregate %.

7 of 7 new or added lines in 2 files covered. (100.0%)

87 of 110 relevant lines covered (79.09%)

3.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.87
/4337/contracts/EIP4337Module.sol
1
// SPDX-License-Identifier: LGPL-3.0-only
2
pragma solidity >=0.8.0 <0.9.0;
3

4
import {HandlerContext} from "@safe-global/safe-contracts/contracts/handler/HandlerContext.sol";
5
import {CompatibilityFallbackHandler} from "@safe-global/safe-contracts/contracts/handler/CompatibilityFallbackHandler.sol";
6
import {UserOperation, UserOperationLib} from "./UserOperation.sol";
7
import {INonceManager} from "./interfaces/ERC4337.sol";
8
import {ISafe} from "./interfaces/Safe.sol";
9

10
/// @title EIP4337Module
11
abstract contract EIP4337Module is HandlerContext, CompatibilityFallbackHandler {
12
    using UserOperationLib for UserOperation;
13

14
    // value in case of signature failure, with no time-range.
15
    // equivalent to _packValidationData(true,0,0);
16
    uint256 internal constant SIG_VALIDATION_FAILED = 1;
17

18
    bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
19

20
    bytes32 private constant SAFE_OP_TYPEHASH =
21
        keccak256(
22
            "SafeOp(address safe,bytes callData,uint256 nonce,uint256 preVerificationGas,uint256 verificationGasLimit,uint256 callGasLimit,uint256 maxFeePerGas,uint256 maxPriorityFeePerGas,address entryPoint)"
23
        );
24

25
    address public immutable supportedEntryPoint;
26
    bytes4 public immutable expectedExecutionFunctionId;
27

28
    constructor(address entryPoint, bytes4 executionFunctionId) {
29
        supportedEntryPoint = entryPoint;
14✔
30
        expectedExecutionFunctionId = executionFunctionId;
14✔
31
    }
32

33
    /// @dev Validates user operation provided by the entry point
34
    /// @param userOp User operation struct
35
    /// @param requiredPrefund Required prefund to execute the operation
36
    function validateUserOp(
37
        UserOperation calldata userOp,
38
        bytes32,
39
        uint256 requiredPrefund
40
    ) external returns (uint256 validationResult) {
41
        address payable safeAddress = payable(userOp.sender);
4✔
42
        // The entryPoint address is appended to the calldata in `HandlerContext` contract
43
        // Because of this, the relayer may manipulate the entryPoint address, therefore we have to verify that
44
        // the sender is the Safe specified in the userOperation
45
        require(safeAddress == msg.sender, "Invalid Caller");
4!
46

47
        validateReplayProtection(userOp);
4✔
48

49
        require(expectedExecutionFunctionId == bytes4(userOp.callData), "Unsupported execution function id");
4!
50

51
        address entryPoint = _msgSender();
4✔
52
        require(entryPoint == supportedEntryPoint, "Unsupported entry point");
4!
53
        validationResult = validateSignatures(entryPoint, userOp);
4✔
54

55
        // We trust the entrypoint to set the correct prefund value, based on the operation params
56
        // We need to perform this even if the signature is not valid, else the simulation function of the Entrypoint will not work
57
        if (requiredPrefund != 0) {
4✔
58
            ISafe(safeAddress).execTransactionFromModule(entryPoint, requiredPrefund, "", 0);
2✔
59
        }
60
    }
61

62
    function validateReplayProtection(UserOperation calldata userOp) internal virtual;
63

64
    function domainSeparator() public view returns (bytes32) {
65
        return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, block.chainid, this));
5✔
66
    }
67

68
    /// @dev Returns the bytes that are hashed to be signed by owners.
69
    /// @param safe Safe address
70
    /// @param callData Call data
71
    /// @param nonce Nonce of the operation
72
    /// @param preVerificationGas Gas required for pre-verification (e.g. for EOA signature verification)
73
    /// @param verificationGasLimit Gas required for verification
74
    /// @param callGasLimit Gas available during the execution of the call
75
    /// @param maxFeePerGas Max fee per gas
76
    /// @param maxPriorityFeePerGas Max priority fee per gas
77
    /// @param entryPoint Address of the entry point
78
    /// @return Operation hash bytes
79
    function getOperationHash(
80
        address safe,
81
        bytes calldata callData,
82
        uint256 nonce,
83
        uint256 preVerificationGas,
84
        uint256 verificationGasLimit,
85
        uint256 callGasLimit,
86
        uint256 maxFeePerGas,
87
        uint256 maxPriorityFeePerGas,
88
        address entryPoint
89
    ) public view returns (bytes32) {
90
        bytes32 safeOperationHash = keccak256(
5✔
91
            abi.encode(
92
                SAFE_OP_TYPEHASH,
93
                safe,
94
                keccak256(callData),
95
                nonce,
96
                preVerificationGas,
97
                verificationGasLimit,
98
                callGasLimit,
99
                maxFeePerGas,
100
                maxPriorityFeePerGas,
101
                entryPoint
102
            )
103
        );
104
        return keccak256(abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator(), safeOperationHash));
5✔
105
    }
106

107
    /// @dev Validates that the user operation is correctly signed. Users methods from Safe contract, reverts if signatures are invalid
108
    /// @param entryPoint Address of the entry point
109
    /// @param userOp User operation struct
110
    function validateSignatures(address entryPoint, UserOperation calldata userOp) internal view returns (uint256) {
111
        bytes32 operationHash = getOperationHash(
4✔
112
            payable(userOp.sender),
113
            userOp.callData,
114
            userOp.nonce,
115
            userOp.preVerificationGas,
116
            userOp.verificationGasLimit,
117
            userOp.callGasLimit,
118
            userOp.maxFeePerGas,
119
            userOp.maxPriorityFeePerGas,
120
            entryPoint
121
        );
122

123
        try ISafe(payable(userOp.sender)).checkSignatures(operationHash, "", userOp.signature) {
4✔
124
            return 0;
4✔
125
        } catch {
126
            return SIG_VALIDATION_FAILED;
×
127
        }
128
    }
129
}
130

131
contract Simple4337Module is EIP4337Module {
132
    constructor(address entryPoint)
133
        EIP4337Module(entryPoint, bytes4(keccak256("execTransactionFromModule(address,uint256,bytes,uint8)")))
134
    {}
135

136
    function validateReplayProtection(UserOperation calldata userOp) internal override {
137
        // The entrypoints handles the increase of the nonce
138
        // Right shifting fills up with 0s from the left
139
        uint192 key = uint192(userOp.nonce >> 64);
4✔
140
        uint256 safeNonce = INonceManager(supportedEntryPoint).getNonce(userOp.sender, key);
4✔
141

142
        // Check returned nonce against the user operation nonce
143
        require(safeNonce == userOp.nonce, "Invalid Nonce");
4!
144
    }
145
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc