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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ docs/

# Dotenv file
.env

.vscode
48 changes: 38 additions & 10 deletions src/ERC4337Checker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ contract ERC4337Checker {
}

bool result = true;
if (!validateForbiddenOpcodes(debugSteps)) {
if (!validateForbiddenOpcodes(debugSteps, userOp)) {
result = false;
}
if (!validateCall(debugSteps, address(entryPoint), true)) {
Expand Down Expand Up @@ -298,11 +298,38 @@ contract ERC4337Checker {
* May not invokes any forbidden opcodes
* Must not use GAS opcode (unless followed immediately by one of { CALL, DELEGATECALL, CALLCODE, STATICCALL }.)
*/
function validateForbiddenOpcodes(Vm.DebugStep[] memory debugSteps) private returns (bool) {
function validateForbiddenOpcodes(Vm.DebugStep[] memory debugSteps, UserOperation memory userOp) private returns (bool) {
bool result = true;
for (uint256 i = 0; i < debugSteps.length; i++) {
uint8 opcode = debugSteps[i].opcode;
if (isForbiddenOpcode(opcode)) {

// exception for CREATE opcode
if (opcode == 0xF0){
// CREATE is only allowed if factory exists AND sender directly executes it
if(getFactoryAddr(userOp) == address(0)){
failureLogs.push(FailureLog({
errorMsg: string(abi.encodePacked(
"CREATE opcode forbidden: no factory address present"
)),
contractAddr: debugSteps[i].contractAddr
}));
result = false;
} else if (debugSteps[i].contractAddr != userOp.sender) {
// Factory exists, but CREATE must be directly from sender, not utility contract
failureLogs.push(FailureLog({
errorMsg: string(abi.encodePacked(
"CREATE opcode forbidden: only sender can execute it. ",
"Expected: [", Strings.toHexString(userOp.sender), "], ",
"Got: [", Strings.toHexString(debugSteps[i].contractAddr), "]"
)),
contractAddr: debugSteps[i].contractAddr
}));
result = false;
}
continue;
}

// exception case for GAS opcode
if (opcode == 0x5A && i < debugSteps.length - 1) {
if (!isValidNextOpcodeOfGas(debugSteps[i + 1].opcode)) {
Expand All @@ -314,15 +341,16 @@ contract ERC4337Checker {
}));
result = false;
}
} else {
failureLogs.push(FailureLog({
errorMsg: string(abi.encodePacked(
"forbidden op-code usage. opcode: [", Strings.toHexString(opcode), "]"
)),
contractAddr: debugSteps[i].contractAddr
}));
result = false;
continue;
}

failureLogs.push(FailureLog({
errorMsg: string(abi.encodePacked(
"forbidden op-code usage. opcode: [", Strings.toHexString(opcode), "]"
)),
contractAddr: debugSteps[i].contractAddr
}));
result = false;
}
}
return result;
Expand Down
19 changes: 19 additions & 0 deletions test/ERC4337Checker.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,25 @@ contract ERC4337CheckerTest is Test {
checker.printFailureLogs();
}

function test_forbiddenOpCodeCreate() public {
address mockAccountAddr = address(mockAccount);

bytes memory encodedCallData = abi.encodeWithSelector(
MockAccount.execute.selector,
MockAccount.AttackType.FORBIDDEN_OPCODE_CREATE
);

UserOperation memory userOp = _getUnsignedOp(
mockAccountAddr,
encodedCallData
);

assertFalse(
checker.simulateAndVerifyUserOp(vm, userOp, entryPoint)
);
checker.printFailureLogs();
}

function test_outOfGas() public {
address mockAccountAddr = address(mockAccount);

Expand Down
7 changes: 6 additions & 1 deletion test/mocks/MockAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ contract MockAccount is BaseAccount {
FORBIDDEN_OPCODE_GASLIMIT,
FORBIDDEN_OPCODE_COINBASE,
FORBIDDEN_OPCODE_ORIGIN,
FORBIDDEN_OPCODE_INVALID
FORBIDDEN_OPCODE_INVALID,
FORBIDDEN_OPCODE_CREATE
}

IEntryPoint private _entryPoint;
Expand Down Expand Up @@ -113,6 +114,10 @@ contract MockAccount is BaseAccount {
// The invalid opcode was executed and caught
console2.log("Invalid opcode caught in try-catch");
}
} else if (attackType == AttackType.FORBIDDEN_OPCODE_CREATE){
address demoAddress = address(new InvalidActions());
console2.log("New contract", demoAddress);

}

return 0;
Expand Down