Skip to content

Commit 1dc3782

Browse files
Filipp MakarovFilipp Makarov
authored andcommitted
chore: validateUserOp fuzzing works
1 parent 29963cd commit 1dc3782

2 files changed

Lines changed: 167 additions & 15 deletions

File tree

test/fork/mee-k1-validator/safe-acc-mode/MeeK1Validator_SafeAcc_Mode_Test.t.sol

Lines changed: 166 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ import { MerkleTreeLib } from "solady/utils/MerkleTreeLib.sol";
1414
import { CopyUserOpLib } from "../../../util/CopyUserOpLib.sol";
1515
import {
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";
2021
import { console2 } from "forge-std/console2.sol";
2122

2223
contract 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
}

test/unit/mee-k1-validator/MeeK1Validator_Base_Test.t.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { MockAccount } from "../../mock/accounts/MockAccount.sol";
1010
import { CopyUserOpLib } from "../../util/CopyUserOpLib.sol";
1111
import { HashLib, SUPER_TX_MEE_USER_OP_ARRAY_TYPEHASH } from "contracts/lib/stx-validator/HashLib.sol";
1212
import "contracts/types/Constants.sol";
13+
import { console2 } from "forge-std/console2.sol";
1314

1415
contract MeeK1Validator_Base_Test is BaseTest {
1516
using CopyUserOpLib for PackedUserOperation;

0 commit comments

Comments
 (0)