Skip to content

Commit fded39b

Browse files
Filipp MakarovFilipp Makarov
authored andcommitted
chore: add test
1 parent 2ff431d commit fded39b

3 files changed

Lines changed: 178 additions & 25 deletions

File tree

foundry.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
bytecode_hash = "none"
77
evm_version = "cancun" # See https://www.evmdiff.com/features?name=PUSH0&kind=opcode
88
fuzz = { runs = 1_000 }
9-
via-ir = true
9+
via-ir = false
1010
gas_reports = ["*"]
1111
optimizer = true
1212
optimizer_runs = 999
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.27;
4+
5+
import { Vm, console2 } from "forge-std/Test.sol";
6+
import { PackedUserOperation } from "account-abstraction/core/UserOperationLib.sol";
7+
import { MeeK1Validator_Base_Test } from "../MeeK1Validator_Base_Test.t.sol";
8+
import { MockTarget } from "../../../mock/MockTarget.sol";
9+
import { MockAccount } from "../../../mock/accounts/MockAccount.sol";
10+
import { CopyUserOpLib } from "../../../util/CopyUserOpLib.sol";
11+
import { HashLib, SUPER_TX_MEE_USER_OP_ARRAY_TYPEHASH } from "contracts/lib/stx-validator/HashLib.sol";
12+
import { MEEUserOpHashLib } from "contracts/lib/stx-validator/MEEUserOpHashLib.sol";
13+
import "contracts/types/Constants.sol";
14+
import { EfficientHashLib } from "solady/utils/EfficientHashLib.sol";
15+
16+
/**
17+
* @title MeeK1Validator_Simple_Mode_Multichain_Test
18+
* @notice Tests that a 712 simple mode signature is valid on all chains
19+
* @dev The signature is valid because the domain separator is the same for all chains
20+
* (has no chain id, address, or version in the simple mode implementation)
21+
*/
22+
contract MeeK1Validator_Simple_Mode_Multichain_Test is MeeK1Validator_Base_Test {
23+
using CopyUserOpLib for PackedUserOperation;
24+
using EfficientHashLib for *;
25+
26+
// Chain IDs for testing
27+
uint256 constant CHAIN_1 = 1; // Ethereum mainnet
28+
uint256 constant CHAIN_2 = 137; // Polygon
29+
30+
// Mock accounts per chain
31+
MockAccount mockAccountChain1;
32+
MockAccount mockAccountChain2;
33+
34+
// Mock targets per chain
35+
MockTarget mockTargetChain1;
36+
MockTarget mockTargetChain2;
37+
38+
function setUp() public virtual override {
39+
super.setUp();
40+
}
41+
42+
/**
43+
* @notice Test with a more realistic scenario: single signature for all chains
44+
* @dev This test demonstrates that in simple mode, one signature can validate across chains
45+
* because the domain separator doesn't include chainId
46+
*/
47+
function test_simple_mode_single_signature_multiple_chains() public {
48+
// Save original chain ID
49+
uint256 originalChainId = block.chainid;
50+
51+
// ============ DEPLOY ON MULTIPLE CHAINS ============
52+
vm.chainId(CHAIN_1);
53+
mockTargetChain1 = new MockTarget();
54+
mockAccountChain1 = deployMockAccount({ validator: address(k1MeeValidator), handler: address(0) });
55+
vm.prank(address(mockAccountChain1));
56+
k1MeeValidator.transferOwnership(wallet.addr);
57+
58+
vm.chainId(CHAIN_2);
59+
mockTargetChain2 = new MockTarget();
60+
mockAccountChain2 = deployMockAccount({ validator: address(k1MeeValidator), handler: address(0) });
61+
vm.prank(address(mockAccountChain2));
62+
k1MeeValidator.transferOwnership(wallet.addr);
63+
64+
//make sure the accounts are deployed to the different addresses
65+
assertTrue(
66+
address(mockAccountChain1) != address(mockAccountChain2), "Chain 1 and Chain 2 accounts should be different"
67+
);
68+
69+
// ============ CREATE USER OPS ============
70+
uint48 lowerBoundTimestamp = uint48(block.timestamp);
71+
uint48 upperBoundTimestamp = uint48(block.timestamp + 1000);
72+
73+
vm.chainId(CHAIN_1);
74+
bytes memory innerCallDataChain1 = abi.encodeWithSelector(MockTarget.incrementCounter.selector);
75+
PackedUserOperation memory userOpChain1 = buildBasicMEEUserOpWithCalldata({
76+
callData: abi.encodeWithSelector(
77+
mockAccountChain1.execute.selector, address(mockTargetChain1), uint256(0), innerCallDataChain1
78+
),
79+
account: address(mockAccountChain1),
80+
userOpSigner: wallet
81+
});
82+
83+
vm.chainId(CHAIN_2);
84+
bytes memory innerCallDataChain2 = abi.encodeWithSelector(MockTarget.incrementCounter.selector);
85+
PackedUserOperation memory userOpChain2 = buildBasicMEEUserOpWithCalldata({
86+
callData: abi.encodeWithSelector(
87+
mockAccountChain2.execute.selector, address(mockTargetChain2), uint256(0), innerCallDataChain2
88+
),
89+
account: address(mockAccountChain2),
90+
userOpSigner: wallet
91+
});
92+
93+
// ============ CREATE SUPER TX WITH BOTH USER OPS ============
94+
PackedUserOperation[] memory userOps = new PackedUserOperation[](2);
95+
userOps[0] = userOpChain1;
96+
userOps[1] = userOpChain2;
97+
98+
// Calculate item hashes
99+
bytes32[] memory stxItemHashes = new bytes32[](2);
100+
101+
vm.chainId(CHAIN_1);
102+
bytes32 userOpHash1 = ENTRYPOINT.getUserOpHash(userOpChain1);
103+
stxItemHashes[0] =
104+
MEEUserOpHashLib.getMeeUserOpEip712Hash(userOpHash1, lowerBoundTimestamp, upperBoundTimestamp);
105+
106+
vm.chainId(CHAIN_2);
107+
bytes32 userOpHash2 = ENTRYPOINT.getUserOpHash(userOpChain2);
108+
stxItemHashes[1] =
109+
MEEUserOpHashLib.getMeeUserOpEip712Hash(userOpHash2, lowerBoundTimestamp, upperBoundTimestamp);
110+
111+
// ============ SIGN WITH CHAIN 1'S ACCOUNT ============
112+
vm.chainId(CHAIN_1);
113+
114+
bytes32 stxStructTypeHash = SUPER_TX_MEE_USER_OP_ARRAY_TYPEHASH;
115+
116+
bytes memory encodedData;
117+
bytes32[] memory a = EfficientHashLib.malloc(stxItemHashes.length);
118+
for (uint256 i; i < stxItemHashes.length; ++i) {
119+
a.set(i, stxItemHashes[i]);
120+
}
121+
encodedData = abi.encodePacked(a.hash());
122+
bytes32 structHash = keccak256(abi.encodePacked(stxStructTypeHash, encodedData));
123+
bytes32 stxEip712HashToSign = HashLib.hashTypedDataForAccount(address(mockAccountChain1), structHash);
124+
125+
(uint8 v, bytes32 r, bytes32 s) = vm.sign(wallet.privateKey, stxEip712HashToSign);
126+
bytes memory superTxHashSignature = abi.encodePacked(r, s, v);
127+
128+
// ============ CREATE SIGNED USER OPS ============
129+
PackedUserOperation[] memory signedUserOps = new PackedUserOperation[](2);
130+
131+
signedUserOps[0] = userOpChain1.deepCopy();
132+
signedUserOps[0].signature = abi.encodePacked(
133+
SIG_TYPE_SIMPLE,
134+
abi.encode(
135+
stxStructTypeHash,
136+
uint256(0),
137+
stxItemHashes,
138+
superTxHashSignature,
139+
uint256((uint256(lowerBoundTimestamp) << 128) | uint256(upperBoundTimestamp))
140+
)
141+
);
142+
143+
signedUserOps[1] = userOpChain2.deepCopy();
144+
signedUserOps[1].signature = abi.encodePacked(
145+
SIG_TYPE_SIMPLE,
146+
abi.encode(
147+
stxStructTypeHash,
148+
uint256(1),
149+
stxItemHashes,
150+
superTxHashSignature,
151+
uint256((uint256(lowerBoundTimestamp) << 128) | uint256(upperBoundTimestamp))
152+
)
153+
);
154+
155+
// ============ EXECUTE ON EACH CHAIN ============
156+
vm.chainId(CHAIN_1);
157+
uint256 counterBeforeChain1 = mockTargetChain1.counter();
158+
vm.startPrank(MEE_NODE_EXECUTOR_EOA, MEE_NODE_EXECUTOR_EOA);
159+
PackedUserOperation[] memory userOpArrayChain1 = new PackedUserOperation[](1);
160+
userOpArrayChain1[0] = signedUserOps[0];
161+
ENTRYPOINT.handleOps(userOpArrayChain1, payable(MEE_NODE_ADDRESS));
162+
vm.stopPrank();
163+
assertEq(mockTargetChain1.counter(), counterBeforeChain1 + 1, "Chain 1 execution failed");
164+
165+
vm.chainId(CHAIN_2);
166+
uint256 counterBeforeChain2 = mockTargetChain2.counter();
167+
vm.startPrank(MEE_NODE_EXECUTOR_EOA, MEE_NODE_EXECUTOR_EOA);
168+
PackedUserOperation[] memory userOpArrayChain2 = new PackedUserOperation[](1);
169+
userOpArrayChain2[0] = signedUserOps[1];
170+
ENTRYPOINT.handleOps(userOpArrayChain2, payable(MEE_NODE_ADDRESS));
171+
vm.stopPrank();
172+
assertEq(mockTargetChain2.counter(), counterBeforeChain2 + 1, "Chain 2 execution failed");
173+
174+
// Restore original chain ID
175+
vm.chainId(originalChainId);
176+
}
177+
}

