@@ -14,9 +14,10 @@ import { MerkleTreeLib } from "solady/utils/MerkleTreeLib.sol";
1414import { CopyUserOpLib } from "../../../util/CopyUserOpLib.sol " ;
1515import {
1616 DecodedSafeAccountSignatureFull,
17+ DecodedSafeAccountSignatureShort,
1718 SafeTxnData
1819} from "contracts/lib/stx-validator/validation-modes/SafeAccountValidatorLib.sol " ;
19- import { SIG_TYPE_SAFE_ACCOUNT } from "contracts/types/Constants.sol " ;
20+ import { SIG_TYPE_SAFE_ACCOUNT, ERC1271_SUCCESS } from "contracts/types/Constants.sol " ;
2021import { console2 } from "forge-std/console2.sol " ;
2122
2223contract MeeK1Validator_SafeAcc_Mode_Test_Fork is MeeK1Validator_Base_Test {
@@ -51,7 +52,7 @@ contract MeeK1Validator_SafeAcc_Mode_Test_Fork is MeeK1Validator_Base_Test {
5152
5253 uint256 bicoPrivateKey = vm.envUint ("TESTNET_PRIVATE_KEY " );
5354 signer1 = vm.createWallet (uint256 (0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356 )); // 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955
54- signer2 = vm.createWallet (bicoPrivateKey);
55+ signer2 = vm.createWallet (bicoPrivateKey); // 0x531b827c1221EC7CE13266e8F5CB1ec6Ae470be5
5556 signer3 = vm.createWallet (uint256 (0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba )); // 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc
5657
5758 vm.label (signer1.addr, "signer1 anvil " );
@@ -92,10 +93,8 @@ contract MeeK1Validator_SafeAcc_Mode_Test_Fork is MeeK1Validator_Base_Test {
9293 assertEq (erc20.balanceOf (receiver), 0 );
9394 }
9495
95- function test_superTxFlow_safeAcc_mode_ValidateUserOp_success () public {
96- // create user Ops on each chain to transfer tokens to receiver
97-
98- uint256 numOfClones = 5 ;
96+ function test_superTxFlow_safeAcc_mode_ValidateUserOp_success (uint256 numOfClones ) public {
97+ numOfClones = bound (numOfClones, 1 , 25 );
9998
10099 bytes memory innerCallData = abi.encodeWithSelector (erc20.transfer.selector , receiver, amountToTransfer);
101100
@@ -129,10 +128,7 @@ contract MeeK1Validator_SafeAcc_Mode_Test_Fork is MeeK1Validator_Base_Test {
129128 userOps[userOps_baseSepolia.length + i] = userOps_sepolia[i].deepCopy ();
130129 }
131130
132- Vm.Wallet[] memory signers = new Vm.Wallet [](3 );
133- signers[0 ] = signer1;
134- signers[1 ] = signer2;
135- signers[2 ] = signer3;
131+ Vm.Wallet[] memory signers = _getSigners ();
136132
137133 // transfer required amount of tokens to the orchestrator
138134 bytes memory safeTxnCalldata = abi.encodeWithSelector (
@@ -168,8 +164,65 @@ contract MeeK1Validator_SafeAcc_Mode_Test_Fork is MeeK1Validator_Base_Test {
168164 assertEq (erc20.balanceOf (receiver), amountToTransfer * (numOfClones + 1 ));
169165 }
170166
167+ /*
168+ function test_superTxFlow_safeAcc_mode_1271_and_WithData_success(uint256 numOfObjs) public {
169+ // Test isValidSignature and validateSignatureWithData flows for Safe Account mode
170+ // Similar to the permit mode test but using Safe Account signatures
171+ vm.selectFork(baseSepolia);
172+
173+ numOfObjs = bound(numOfObjs, 2, 25);
174+
175+ bytes[] memory meeSigs = new bytes[](numOfObjs);
176+ bytes32 baseHash = keccak256(abi.encode("test"));
177+
178+ meeSigs = _makeSafeAccSuperTxSignatures({
179+ baseHash: baseHash,
180+ total: numOfObjs,
181+ signers: _getSigners(),
182+ safeAccount: safe,
183+ safeTxnCalldata: abi.encodeWithSelector(
184+ erc20.transfer.selector,
185+ address(orchestrator),
186+ 1 ether
187+ )
188+ });
189+
190+ // Test both isValidSignature (ERC-1271) and validateSignatureWithData flows
191+ for (uint256 i = 0; i < numOfObjs; i++) {
192+ bytes32 includedLeafHash = keccak256(abi.encode(baseHash, i));
193+
194+ if (i % 2 == 0) {
195+ // Test validateSignatureWithData (stateless validator interface)
196+ // For validateSignatureWithData, we use the raw hash without smart account address
197+ assertTrue(
198+ orchestrator.validateSignatureWithData(
199+ includedLeafHash,
200+ meeSigs[i],
201+ abi.encodePacked(address(safe))
202+ )
203+ );
204+ } else {
205+ // Test isValidSignature (ERC-1271 interface)
206+ // For isValidSignature with Safe Account mode, we need to hash with the smart account address
207+ // as per K1MeeValidator line 239-244
208+ bytes32 safeHash = keccak256(abi.encodePacked(includedLeafHash, address(orchestrator)));
209+ bytes4 result = orchestrator.isValidSignature(safeHash, meeSigs[i]);
210+ assertTrue(result == ERC1271_SUCCESS);
211+ }
212+ }
213+ }
214+ */
215+
171216 // ================================ UTILS ================================
172217
218+ function _getSigners () internal view returns (Vm.Wallet[] memory ) {
219+ Vm.Wallet[] memory signers = new Vm.Wallet [](3 );
220+ signers[0 ] = signer1;
221+ signers[1 ] = signer2;
222+ signers[2 ] = signer3;
223+ return signers;
224+ }
225+
173226 function _makeSafeAccSuperTx (
174227 PackedUserOperation[] memory userOps ,
175228 Vm.Wallet[] memory signers ,
@@ -179,32 +232,36 @@ contract MeeK1Validator_SafeAcc_Mode_Test_Fork is MeeK1Validator_Base_Test {
179232 internal
180233 returns (PackedUserOperation[] memory )
181234 {
182- uint48 lowerBoundTimestamp = uint48 (block .timestamp );
183- uint48 upperBoundTimestamp = uint48 (block .timestamp + 1000 );
235+ // setting fixed timestamps
236+ // because block.timestamp turned out to be not deterministic in the multifork environment
237+ uint48 lowerBoundTimestamp = 0 ;
238+ uint48 upperBoundTimestamp = 2_763_994_636 ;
184239
185240 bytes32 [] memory leaves = new bytes32 [](userOps.length );
186241
187242 // We have to switch forks because building leaves involves getting the userOpHash, which is chain specific
188243 {
244+
245+ uint256 halfLength = userOps.length / 2 ;
246+
189247 // base sepolia
190248 vm.selectFork (baseSepolia);
191249 bytes32 [] memory leaves_baseSepolia =
192250 _buildLeavesOutOfUserOps (userOps, lowerBoundTimestamp, upperBoundTimestamp);
251+
193252 // sepolia
194253 vm.selectFork (sepolia);
195254 bytes32 [] memory leaves_sepolia =
196255 _buildLeavesOutOfUserOps (userOps, lowerBoundTimestamp, upperBoundTimestamp);
197256
198- uint256 halfLength = leaves_baseSepolia.length / 2 ;
199-
200257 // combine the leaves
201258 // we take only the first half of the leaves from base sepolia
202259 // and the second half from sepolia
203260 // because they are build with appropriate chain id for each chain
204261 for (uint256 i = 0 ; i < halfLength; i++ ) {
205262 leaves[i] = leaves_baseSepolia[i];
206263 }
207- for (uint256 i = halfLength; i < leaves_sepolia .length ; i++ ) {
264+ for (uint256 i = halfLength; i < userOps .length ; i++ ) {
208265 leaves[i] = leaves_sepolia[i];
209266 }
210267 }
@@ -284,4 +341,98 @@ contract MeeK1Validator_SafeAcc_Mode_Test_Fork is MeeK1Validator_Base_Test {
284341 }
285342 return superTxUserOps;
286343 }
344+
345+ /*
346+ function _makeSafeAccSuperTxSignatures(
347+ bytes32 baseHash,
348+ uint256 total,
349+ Vm.Wallet[] memory signers,
350+ ISafe safeAccount,
351+ bytes memory safeTxnCalldata
352+ )
353+ internal
354+ view
355+ returns (bytes[] memory)
356+ {
357+ bytes[] memory meeSigs = new bytes[](total);
358+ require(total > 0, "total must be greater than 0");
359+
360+ bytes32[] memory leaves = new bytes32[](total);
361+
362+ // Build leaves with different hashing schemes similar to permit mode
363+ for (uint256 i = 0; i < total; i++) {
364+ if (i % 2 == 0) {
365+ // For validateSignatureWithData (even indices)
366+ leaves[i] = keccak256(abi.encode(baseHash, i));
367+ } else {
368+ // For isValidSignature (odd indices) - hash with smart account address
369+ // This mimics the safe hash preparation for ERC-1271
370+ leaves[i] = keccak256(abi.encodePacked(keccak256(abi.encode(baseHash, i)), address(orchestrator)));
371+ }
372+ }
373+
374+ // Build merkle tree
375+ bytes32[] memory tree = leaves.build();
376+ bytes32 root = tree.root();
377+
378+ // Get Safe transaction parameters
379+ uint256 curNonce = safeAccount.nonce();
380+ bytes32 domainSeparator = safeAccount.domainSeparator();
381+
382+ // Create safe txn data
383+ SafeTxnData memory safeTxnData = SafeTxnData({
384+ ogDomainSeparator: domainSeparator,
385+ to: address(erc20),
386+ value: 0,
387+ data: safeTxnCalldata,
388+ operation: SafeEnumLib.Operation.Call,
389+ safeTxGas: 0,
390+ baseGas: 0,
391+ gasPrice: 0,
392+ gasToken: address(0),
393+ refundReceiver: payable(address(0)),
394+ nonce: curNonce,
395+ signatures: ""
396+ });
397+
398+ // Add the super tx root hash to the data
399+ safeTxnData.data = abi.encodePacked(safeTxnData.data, root);
400+
401+ // Get safe transaction hash
402+ bytes32 safeTxHash = ISafe(safeAccount).getTransactionHash({
403+ to: safeTxnData.to,
404+ value: safeTxnData.value,
405+ data: safeTxnData.data,
406+ operation: safeTxnData.operation,
407+ safeTxGas: safeTxnData.safeTxGas,
408+ baseGas: safeTxnData.baseGas,
409+ gasPrice: safeTxnData.gasPrice,
410+ gasToken: safeTxnData.gasToken,
411+ refundReceiver: safeTxnData.refundReceiver,
412+ _nonce: safeTxnData.nonce
413+ });
414+
415+ // Sign with all safe signers
416+ for (uint256 i = 0; i < signers.length; i++) {
417+ (uint8 v, bytes32 r, bytes32 s) = vm.sign(signers[i].privateKey, safeTxHash);
418+ safeTxnData.signatures = abi.encodePacked(safeTxnData.signatures, r, s, v);
419+ }
420+
421+ // Build signatures for each leaf
422+ for (uint256 i = 0; i < total; i++) {
423+ bytes32[] memory proof = tree.leafProof(i);
424+ bytes memory signature = abi.encodePacked(
425+ SIG_TYPE_SAFE_ACCOUNT,
426+ abi.encode(
427+ DecodedSafeAccountSignatureShort({
428+ safeTxnData: safeTxnData,
429+ proof: proof
430+ })
431+ )
432+ );
433+ meeSigs[i] = signature;
434+ }
435+ return meeSigs;
436+ }
437+ */
287438}
0 commit comments