Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 47 additions & 50 deletions src/test/utils/ERC4337Helpers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,83 +46,94 @@ library ERC4337Helpers {
error InvalidRevertMessage(bytes4 expected, bytes4 reason);
error InvalidRevertMessageBytes(bytes expected, bytes reason);

struct ExecutionContext {
uint256 isExpectRevert;
address payable beneficiary;
bytes userOpCalldata;
bool success;
bytes returnData;
}

function exec4337(
PackedUserOperation[] memory userOps,
IEntryPoint onEntryPoint
)
internal
returns (ExecutionReturnData memory executionData)
{
uint256 isExpectRevert = getExpectRevert();
// Initialize execution context
ExecutionContext memory ctx = ExecutionContext({
isExpectRevert: getExpectRevert(),
beneficiary: payable(address(0x69)),
userOpCalldata: "",
success: false,
returnData: ""
});

// ERC-4337 specs validation
// Handle simulation
if (envOr("SIMULATE", false) || getSimulateUserOp()) {
bool simulationSuccess = userOps[0].simulateUserOp(address(onEntryPoint));

if (isExpectRevert == 0) {
if (ctx.isExpectRevert == 0) {
require(simulationSuccess, "UserOperation simulation failed");
}
}
// Record logs to determine if a revert happened

// Record logs for revert detection
recordLogs();

// Execute userOps
address payable beneficiary = payable(address(0x69));
bytes memory userOpCalldata = abi.encodeCall(IEntryPoint.handleOps, (userOps, beneficiary));
(bool success, bytes memory returnData) = address(onEntryPoint).call(userOpCalldata);
// Prepare and execute userOps
ctx.userOpCalldata = abi.encodeCall(IEntryPoint.handleOps, (userOps, ctx.beneficiary));
(ctx.success, ctx.returnData) = address(onEntryPoint).call(ctx.userOpCalldata);

if (isExpectRevert == 0) {
require(success, "UserOperation execution failed");
} else if (isExpectRevert == 2 && !success) {
checkRevertMessage(returnData);
if (ctx.isExpectRevert == 0) {
require(ctx.success, "UserOperation execution failed");
} else if (ctx.isExpectRevert == 2 && !ctx.success) {
checkRevertMessage(ctx.returnData);
}

// Parse logs and determine if a revert happened
// Process logs
VmSafe.Log[] memory logs = getRecordedLogs();
executionData = ExecutionReturnData(logs);
uint256 totalUserOpGas = 0;

for (uint256 i; i < logs.length; i++) {
// UserOperationEvent(bytes32,address,address,uint256,bool,uint256,uint256)
if (
logs[i].topics[0]
== 0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f
) {
(uint256 nonce, bool userOpSuccess,, uint256 actualGasUsed) =
abi.decode(logs[i].data, (uint256, bool, uint256, uint256));
totalUserOpGas = actualGasUsed;

if (!userOpSuccess) {
bytes32 userOpHash = logs[i].topics[1];
if (isExpectRevert == 0) {
if (ctx.isExpectRevert == 0) {
bytes memory revertReason = getUserOpRevertReason(logs, userOpHash);
address account = address(bytes20(logs[i].topics[2]));
revert UserOperationReverted(
userOpHash, account, getLabel(account), nonce, revertReason
);
} else {
if (isExpectRevert == 2) {
if (ctx.isExpectRevert == 2) {
checkRevertMessage(getUserOpRevertReason(logs, userOpHash));
}
clearExpectRevert();
}
}
}
// ModuleInstalled(uint256, address)
// Handle module events
else if (
logs[i].topics[0]
== 0xd21d0b289f126c4b473ea641963e766833c2f13866e4ff480abd787c100ef123
) {
(uint256 moduleType, address module) = abi.decode(logs[i].data, (uint256, address));
writeInstalledModule(InstalledModule(moduleType, module), logs[i].emitter);
}
// ModuleUninstalled(uint256, address)
else if (
} else if (
logs[i].topics[0]
== 0x341347516a9de374859dfda710fa4828b2d48cb57d4fbe4c1149612b8e02276e
) {
(uint256 moduleType, address module) = abi.decode(logs[i].data, (uint256, address));
// Get all installed modules
InstalledModule[] memory installedModules = getInstalledModules(logs[i].emitter);
// Remove the uninstalled module from the list of installed modules
for (uint256 j; j < installedModules.length; j++) {
if (
installedModules[j].moduleAddress == module
Expand All @@ -134,30 +145,23 @@ library ERC4337Helpers {
}
}
}
isExpectRevert = getExpectRevert();
if (isExpectRevert != 0) {
if (success) {
revert("UserOperation did not revert");
} else {
require(!success, "UserOperation execution did not fail as expected");
}
}
clearExpectRevert();

// Calculate gas for userOp
// Handle gas calculations
string memory gasIdentifier = getGasIdentifier();
if (
envOr("GAS", false) && bytes(gasIdentifier).length > 0
&& bytes(gasIdentifier).length < 50
) {
calculateGas(userOps, onEntryPoint, beneficiary, gasIdentifier, totalUserOpGas);
calculateGas(userOps, onEntryPoint, ctx.beneficiary, gasIdentifier, totalUserOpGas);
}

// Emit events
for (uint256 i; i < userOps.length; i++) {
emit ModuleKitLogs.ModuleKit_Exec4337(userOps[i].sender);
}
}

// Original helper functions unchanged
function exec4337(
PackedUserOperation memory userOp,
IEntryPoint onEntryPoint
Expand All @@ -179,7 +183,6 @@ library ERC4337Helpers {
returns (bytes memory revertReason)
{
for (uint256 i; i < logs.length; i++) {
// UserOperationRevertReason(bytes32,address,uint256,bytes)
if (
logs[i].topics[0]
== 0x1c4fada7374c0a9ee8841fc38afe82932dc0f8e69012e927f061a8bae611a201
Expand Down Expand Up @@ -216,41 +219,35 @@ library ERC4337Helpers {
{
uint256 bytesOffset;
assembly {
let ptr := add(actualReason, 0x20) // to data start
ptr := add(ptr, 0x04) // skip selector
ptr := add(ptr, 0x40) // skip two params
let ptr := add(actualReason, 0x20)
ptr := add(ptr, 0x04)
ptr := add(ptr, 0x40)
bytesOffset := mload(ptr)
}

bytes memory actual;
assembly {
let ptr := add(actualReason, 0x20) // to data start
ptr := add(ptr, 0x04) // skip selector
ptr := add(ptr, bytesOffset) // go to bytes position
let innerLength := mload(ptr) // load length of inner bytes
let ptr := add(actualReason, 0x20)
ptr := add(ptr, 0x04)
ptr := add(ptr, bytesOffset)
let innerLength := mload(ptr)

// Allocate memory for actual bytes
actual := mload(0x40)
mstore(actual, innerLength) // store length
mstore(actual, innerLength)

// Copy the data
let srcPtr := add(ptr, 0x20)
let destPtr := add(actual, 0x20)
mstore(destPtr, mload(srcPtr))

// Update free memory pointer
mstore(0x40, add(add(actual, 0x20), innerLength))
}

// If the length of the revert message is 4, just compare the selectors
if (revertMessage.length == 4) {
bytes4 expected = bytes4(revertMessage);
if (expected != bytes4(actual)) {
revert InvalidRevertMessage(expected, bytes4(actual));
}
}
// Otherwise, compare the actual bytes
else {
} else {
if (keccak256(actual) != keccak256(revertMessage)) {
revert InvalidRevertMessageBytes(revertMessage, actual);
}
Expand Down
27 changes: 23 additions & 4 deletions test/Diff.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ contract ERC7579DifferentialModuleKitLibTest is BaseTest {
string memory env = envs[i];
if (keccak256(abi.encodePacked(env)) == keccak256(abi.encodePacked("INVALID"))) {
vm.expectRevert(ModuleKitHelpers.InvalidAccountType.selector);
_usingAccountEnv(env);
this._usingAccountEnv(env);
} else {
_usingAccountEnv(env);
}
Expand Down Expand Up @@ -583,14 +583,18 @@ contract ERC7579DifferentialModuleKitLibTest is BaseTest {

function testSetAccountEnv_RevertsWhen_InvalidAccountType() public {
vm.expectRevert(ModuleKitHelpers.InvalidAccountType.selector);
instance.setAccountEnv("INVALID");
this.callSetAccountENVInvalid();
}

/*//////////////////////////////////////////////////////////////
HELPERS
//////////////////////////////////////////////////////////////*/

function _usingAccountEnv(string memory env) internal usingAccountEnv(env.toAccountType()) {
function callSetAccountENVInvalid() public {
instance.setAccountEnv("INVALID");
}

function _usingAccountEnv(string memory env) public usingAccountEnv(env.toAccountType()) {
AccountInstance memory newInstance = makeAccountInstance(keccak256(abi.encode(env)));
assertTrue(newInstance.account.code.length == 0);

Expand Down Expand Up @@ -700,7 +704,7 @@ contract ERC7579DifferentialModuleKitLibTest is BaseTest {
// Expect revert
vm.expectRevert();
// Assert that the module storage was cleared
instance.verifyModuleStorageWasCleared(accountAccesses, module);
this.callVerifyStorageWasNotCleared(instance, module, accountAccesses);
}

function __revertWhen_verifyModuleStorageWasCleared_NotCleared() public {
Expand Down Expand Up @@ -829,4 +833,19 @@ contract ERC7579DifferentialModuleKitLibTest is BaseTest {
}
_;
}

/*//////////////////////////////////////////////////////////////
HELPERS
//////////////////////////////////////////////////////////////*/

function callVerifyStorageWasNotCleared(
AccountInstance memory _instance,
address _module,
VmSafe.AccountAccess[] memory _accountAccesses
)
public
view
{
_instance.verifyModuleStorageWasCleared(_accountAccesses, _module);
}
}
Loading