test/unit/mee-k1-validator/simple-mode/MeeK1Validator_Simple_Mode_Test.t.sol

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -99,30 +99,6 @@ contract MeeK1Validator_Simple_Mode_Test is MeeK1Validator_Base_Test {
9999
}
100100
}
101101

102-
/**
103-
*
104-
* For claude:
105-
*
106-
* I want to add a multichain test for the simple mode.
107-
* It should test that a 712 simple mode signature is valid on all chains.
108-
* It should be valid because the domain separator is the same for all chains (has no chain id, address, or version)
109-
*
110-
* Approach to take here:
111-
*
112-
* 1) use vm.chainId cheatcode to set the chain id
113-
* 2) deploy new smart account on the new chain, make sure it is deployed to the different address on the new chain,
114-
* owner should be the same across chains
115-
* 3) create a superTxn that consists of two userOps, each with a different chain id
116-
* 4) sign the superTxn as a eip-712 data structure with the owner's private key
117-
* 5) call EP.handleOps on both chains to makes sure both userOps are handled correctly
118-
*
119-
* Analyze this file, as well as the K1MEEValidatorTest.t.sol file, MeeK1Validator_Base_Test.t.sol file,
120-
* K1MeeValidator.sol, SimpleValidatorLib.sol, HashLib.sol files
121-
*
122-
*
123-
*
124-
*/
125-
126102
// ==== SIMPLE SUPER TX UTILS ====
127103

128104
/**

0 commit comments

Comments
 (0)