@across-protocol/contracts 5.0.6-alpha.0 → 5.0.6-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity ^0.8.0;
3
+
4
+ import "../interfaces/SpokePoolInterface.sol";
5
+
6
+ // Used for calling SpokePool.sol functions from a contract instead of an EOA. Can be used to simulate aggregator
7
+ // or pooled relayer behavior. Makes all calls from constructor to make sure SpokePool is not relying on checking the
8
+ // caller's code size which is 0 at construction time.
9
+
10
+ contract MockCaller {
11
+ constructor(
12
+ address _spokePool,
13
+ uint32 rootBundleId,
14
+ SpokePoolInterface.RelayerRefundLeaf memory relayerRefundLeaf,
15
+ bytes32[] memory proof
16
+ ) {
17
+ require(_spokePool != address(this), "spokePool not external");
18
+ SpokePoolInterface(_spokePool).executeRelayerRefundLeaf(rootBundleId, relayerRefundLeaf, proof);
19
+ }
20
+ }
@@ -0,0 +1,21 @@
1
+ //SPDX-License-Identifier: Unlicense
2
+ pragma solidity ^0.8.0;
3
+
4
+ import "@openzeppelin/contracts-v4/interfaces/IERC1271.sol";
5
+
6
+ import "@openzeppelin/contracts-v4/utils/cryptography/ECDSA.sol";
7
+ import "@openzeppelin/contracts-v4/access/Ownable.sol";
8
+
9
+ /**
10
+ * @title MockERC1271
11
+ * @notice Implements mocked ERC1271 contract for testing.
12
+ */
13
+ contract MockERC1271 is IERC1271, Ownable {
14
+ constructor(address originalOwner) {
15
+ transferOwnership(originalOwner);
16
+ }
17
+
18
+ function isValidSignature(bytes32 hash, bytes memory signature) public view override returns (bytes4 magicValue) {
19
+ return ECDSA.recover(hash, signature) == owner() ? this.isValidSignature.selector : bytes4(0);
20
+ }
21
+ }
@@ -0,0 +1,87 @@
1
+ //SPDX-License-Identifier: Unlicense
2
+ pragma solidity ^0.8.0;
3
+
4
+ import { IERC20Auth } from "../external/interfaces/IERC20Auth.sol";
5
+ import { ERC20Permit } from "@openzeppelin/contracts-v4/token/ERC20/extensions/ERC20Permit.sol";
6
+ import { ERC20 } from "@openzeppelin/contracts-v4/token/ERC20/ERC20.sol";
7
+ import { SignatureChecker } from "@openzeppelin/contracts-v4/utils/cryptography/SignatureChecker.sol";
8
+
9
+ /**
10
+ * @title MintableERC20
11
+ * @notice Simple mintable ERC20 with configurable decimals for testing.
12
+ */
13
+ contract MintableERC20 is ERC20 {
14
+ uint8 private _decimals;
15
+
16
+ constructor(string memory name, string memory symbol, uint8 decimals_) ERC20(name, symbol) {
17
+ _decimals = decimals_;
18
+ }
19
+
20
+ function mint(address to, uint256 amount) external returns (bool) {
21
+ _mint(to, amount);
22
+ return true;
23
+ }
24
+
25
+ function burn(uint256 amount) external {
26
+ _burn(msg.sender, amount);
27
+ }
28
+
29
+ function burnFrom(address from, uint256 amount) external returns (bool) {
30
+ _spendAllowance(from, msg.sender, amount);
31
+ _burn(from, amount);
32
+ return true;
33
+ }
34
+
35
+ // ExpandedIERC20 compatibility
36
+ function addMinter(address) external {}
37
+ function addBurner(address) external {}
38
+ function resetOwner(address) external {}
39
+
40
+ function decimals() public view override returns (uint8) {
41
+ return _decimals;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * @title MockERC20
47
+ * @notice Implements mocked ERC20 contract with various features.
48
+ */
49
+ contract MockERC20 is IERC20Auth, ERC20Permit {
50
+ bytes32 public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH =
51
+ keccak256(
52
+ "ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)"
53
+ );
54
+ // Expose the typehash in ERC20Permit.
55
+ bytes32 public constant PERMIT_TYPEHASH_EXTERNAL =
56
+ keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
57
+
58
+ constructor() ERC20Permit("MockERC20") ERC20("MockERC20", "ERC20") {}
59
+
60
+ // This does no nonce checking.
61
+ function receiveWithAuthorization(
62
+ address from,
63
+ address to,
64
+ uint256 value,
65
+ uint256 validAfter,
66
+ uint256 validBefore,
67
+ bytes32 nonce,
68
+ uint8 v,
69
+ bytes32 r,
70
+ bytes32 s
71
+ ) external {
72
+ require(validAfter <= block.timestamp && validBefore >= block.timestamp, "Invalid time bounds");
73
+ require(msg.sender == to, "Receiver not caller");
74
+ bytes memory signature = bytes.concat(r, s, bytes1(v));
75
+
76
+ bytes32 structHash = keccak256(
77
+ abi.encode(RECEIVE_WITH_AUTHORIZATION_TYPEHASH, from, to, value, validAfter, validBefore, nonce)
78
+ );
79
+ bytes32 sigHash = _hashTypedDataV4(structHash);
80
+ require(SignatureChecker.isValidSignatureNow(from, sigHash, signature), "Invalid signature");
81
+ _transfer(from, to, value);
82
+ }
83
+
84
+ function hashTypedData(bytes32 typedData) external view returns (bytes32) {
85
+ return _hashTypedDataV4(typedData);
86
+ }
87
+ }
@@ -0,0 +1,16 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity ^0.8.0;
3
+
4
+ import { IEndpoint } from "../interfaces/IOFT.sol";
5
+
6
+ contract MockEndpoint is IEndpoint {
7
+ uint32 internal _eid;
8
+
9
+ constructor(uint32 eid_) {
10
+ _eid = eid_;
11
+ }
12
+
13
+ function eid() external view override returns (uint32) {
14
+ return _eid;
15
+ }
16
+ }
@@ -0,0 +1,100 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import { HubPoolInterface } from "../interfaces/HubPoolInterface.sol";
5
+
6
+ /// @title MockHubPool for Token Relay between Layer 1 and Layer 2
7
+ /// @dev This contract acts as a mock implementation for testing purposes,
8
+ /// simulating the behavior of a hub pool that can relay tokens and messages
9
+ /// between Ethereum (Layer 1) and a Layer 2 solution like Optimism.
10
+ /// It delegates calls to an external chain adapter contract for actual execution.
11
+ contract MockHubPool {
12
+ /// @notice Emitted when the adapter contract address is changed.
13
+ event AdapterChanged(address indexed oldAdapter, address indexed newAdapter);
14
+
15
+ /// @notice The address of the contract owner, set to the deployer of the contract.
16
+ address public immutable owner;
17
+ /// @notice The address of the adapter contract responsible for handling
18
+ /// token relay and message passing to Layer 2.
19
+ address public adapter;
20
+
21
+ HubPoolInterface.RootBundle public rootBundleProposal;
22
+
23
+ /// @notice Creates a new MockHubPool and sets the owner and initial adapter.
24
+ /// @param _adapter The address of the initial adapter contract.
25
+ constructor(address _adapter) {
26
+ adapter = _adapter;
27
+ owner = msg.sender; // Set the contract deployer as the owner
28
+ }
29
+
30
+ /// @notice Ensures that a function is callable only by the contract owner.
31
+ modifier onlyOwner() {
32
+ require(msg.sender == owner, "Caller is not the owner");
33
+ _;
34
+ }
35
+
36
+ /// @notice Fallback function to receive ETH.
37
+ fallback() external payable {} // solhint-disable-line no-empty-blocks
38
+
39
+ /// @notice Receive function to handle direct ETH transfers.
40
+ receive() external payable {} // solhint-disable-line no-empty-blocks
41
+
42
+ /// @notice Changes the adapter contract address.
43
+ /// @dev This function can only be called by the contract owner.
44
+ /// @param _adapter The new adapter contract address.
45
+ function changeAdapter(address _adapter) public onlyOwner {
46
+ require(_adapter != address(0), "NO_ZERO_ADDRESS");
47
+ address _oldAdapter = adapter;
48
+ adapter = _adapter;
49
+ emit AdapterChanged(_oldAdapter, _adapter);
50
+ }
51
+
52
+ function setPendingRootBundle(HubPoolInterface.RootBundle memory _rootBundleProposal) external {
53
+ rootBundleProposal = _rootBundleProposal;
54
+ }
55
+
56
+ /// @notice Relays tokens from L1 to L2 using the adapter contract.
57
+ /// @dev This function delegates the call to the adapter contract.
58
+ /// @param l1Token The address of the L1 token to relay.
59
+ /// @param l2Token The address of the L2 token to receive.
60
+ /// @param amount The amount of tokens to relay.
61
+ /// @param to The address on L2 to receive the tokens.
62
+ function relayTokens(address l1Token, address l2Token, uint256 amount, address to) external {
63
+ (bool success, ) = adapter.delegatecall(
64
+ abi.encodeWithSignature(
65
+ "relayTokens(address,address,uint256,address)",
66
+ l1Token, // l1Token.
67
+ l2Token, // l2Token.
68
+ amount, // amount.
69
+ to // to. This should be the spokePool.
70
+ )
71
+ );
72
+ require(success, "delegatecall failed");
73
+ }
74
+
75
+ /// @notice Queries the balance of a user for a specific L2 token using the adapter contract.
76
+ /// @dev This function delegates the call to the adapter contract to execute the balance check.
77
+ /// @param l2Token The address of the L2 token.
78
+ /// @param user The user address whose balance is being queried.
79
+ function balanceOf(address l2Token, address user) external {
80
+ (bool success, ) = adapter.delegatecall(
81
+ abi.encodeWithSignature(
82
+ "relayMessage(address,bytes)",
83
+ l2Token,
84
+ abi.encodeWithSignature("balanceOf(address)", user) // message
85
+ )
86
+ );
87
+ require(success, "delegatecall failed");
88
+ }
89
+
90
+ /// @notice Relays an arbitrary message to the L2 chain via the adapter contract.
91
+ /// @dev This function delegates the call to the adapter contract to execute the message relay.
92
+ /// @param target The address of the target contract on L2.
93
+ /// @param l2CallData The calldata to send to the target contract on L2. (must be abi encoded)
94
+ function arbitraryMessage(address target, bytes memory l2CallData) external {
95
+ (bool success, ) = adapter.delegatecall(
96
+ abi.encodeWithSignature("relayMessage(address,bytes)", target, l2CallData)
97
+ );
98
+ require(success, "delegatecall failed");
99
+ }
100
+ }
@@ -0,0 +1,88 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity ^0.8.0;
3
+
4
+ import "../interfaces/IOFT.sol";
5
+
6
+ /**
7
+ * @notice Facilitate bridging tokens via LayerZero's OFT.
8
+ * @dev This contract is intended to be inherited by other chain-specific adapters and spoke pools.
9
+ * @custom:security-contact bugs@across.to
10
+ */
11
+ contract MockOFTMessenger is IOFT, IOAppCore {
12
+ address public token;
13
+ uint256 public nativeFee;
14
+ uint256 public lzFee;
15
+ uint256 public amountSentLDToReturn;
16
+ uint256 public amountReceivedLDToReturn;
17
+ bool public useCustomReceipt;
18
+
19
+ // IOAppCore endpoint for tests that require endpoint().eid()
20
+ IEndpoint public endpoint_;
21
+
22
+ // Captured call data for assertions in tests
23
+ SendParam public lastSendParam;
24
+ MessagingFee public lastFee;
25
+ address public lastRefundAddress;
26
+ uint256 public lastMsgValue;
27
+ uint256 public sendCallCount;
28
+
29
+ constructor(address _token) {
30
+ token = _token;
31
+ }
32
+
33
+ // Test helper to set the endpoint used by IOAppCore
34
+ function setEndpoint(address endpointAddr) external {
35
+ endpoint_ = IEndpoint(endpointAddr);
36
+ }
37
+
38
+ // IOAppCore
39
+ function endpoint() external view returns (IEndpoint iEndpoint) {
40
+ return endpoint_;
41
+ }
42
+
43
+ function sharedDecimals() external pure returns (uint8) {
44
+ return 6;
45
+ }
46
+
47
+ // IOFT
48
+ function quoteSend(
49
+ SendParam calldata /*_sendParam*/,
50
+ bool /*_payInLzToken*/
51
+ ) external view returns (MessagingFee memory) {
52
+ return MessagingFee(nativeFee, lzFee);
53
+ }
54
+
55
+ function send(
56
+ SendParam calldata _sendParam,
57
+ MessagingFee calldata _fee,
58
+ address _refundAddress
59
+ ) external payable returns (MessagingReceipt memory, OFTReceipt memory) {
60
+ lastSendParam = _sendParam;
61
+ lastFee = _fee;
62
+ lastRefundAddress = _refundAddress;
63
+ lastMsgValue = msg.value;
64
+ sendCallCount++;
65
+ if (useCustomReceipt) {
66
+ return (
67
+ MessagingReceipt(0, 0, MessagingFee(0, 0)),
68
+ OFTReceipt(amountSentLDToReturn, amountReceivedLDToReturn)
69
+ );
70
+ }
71
+ return (MessagingReceipt(0, 0, MessagingFee(0, 0)), OFTReceipt(_sendParam.amountLD, _sendParam.amountLD));
72
+ }
73
+
74
+ function setLDAmountsToReturn(uint256 _amountSentLD, uint256 _amountReceivedLD) external {
75
+ amountSentLDToReturn = _amountSentLD;
76
+ amountReceivedLDToReturn = _amountReceivedLD;
77
+ useCustomReceipt = true;
78
+ }
79
+
80
+ function resetReceipt() external {
81
+ useCustomReceipt = false;
82
+ }
83
+
84
+ function setFeesToReturn(uint256 _nativeFee, uint256 _lzFee) external {
85
+ nativeFee = _nativeFee;
86
+ lzFee = _lzFee;
87
+ }
88
+ }
@@ -0,0 +1,22 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity ^0.8.0;
3
+ import "../spoke-pools/Ovm_SpokePool.sol";
4
+
5
+ /**
6
+ * @notice Mock Optimism Spoke pool allowing deployer to override constructor params.
7
+ */
8
+ contract MockOptimism_SpokePool is Ovm_SpokePool {
9
+ /// @custom:oz-upgrades-unsafe-allow constructor
10
+ constructor(
11
+ address _wrappedNativeTokenAddress
12
+ ) Ovm_SpokePool(_wrappedNativeTokenAddress, 1 hours, 9 hours, IERC20(address(0)), ITokenMessenger(address(0))) {} // solhint-disable-line no-empty-blocks
13
+
14
+ function initialize(
15
+ address l2Eth,
16
+ uint32 _initialDepositId,
17
+ address _crossDomainAdmin,
18
+ address _hubPool
19
+ ) public initializer {
20
+ __OvmSpokePool_init(_initialDepositId, _crossDomainAdmin, _hubPool, l2Eth);
21
+ }
22
+ }
@@ -0,0 +1,188 @@
1
+ pragma solidity ^0.8.0;
2
+
3
+ import { IPermit2 } from "../external/interfaces/IPermit2.sol";
4
+ import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol";
5
+ import { SafeERC20 } from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol";
6
+ import { IERC1271 } from "@openzeppelin/contracts-v4/interfaces/IERC1271.sol";
7
+ import { EIP712 } from "@openzeppelin/contracts-v4/utils/cryptography/EIP712.sol";
8
+
9
+ // Taken from https://github.com/Uniswap/permit2/blob/main/src/EIP712.sol
10
+ contract Permit2EIP712 {
11
+ bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
12
+ uint256 private immutable _CACHED_CHAIN_ID;
13
+
14
+ bytes32 private constant _HASHED_NAME = keccak256("Permit2");
15
+ bytes32 private constant _TYPE_HASH =
16
+ keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
17
+
18
+ constructor() {
19
+ _CACHED_CHAIN_ID = block.chainid;
20
+ _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME);
21
+ }
22
+
23
+ function DOMAIN_SEPARATOR() public view returns (bytes32) {
24
+ return
25
+ block.chainid == _CACHED_CHAIN_ID
26
+ ? _CACHED_DOMAIN_SEPARATOR
27
+ : _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME);
28
+ }
29
+
30
+ function _buildDomainSeparator(bytes32 typeHash, bytes32 nameHash) private view returns (bytes32) {
31
+ return keccak256(abi.encode(typeHash, nameHash, block.chainid, address(this)));
32
+ }
33
+
34
+ function _hashTypedData(bytes32 dataHash) internal view returns (bytes32) {
35
+ return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), dataHash));
36
+ }
37
+ }
38
+
39
+ contract MockPermit2 is IPermit2, Permit2EIP712 {
40
+ using SafeERC20 for IERC20;
41
+
42
+ mapping(address => mapping(uint256 => uint256)) public nonceBitmap;
43
+ mapping(address => mapping(address => mapping(address => uint256))) public allowance;
44
+
45
+ bytes32 public constant _TOKEN_PERMISSIONS_TYPEHASH = keccak256("TokenPermissions(address token,uint256 amount)");
46
+ string public constant _PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB =
47
+ "PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,";
48
+
49
+ error SignatureExpired();
50
+ error InvalidAmount();
51
+ error InvalidNonce();
52
+ error AllowanceExpired();
53
+ error InsufficientAllowance();
54
+
55
+ function permitWitnessTransferFrom(
56
+ PermitTransferFrom memory _permit,
57
+ SignatureTransferDetails calldata transferDetails,
58
+ address owner,
59
+ bytes32 witness,
60
+ string calldata witnessTypeString,
61
+ bytes calldata signature
62
+ ) external override {
63
+ _permitTransferFrom(
64
+ _permit,
65
+ transferDetails,
66
+ owner,
67
+ hashWithWitness(_permit, witness, witnessTypeString),
68
+ signature
69
+ );
70
+ }
71
+
72
+ function transferFrom(address from, address to, uint160 amount, address token) external {
73
+ _transfer(from, to, amount, token);
74
+ }
75
+
76
+ // This is not a copy of permit2's permit.
77
+ function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external {
78
+ if (block.timestamp > permitSingle.sigDeadline) revert SignatureExpired();
79
+
80
+ // Verify the signer address from the signature.
81
+ SignatureVerification.verify(signature, _hashTypedData(keccak256(abi.encode(permitSingle))), owner);
82
+
83
+ allowance[owner][permitSingle.details.token][permitSingle.spender] = permitSingle.details.amount;
84
+ }
85
+
86
+ // This is not a copy of permit2's permit.
87
+ function _transfer(address from, address to, uint160 amount, address token) private {
88
+ uint256 allowed = allowance[from][token][msg.sender];
89
+
90
+ if (allowed != type(uint160).max) {
91
+ if (amount > allowed) {
92
+ revert InsufficientAllowance();
93
+ } else {
94
+ unchecked {
95
+ allowance[from][token][msg.sender] = uint160(allowed) - amount;
96
+ }
97
+ }
98
+ }
99
+
100
+ // Transfer the tokens from the from address to the recipient.
101
+ IERC20(token).safeTransferFrom(from, to, amount);
102
+ }
103
+
104
+ function _permitTransferFrom(
105
+ PermitTransferFrom memory _permit,
106
+ SignatureTransferDetails calldata transferDetails,
107
+ address owner,
108
+ bytes32 dataHash,
109
+ bytes calldata signature
110
+ ) private {
111
+ uint256 requestedAmount = transferDetails.requestedAmount;
112
+
113
+ if (block.timestamp > _permit.deadline) revert SignatureExpired();
114
+ if (requestedAmount > _permit.permitted.amount) revert InvalidAmount();
115
+
116
+ _useUnorderedNonce(owner, _permit.nonce);
117
+
118
+ SignatureVerification.verify(signature, _hashTypedData(dataHash), owner);
119
+
120
+ IERC20(_permit.permitted.token).safeTransferFrom(owner, transferDetails.to, requestedAmount);
121
+ }
122
+
123
+ function bitmapPositions(uint256 nonce) private pure returns (uint256 wordPos, uint256 bitPos) {
124
+ wordPos = uint248(nonce >> 8);
125
+ bitPos = uint8(nonce);
126
+ }
127
+
128
+ function _useUnorderedNonce(address from, uint256 nonce) internal {
129
+ (uint256 wordPos, uint256 bitPos) = bitmapPositions(nonce);
130
+ uint256 bit = 1 << bitPos;
131
+ uint256 flipped = nonceBitmap[from][wordPos] ^= bit;
132
+
133
+ if (flipped & bit == 0) revert InvalidNonce();
134
+ }
135
+
136
+ function hashWithWitness(
137
+ PermitTransferFrom memory _permit,
138
+ bytes32 witness,
139
+ string calldata witnessTypeString
140
+ ) internal view returns (bytes32) {
141
+ bytes32 typeHash = keccak256(abi.encodePacked(_PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB, witnessTypeString));
142
+
143
+ bytes32 tokenPermissionsHash = _hashTokenPermissions(_permit.permitted);
144
+ return
145
+ keccak256(abi.encode(typeHash, tokenPermissionsHash, msg.sender, _permit.nonce, _permit.deadline, witness));
146
+ }
147
+
148
+ function _hashTokenPermissions(TokenPermissions memory permitted) private pure returns (bytes32) {
149
+ return keccak256(abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, permitted));
150
+ }
151
+ }
152
+
153
+ // Taken from https://github.com/Uniswap/permit2/blob/main/src/libraries/SignatureVerification.sol
154
+ library SignatureVerification {
155
+ error InvalidSignatureLength();
156
+ error InvalidSignature();
157
+ error InvalidSigner();
158
+ error InvalidContractSignature();
159
+
160
+ bytes32 constant UPPER_BIT_MASK = (0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
161
+
162
+ function verify(bytes calldata signature, bytes32 hash, address claimedSigner) internal view {
163
+ bytes32 r;
164
+ bytes32 s;
165
+ uint8 v;
166
+
167
+ if (claimedSigner.code.length == 0) {
168
+ if (signature.length == 65) {
169
+ (r, s) = abi.decode(signature, (bytes32, bytes32));
170
+ v = uint8(signature[64]);
171
+ } else if (signature.length == 64) {
172
+ // EIP-2098
173
+ bytes32 vs;
174
+ (r, vs) = abi.decode(signature, (bytes32, bytes32));
175
+ s = vs & UPPER_BIT_MASK;
176
+ v = uint8(uint256(vs >> 255)) + 27;
177
+ } else {
178
+ revert InvalidSignatureLength();
179
+ }
180
+ address signer = ecrecover(hash, v, r, s);
181
+ if (signer == address(0)) revert InvalidSignature();
182
+ if (signer != claimedSigner) revert InvalidSigner();
183
+ } else {
184
+ bytes4 magicValue = IERC1271(claimedSigner).isValidSignature(hash, signature);
185
+ if (magicValue != IERC1271.isValidSignature.selector) revert InvalidContractSignature();
186
+ }
187
+ }
188
+ }