From e713cf263680595a8b308c52023de64f10f82e0d Mon Sep 17 00:00:00 2001 From: highskore Date: Fri, 10 Jan 2025 16:48:52 +0100 Subject: [PATCH 1/5] feat(ERC4337Helpers): return logs from exec4337 --- src/test/ModuleKitHelpers.sol | 6 ++++-- src/test/RhinestoneModuleKit.sol | 6 ++++++ src/test/utils/ERC4337Helpers.sol | 20 +++++++++++++++++--- test/Diff.t.sol | 6 +++++- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/test/ModuleKitHelpers.sol b/src/test/ModuleKitHelpers.sol index f0d80417..0f0c22ff 100644 --- a/src/test/ModuleKitHelpers.sol +++ b/src/test/ModuleKitHelpers.sol @@ -5,6 +5,7 @@ pragma solidity >=0.8.23 <0.9.0; import { AccountInstance, UserOpData, + ExecutionData, AccountType, DEFAULT, SAFE, @@ -94,9 +95,10 @@ library ModuleKitHelpers { /// @notice Executes userOps on the entrypoint /// @param userOpData UserOpData struct containing the userOp, userOpHash, and entrypoint - function execUserOps(UserOpData memory userOpData) internal { + /// @return ExecutionData struct containing the logs from the execution + function execUserOps(UserOpData memory userOpData) internal returns (ExecutionData memory) { // Send userOp to entrypoint - ERC4337Helpers.exec4337(userOpData.userOp, userOpData.entrypoint); + return ERC4337Helpers.exec4337(userOpData.userOp, userOpData.entrypoint); } /// @notice Configures a userOp to execute a single operation diff --git a/src/test/RhinestoneModuleKit.sol b/src/test/RhinestoneModuleKit.sol index d4d8cd30..82393263 100644 --- a/src/test/RhinestoneModuleKit.sol +++ b/src/test/RhinestoneModuleKit.sol @@ -106,6 +106,12 @@ struct UserOpData { IEntryPoint entrypoint; } +/// @title ExecutionData +/// @param logs Execution logs +struct ExecutionData { + VmSafe.Log[] logs; +} + /// @title RhinestoneModuleKit /// @notice A development kit for building and testing smart account modules contract RhinestoneModuleKit is AuxiliaryFactory { diff --git a/src/test/utils/ERC4337Helpers.sol b/src/test/utils/ERC4337Helpers.sol index 10bd6290..627f5bbf 100644 --- a/src/test/utils/ERC4337Helpers.sol +++ b/src/test/utils/ERC4337Helpers.sol @@ -8,6 +8,7 @@ import { IEntryPointSimulations, IStakeManager } from "../../external/ERC4337.sol"; +import { ExecutionData } from "../RhinestoneModuleKit.sol"; // Deployments import { ENTRYPOINT_ADDR } from "../../deployment/predeploy/EntryPoint.sol"; @@ -45,7 +46,13 @@ library ERC4337Helpers { error InvalidRevertMessage(bytes4 expected, bytes4 reason); error InvalidRevertMessageBytes(bytes expected, bytes reason); - function exec4337(PackedUserOperation[] memory userOps, IEntryPoint onEntryPoint) internal { + function exec4337( + PackedUserOperation[] memory userOps, + IEntryPoint onEntryPoint + ) + internal + returns (ExecutionData memory executionData) + { uint256 isExpectRevert = getExpectRevert(); // ERC-4337 specs validation @@ -72,6 +79,7 @@ library ERC4337Helpers { // Parse logs and determine if a revert happened VmSafe.Log[] memory logs = getRecordedLogs(); + executionData = ExecutionData(logs); uint256 totalUserOpGas = 0; for (uint256 i; i < logs.length; i++) { // UserOperationEvent(bytes32,address,address,uint256,bool,uint256,uint256) @@ -150,10 +158,16 @@ library ERC4337Helpers { } } - function exec4337(PackedUserOperation memory userOp, IEntryPoint onEntryPoint) internal { + function exec4337( + PackedUserOperation memory userOp, + IEntryPoint onEntryPoint + ) + internal + returns (ExecutionData memory logs) + { PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; - exec4337(userOps, onEntryPoint); + return exec4337(userOps, onEntryPoint); } function getUserOpRevertReason( diff --git a/test/Diff.t.sol b/test/Diff.t.sol index 851ee6f1..a729b2a5 100644 --- a/test/Diff.t.sol +++ b/test/Diff.t.sol @@ -4,6 +4,7 @@ pragma solidity >=0.8.23 <0.9.0; import "src/ModuleKit.sol"; import "./BaseTest.t.sol"; import "src/Mocks.sol"; +import { ExecutionData } from "src/test/RhinestoneModuleKit.sol"; import { MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, @@ -100,13 +101,16 @@ contract ERC7579DifferentialModuleKitLibTest is BaseTest { // bytes memory signature = ""; // Create userOperation - instance.getExecOps({ + ExecutionData memory executionData = instance.getExecOps({ target: receiver, value: value, callData: callData, txValidator: address(instance.defaultValidator) }).execUserOps(); + // Validate Logs + assertTrue(executionData.logs.length == 5); + // Validate userOperation assertEq(receiver.balance, value, "Receiver should have 10 gwei"); } From f48a76c2eb2ba9ecbd783f8ad520b23583d61c94 Mon Sep 17 00:00:00 2001 From: highskore Date: Fri, 10 Jan 2025 16:51:26 +0100 Subject: [PATCH 2/5] fix: update expected length --- test/Diff.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Diff.t.sol b/test/Diff.t.sol index a729b2a5..5d33ebb5 100644 --- a/test/Diff.t.sol +++ b/test/Diff.t.sol @@ -109,7 +109,7 @@ contract ERC7579DifferentialModuleKitLibTest is BaseTest { }).execUserOps(); // Validate Logs - assertTrue(executionData.logs.length == 5); + assertTrue(executionData.logs.length >= 5); // Validate userOperation assertEq(receiver.balance, value, "Receiver should have 10 gwei"); From 322db8632bce5d4e1092c35553b6769034e5f8d2 Mon Sep 17 00:00:00 2001 From: highskore Date: Fri, 10 Jan 2025 17:13:07 +0100 Subject: [PATCH 3/5] chore: try skipping big test on sim --- test/integrations/SwapTest.t.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/integrations/SwapTest.t.sol b/test/integrations/SwapTest.t.sol index 1c3527e6..df1cd33f 100644 --- a/test/integrations/SwapTest.t.sol +++ b/test/integrations/SwapTest.t.sol @@ -6,6 +6,8 @@ import "src/ModuleKit.sol"; import { ERC7579ExecutorBase } from "src/Modules.sol"; import { IERC20 } from "forge-std/interfaces/IERC20.sol"; import { UniswapV3Integration } from "src/integrations/uniswap/v3/Uniswap.sol"; +import { envOr } from "src/test/utils/Vm.sol"; +import { getSimulateUserOp } from "src/test/utils/Storage.sol"; contract TestUniswap is BaseTest { using ModuleKitHelpers for AccountInstance; @@ -50,6 +52,9 @@ contract TestUniswap is BaseTest { } function testApproveAndSwap() public { + if (envOr("SIMULATE", false) || getSimulateUserOp()) { + return; + } address poolAddress = UniswapV3Integration.getPoolAddress(address(tokenA), address(tokenB)); uint160 sqrtPriceX96 = UniswapV3Integration.getSqrtPriceX96(poolAddress); From ffebac8359ee161b9d8afb2acbf87fcc0a2d4cc6 Mon Sep 17 00:00:00 2001 From: highskore Date: Fri, 10 Jan 2025 17:28:09 +0100 Subject: [PATCH 4/5] feat(simulateUserOp): simulateUserOp overwrites env --- src/test/ModuleKitHelpers.sol | 5 ++++- src/test/utils/Vm.sol | 4 ++++ test/Diff.t.sol | 1 + test/integrations/SwapTest.t.sol | 6 +----- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/test/ModuleKitHelpers.sol b/src/test/ModuleKitHelpers.sol index 0f0c22ff..00b1cb15 100644 --- a/src/test/ModuleKitHelpers.sol +++ b/src/test/ModuleKitHelpers.sol @@ -42,7 +42,8 @@ import { startStateDiffRecording as vmStartStateDiffRecording, stopAndReturnStateDiff as vmStopAndReturnStateDiff, getMappingKeyAndParentOf, - envOr + envOr, + setEnv } from "./utils/Vm.sol"; import { getAccountType as getAccountTypeFromStorage, @@ -558,6 +559,8 @@ library ModuleKitHelpers { /// @param value The value to write to storage (true or false) function simulateUserOp(AccountInstance memory, bool value) internal { writeSimulateUserOp(value); + string memory strValue = value ? "true" : "false"; + setEnv("SIMULATE", strValue); } /// @notice Writes the storage compliance flag to storage diff --git a/src/test/utils/Vm.sol b/src/test/utils/Vm.sol index abd53efd..ac2251c0 100644 --- a/src/test/utils/Vm.sol +++ b/src/test/utils/Vm.sol @@ -113,6 +113,10 @@ function envOr(string memory name, bool defaultValue) view returns (bool value) return Vm(VM_ADDR).envOr(name, defaultValue); } +function setEnv(string memory key, string memory value) { + Vm(VM_ADDR).setEnv(key, value); +} + function envBool(string memory key) view returns (bool value) { return Vm(VM_ADDR).envBool(key); } diff --git a/test/Diff.t.sol b/test/Diff.t.sol index 5d33ebb5..fed6baa3 100644 --- a/test/Diff.t.sol +++ b/test/Diff.t.sol @@ -53,6 +53,7 @@ contract ERC7579DifferentialModuleKitLibTest is BaseTest { token.initialize("Mock Token", "MTK", 18); deal(address(token), instance.account, 100 ether); vm.deal(instance.account, 1000 ether); + instance.simulateUserOp(false); } function test_transfer() public { diff --git a/test/integrations/SwapTest.t.sol b/test/integrations/SwapTest.t.sol index df1cd33f..8b20338f 100644 --- a/test/integrations/SwapTest.t.sol +++ b/test/integrations/SwapTest.t.sol @@ -6,8 +6,6 @@ import "src/ModuleKit.sol"; import { ERC7579ExecutorBase } from "src/Modules.sol"; import { IERC20 } from "forge-std/interfaces/IERC20.sol"; import { UniswapV3Integration } from "src/integrations/uniswap/v3/Uniswap.sol"; -import { envOr } from "src/test/utils/Vm.sol"; -import { getSimulateUserOp } from "src/test/utils/Storage.sol"; contract TestUniswap is BaseTest { using ModuleKitHelpers for AccountInstance; @@ -42,6 +40,7 @@ contract TestUniswap is BaseTest { _fundAccountWithTokenA(amountIn); vm.deal(instance.account, 1 ether); assertTrue(instance.account.balance == 1 ether); + instance.simulateUserOp(false); } function _fundAccountWithTokenA(uint256 amount) internal { @@ -52,9 +51,6 @@ contract TestUniswap is BaseTest { } function testApproveAndSwap() public { - if (envOr("SIMULATE", false) || getSimulateUserOp()) { - return; - } address poolAddress = UniswapV3Integration.getPoolAddress(address(tokenA), address(tokenB)); uint160 sqrtPriceX96 = UniswapV3Integration.getSqrtPriceX96(poolAddress); From 59e1c98bb41a04a15041365c5fc1b376cabc1fd0 Mon Sep 17 00:00:00 2001 From: highskore Date: Fri, 10 Jan 2025 18:22:57 +0100 Subject: [PATCH 5/5] chore: rename struct --- src/test/ModuleKitHelpers.sol | 9 ++++++--- src/test/RhinestoneModuleKit.sol | 4 ++-- src/test/utils/ERC4337Helpers.sol | 8 ++++---- test/Diff.t.sol | 4 ++-- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/test/ModuleKitHelpers.sol b/src/test/ModuleKitHelpers.sol index 00b1cb15..55252ec0 100644 --- a/src/test/ModuleKitHelpers.sol +++ b/src/test/ModuleKitHelpers.sol @@ -5,7 +5,7 @@ pragma solidity >=0.8.23 <0.9.0; import { AccountInstance, UserOpData, - ExecutionData, + ExecutionReturnData, AccountType, DEFAULT, SAFE, @@ -96,8 +96,11 @@ library ModuleKitHelpers { /// @notice Executes userOps on the entrypoint /// @param userOpData UserOpData struct containing the userOp, userOpHash, and entrypoint - /// @return ExecutionData struct containing the logs from the execution - function execUserOps(UserOpData memory userOpData) internal returns (ExecutionData memory) { + /// @return ExecutionReturnData struct containing the logs from the execution + function execUserOps(UserOpData memory userOpData) + internal + returns (ExecutionReturnData memory) + { // Send userOp to entrypoint return ERC4337Helpers.exec4337(userOpData.userOp, userOpData.entrypoint); } diff --git a/src/test/RhinestoneModuleKit.sol b/src/test/RhinestoneModuleKit.sol index 82393263..feed8c7a 100644 --- a/src/test/RhinestoneModuleKit.sol +++ b/src/test/RhinestoneModuleKit.sol @@ -106,9 +106,9 @@ struct UserOpData { IEntryPoint entrypoint; } -/// @title ExecutionData +/// @title ExecutionReturnData /// @param logs Execution logs -struct ExecutionData { +struct ExecutionReturnData { VmSafe.Log[] logs; } diff --git a/src/test/utils/ERC4337Helpers.sol b/src/test/utils/ERC4337Helpers.sol index 627f5bbf..cb5f18ae 100644 --- a/src/test/utils/ERC4337Helpers.sol +++ b/src/test/utils/ERC4337Helpers.sol @@ -8,7 +8,7 @@ import { IEntryPointSimulations, IStakeManager } from "../../external/ERC4337.sol"; -import { ExecutionData } from "../RhinestoneModuleKit.sol"; +import { ExecutionReturnData } from "../RhinestoneModuleKit.sol"; // Deployments import { ENTRYPOINT_ADDR } from "../../deployment/predeploy/EntryPoint.sol"; @@ -51,7 +51,7 @@ library ERC4337Helpers { IEntryPoint onEntryPoint ) internal - returns (ExecutionData memory executionData) + returns (ExecutionReturnData memory executionData) { uint256 isExpectRevert = getExpectRevert(); @@ -79,7 +79,7 @@ library ERC4337Helpers { // Parse logs and determine if a revert happened VmSafe.Log[] memory logs = getRecordedLogs(); - executionData = ExecutionData(logs); + executionData = ExecutionReturnData(logs); uint256 totalUserOpGas = 0; for (uint256 i; i < logs.length; i++) { // UserOperationEvent(bytes32,address,address,uint256,bool,uint256,uint256) @@ -163,7 +163,7 @@ library ERC4337Helpers { IEntryPoint onEntryPoint ) internal - returns (ExecutionData memory logs) + returns (ExecutionReturnData memory logs) { PackedUserOperation[] memory userOps = new PackedUserOperation[](1); userOps[0] = userOp; diff --git a/test/Diff.t.sol b/test/Diff.t.sol index fed6baa3..d19b5050 100644 --- a/test/Diff.t.sol +++ b/test/Diff.t.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.23 <0.9.0; import "src/ModuleKit.sol"; import "./BaseTest.t.sol"; import "src/Mocks.sol"; -import { ExecutionData } from "src/test/RhinestoneModuleKit.sol"; +import { ExecutionReturnData } from "src/test/RhinestoneModuleKit.sol"; import { MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, @@ -102,7 +102,7 @@ contract ERC7579DifferentialModuleKitLibTest is BaseTest { // bytes memory signature = ""; // Create userOperation - ExecutionData memory executionData = instance.getExecOps({ + ExecutionReturnData memory executionData = instance.getExecOps({ target: receiver, value: value, callData: callData,