Skip to content

Commit

Permalink
🚧 Add very simple user op benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
KONFeature committed Jan 30, 2024
1 parent b3ed062 commit 22ee469
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 44 deletions.
12 changes: 9 additions & 3 deletions benchmarks/validator/ECDSA.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
"fullDeployment": 14120
},
"signature": {
"viaKernel": 13902,
"viaValidator": 13442
"ko_viaKernel": 11929,
"ko_viaValidator": 9455,
"viaKernel": 11911,
"viaValidator": 9443
},
"userOp": ""
"userOp": {
"viaEntryPoint": 173360,
"viaKernel": 13229,
"viaValidator": 7173
}
}
9 changes: 0 additions & 9 deletions results/bench_ECDSA.json

This file was deleted.

161 changes: 144 additions & 17 deletions test/benchmark/BaseValidatorBenchmark.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import {console} from "forge-std/Console.sol";

import {Kernel} from "src/Kernel.sol";
import {KernelFactory} from "src/factory/KernelFactory.sol";
import {IKernel} from "src/interfaces/IKernel.sol";
import {IKernelValidator} from "src/interfaces/IKernelValidator.sol";
import {ValidationData} from "src/common/Types.sol";
import {ERC4337Utils} from "src/utils/ERC4337Utils.sol";

import {IEntryPoint} from "I4337/interfaces/IEntryPoint.sol";
import {UserOperation} from "I4337/interfaces/UserOperation.sol";
import {ENTRYPOINT_0_6_ADDRESS, ENTRYPOINT_0_6_BYTECODE} from "I4337/artifacts/EntryPoint_0_6.sol";
import {CREATOR_0_6_BYTECODE, CREATOR_0_6_ADDRESS} from "I4337/artifacts/EntryPoint_0_6.sol";

using ERC4337Utils for IEntryPoint;
using ERC4337Utils for Kernel;
Expand All @@ -24,6 +24,7 @@ abstract contract BaseValidatorBenchmark is Test {
// @dev The different 'master' wallets
address private _factoryOwner;
address private _fakeKernel;
address payable private _userOpBeneficiary;

/// @dev The kernel factory that will be used for the test
KernelFactory private _factory;
Expand All @@ -33,24 +34,30 @@ abstract contract BaseValidatorBenchmark is Test {
address private _kernelImplementation;

/// @dev The erc-4337 entrypoint that will be used for the test
IEntryPoint private _entryPoint;
IEntryPoint internal _entryPoint;

/// @dev The current validator we will benchmark
IKernelValidator internal _validator;

/// @dev The JSON output of the benchmark
string private _jsonOutput = "global json";
string private _jsonOutput;
string private _currentJson;

// Global benchmark config
bool private _isWriteEnabled;

/// @dev dummy contract we will use to test user op
DummyContract private _dummyContract;

/// @dev Init the base stuff required to run the benchmark
function _init() internal {
_isWriteEnabled = vm.envOr("WRITE_BENCHMARK_RESULT", false);

(_factoryOwner,) = makeAddrAndKey("factoryOwner");
(_fakeKernel,) = makeAddrAndKey("fakeKernel");
_dummyContract = new DummyContract();

_factoryOwner = makeAddr("factoryOwner");
_fakeKernel = makeAddr("fakeKernel");
_userOpBeneficiary = payable(makeAddr("userOpBeneficiary"));

// Init of the entry point
vm.etch(ENTRYPOINT_0_6_ADDRESS, ENTRYPOINT_0_6_BYTECODE);
Expand All @@ -77,6 +84,18 @@ abstract contract BaseValidatorBenchmark is Test {
vm.deal(deployedProxy, 100 ether);
}

/// @dev Get the init data for the kernel
function _getInitData() internal virtual returns (bytes memory) {
bytes memory enableData = _getEnableData();
return abi.encodeWithSelector(IKernel.initialize.selector, _validator, enableData);
}

/// @dev Get a signature with the sudo mode prefixed
function _getSudoModeUserOpSignature(UserOperation memory userOpration) internal virtual returns (bytes memory) {
bytes memory signature = _generateUserOpSignature(userOpration);
return abi.encodePacked(bytes4(0x00000000), signature);
}

/* -------------------------------------------------------------------------- */
/* Abstract methods */
/* -------------------------------------------------------------------------- */
Expand All @@ -90,15 +109,8 @@ abstract contract BaseValidatorBenchmark is Test {
/// @dev Fetch the data used to disable a kernel account
function _getDisableData() internal view virtual returns (bytes memory);

/// @dev Get the initialisation data for the current validator
function _getInitData() internal view virtual returns (bytes memory);

/// @dev Generate the signature for the given `_userOperation`
function _generateUserOpSignature(UserOperation memory _userOperation)
internal
view
virtual
returns (bytes memory);
function _generateUserOpSignature(UserOperation memory _userOperation) internal virtual returns (bytes memory);

/// @dev Generate the signature for the given `_hash`
function _generateHashSignature(bytes32 _hash) internal view virtual returns (bytes memory);
Expand All @@ -124,13 +136,17 @@ abstract contract BaseValidatorBenchmark is Test {

// Run the user op related tests
console.log("User op:");
// TODO
_benchmark_userOp_viaEntryPoint();
_benchmark_userOp_viaKernel();
_benchmark_userOp_viaValidator();
_addToGlobalJson("userOp");

// Run the signature related test
console.log("Signature:");
_benchmark_signature_viaValidator();
_benchmark_signature_viaKernel();
_benchmark_signature_ko_viaValidator();
_benchmark_signature_ko_viaKernel();
_addToGlobalJson("signature");

// Write the json output
Expand Down Expand Up @@ -182,15 +198,76 @@ abstract contract BaseValidatorBenchmark is Test {
_addToGlobal("disable", gasConsumed);
}

// TODO: Should we benchmark the different modes here?

/* -------------------------------------------------------------------------- */
/* User op methods */
/* -------------------------------------------------------------------------- */

/// @dev Benchmark the user op validation process only
function _benchmark_userOp_validation() private {
function _benchmark_userOp_viaEntryPoint() private {
// Build a dummy user op
(UserOperation memory userOperation,) = _getSignedDummyUserOp();

// Build an array of user ops for the entry point
UserOperation[] memory userOperations = new UserOperation[](1);
userOperations[0] = userOperation;

// Perform the user op validation
uint256 gasConsumed = gasleft();
_entryPoint.handleOps(userOperations, _userOpBeneficiary);
gasConsumed = gasConsumed - gasleft();
_addToUserOp("viaEntryPoint", gasConsumed);
}

/// @dev Benchmark the user op validation process only
function _benchmark_userOp_viaKernel() private {
// Build a dummy user op
(UserOperation memory userOperations, bytes32 userOperationHash) = _getSignedDummyUserOp();

// Perform the user op validation
uint256 gasConsumed = gasleft();
vm.prank(address(_entryPoint));
ValidationData isValid = _kernel.validateUserOp(userOperations, userOperationHash, 0);
gasConsumed = gasConsumed - gasleft();
_addToUserOp("viaKernel", gasConsumed);

// Ensure the signature was valid
assertEq(ValidationData.unwrap(isValid), uint256(0), "Direct user op check should be valid");
}

/// @dev Benchmark the user op validation process only
function _benchmark_userOp_viaValidator() private {
// Build a dummy user op
(UserOperation memory userOperations, bytes32 userOperationHash) = _getSignedDummyUserOp();

// Regen the signature, to have it not prefixed with the sude mode
userOperations.signature = _generateUserOpSignature(userOperations);

// Perform the user op validation
uint256 gasConsumed = gasleft();
vm.prank(address(_entryPoint));
ValidationData isValid = _validator.validateUserOp(userOperations, userOperationHash, 0);
gasConsumed = gasConsumed - gasleft();
_addToUserOp("viaValidator", gasConsumed);

// Ensure the signature was valid
assertEq(ValidationData.unwrap(isValid), uint256(0), "Direct user op check should be valid");
}

/// @dev Get a dummy user op
function _getDummyUserOp() private view returns (UserOperation memory) {
bytes memory dummyCallData = abi.encodeWithSelector(DummyContract.doDummyShit.selector);
return _entryPoint.fillUserOp(address(_kernel), dummyCallData);
}

// TODO
/// @dev Get a dummy user op
function _getSignedDummyUserOp() private returns (UserOperation memory userOperation, bytes32 userOperationHash) {
userOperation = _getDummyUserOp();
userOperation.signature = _getSudoModeUserOpSignature(userOperation);

// Get the hash of the user operation
userOperationHash = _entryPoint.getUserOpHash(userOperation);
}

/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -229,7 +306,44 @@ abstract contract BaseValidatorBenchmark is Test {
_addToSignature("viaKernel", gasConsumed);

// Ensure the signature was valid
assertEq(sigResponse, Kernel.isValidSignature.selector, "Direct signature check should be valid");
assertEq(sigResponse, Kernel.isValidSignature.selector, "Kernel signature check should be valid");
}

/// @dev Benchmark on a direct signature validation on the validator level
function _benchmark_signature_ko_viaValidator() private {
bytes32 _hash = keccak256("0xacab");
bytes memory signature = _generateHashSignature(_hash);
_hash = keccak256("0xdeadacab");

// Perform the validator signature check directly
uint256 gasConsumed = gasleft();
vm.prank(address(_kernel));
ValidationData isValid = _validator.validateSignature(_hash, signature);
gasConsumed = gasConsumed - gasleft();
_addToSignature("ko_viaValidator", gasConsumed);

// Ensure the signature was valid
assertEq(ValidationData.unwrap(isValid), uint256(1), "Direct signature check should be invalid");
}

/// @dev Benchmark on a direct signature validation on the kernel level
function _benchmark_signature_ko_viaKernel() private {
bytes32 _hash = keccak256("0xacab");
// Get a few data for the domain separator
bytes32 domainSeparator = _kernel.getDomainSeparator();
// Should create a digest of the hash
bytes32 _digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, _hash));
bytes memory signature = _generateHashSignature(_digest);
_hash = keccak256("0xdeadacab");

// Perform the validator signature check directly
uint256 gasConsumed = gasleft();
bytes4 sigResponse = _kernel.isValidSignature(_hash, signature);
gasConsumed = gasConsumed - gasleft();
_addToSignature("ko_viaKernel", gasConsumed);

// Ensure the signature was valid
assertEq(sigResponse, bytes4(0xffffffff), "Kernel signature check should be invalid");
}

/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -270,3 +384,16 @@ abstract contract BaseValidatorBenchmark is Test {
_currentJson = "";
}
}

/// @dev Dummy contract used to test the validator
contract DummyContract {
function isDummy() public pure returns (bool) {
return true;
}

function doDummyShit() public pure {
bytes32 randomHash = keccak256("0xdeadbeef");
bytes memory randomData = abi.encodePacked(randomHash);
randomHash = keccak256(randomData);
}
}
20 changes: 5 additions & 15 deletions test/benchmark/validator/ECDSABenchmark.t.sol
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Test} from "forge-std/Test.sol";
import {console} from "forge-std/Console.sol";

import {Kernel} from "src/Kernel.sol";
import {KernelFactory} from "src/factory/KernelFactory.sol";
import {IKernel} from "src/interfaces/IKernel.sol";
import {IKernelValidator} from "src/interfaces/IKernelValidator.sol";
import {ECDSAValidator} from "src/validator/ECDSAValidator.sol";
import {KernelStorage} from "src/abstract/KernelStorage.sol";
import {ERC4337Utils} from "src/utils/ERC4337Utils.sol";

import {BaseValidatorBenchmark} from "../BaseValidatorBenchmark.t.sol";

import {IEntryPoint} from "I4337/interfaces/IEntryPoint.sol";
import {UserOperation} from "I4337/interfaces/UserOperation.sol";

import {ECDSA} from "solady/utils/ECDSA.sol";

using ERC4337Utils for IEntryPoint;

/// @dev Benchmark of the ECDSA validator
/// @author KONFeature
contract ECDSABenchmark is BaseValidatorBenchmark {
Expand Down Expand Up @@ -56,11 +50,6 @@ contract ECDSABenchmark is BaseValidatorBenchmark {
return "";
}

/// @dev Get the initialisation data for the current validator
function _getInitData() internal view virtual override returns (bytes memory) {
return abi.encodeWithSelector(IKernel.initialize.selector, _validator, abi.encodePacked(_ecdsaOwner));
}

/// @dev Generate the signature for the given `_userOperation`
function _generateUserOpSignature(UserOperation memory _userOperation)
internal
Expand All @@ -69,7 +58,8 @@ contract ECDSABenchmark is BaseValidatorBenchmark {
override
returns (bytes memory)
{
return abi.encodePacked(_userOperation.signature);
// Do a regular ecdsa signature
return _entryPoint.signUserOpHash(vm, _ecdsaOwnerKey, _userOperation);
}

/// @dev Generate the signature for the given `_hash`
Expand Down

0 comments on commit 22ee469

Please sign in to comment.