Skip to content

Commit

Permalink
feat: add MA runtime gas benchmarks
Browse files Browse the repository at this point in the history
  • Loading branch information
adamegyed committed Sep 16, 2024
1 parent a8e7cc8 commit 5903b04
Show file tree
Hide file tree
Showing 17 changed files with 401 additions and 104 deletions.
1 change: 1 addition & 0 deletions .forge-snapshots/LightAccount_Runtime_Erc20Transfer.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
63183
1 change: 1 addition & 0 deletions .forge-snapshots/LightAccount_Runtime_NativeTransfer.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
39356
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
188347
1 change: 1 addition & 0 deletions .forge-snapshots/ModularAccount_Runtime_Erc20Transfer.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
84168
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
60080
7 changes: 0 additions & 7 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ fs_permissions = [
{ access = "read-write", path = "./.forge-snapshots" },
{ access = "read", path = "./test/bin" },
]
remappings = [
'forge-std/=lib/forge-std/src/'
]

[fuzz]
runs = 500
Expand Down Expand Up @@ -60,10 +57,6 @@ optimizer_runs = 10000
out = 'out-optimized'
ffi = true
isolate = true
# forge-gas-snapshot uses a different remapping for forge-std, so we also need to do this within the gas profile.
remappings = [
'forge-std/=lib/forge-std/',
]

[fmt]
line_length = 115
Expand Down
104 changes: 104 additions & 0 deletions gas/lightaccount-v2/ILightAccount.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: UNLICENSED
// !! THIS FILE WAS AUTOGENERATED BY abi-to-sol v0.8.0. SEE SOURCE BELOW. !!
pragma solidity ^0.8.20;

interface ILightAccount {
error ArrayLengthMismatch();
error ECDSAInvalidSignature();
error ECDSAInvalidSignatureLength(uint256 length);
error ECDSAInvalidSignatureS(bytes32 s);
error InvalidInitialization();
error InvalidOwner(address owner);
error InvalidSignatureType();
error NotAuthorized(address caller);
error NotInitializing();
error UnauthorizedCallContext();
error UpgradeFailed();
error ZeroAddressNotAllowed();

event Initialized(uint64 version);
event LightAccountInitialized(address indexed entryPoint, address indexed owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
event Upgraded(address indexed implementation);

function addDeposit() external payable;

function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);

function entryPoint() external view returns (address);

function execute(address dest, uint256 value, bytes memory func) external;

function executeBatch(address[] memory dest, bytes[] memory func) external;

function executeBatch(address[] memory dest, uint256[] memory value, bytes[] memory func) external;

function getDeposit() external view returns (uint256);

function getMessageHash(bytes memory message) external view returns (bytes32);

function getNonce() external view returns (uint256);

function initialize(address owner_) external;

function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4);

function onERC1155BatchReceived(address, address, uint256[] memory, uint256[] memory, bytes memory)
external
pure
returns (bytes4);

function onERC1155Received(address, address, uint256, uint256, bytes memory) external pure returns (bytes4);

function onERC721Received(address, address, uint256, bytes memory) external pure returns (bytes4);

function owner() external view returns (address);

function proxiableUUID() external view returns (bytes32);

function supportsInterface(bytes4 interfaceId) external view returns (bool);

function transferOwnership(address newOwner) external;

function upgradeToAndCall(address newImplementation, bytes memory data) external payable;

function validateUserOp(PackedUserOperation memory userOp, bytes32 userOpHash, uint256 missingAccountFunds)
external
returns (uint256 validationData);

function withdrawDepositTo(address withdrawAddress, uint256 amount) external;

receive() external payable;
}

struct PackedUserOperation {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
bytes32 accountGasLimits;
uint256 preVerificationGas;
bytes32 gasFees;
bytes paymasterAndData;
bytes signature;
}

// THIS FILE WAS AUTOGENERATED FROM THE FOLLOWING ABI JSON:
/*
[{"inputs":[{"internalType":"contract
IEntryPoint","name":"entryPoint_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ArrayLengthMismatch","type":"error"},{"inputs":[],"name":"ECDSAInvalidSignature","type":"error"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"name":"ECDSAInvalidSignatureLength","type":"error"},{"inputs":[{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"ECDSAInvalidSignatureS","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"InvalidOwner","type":"error"},{"inputs":[],"name":"InvalidSignatureType","type":"error"},{"inputs":[{"internalType":"address","name":"caller","type":"address"}],"name":"NotAuthorized","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[],"name":"UnauthorizedCallContext","type":"error"},{"inputs":[],"name":"UpgradeFailed","type":"error"},{"inputs":[],"name":"ZeroAddressNotAllowed","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract
IEntryPoint","name":"entryPoint","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"}],"name":"LightAccountInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"inputs":[],"name":"addDeposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"entryPoint","outputs":[{"internalType":"contract
IEntryPoint","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"dest","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"func","type":"bytes"}],"name":"execute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"dest","type":"address[]"},{"internalType":"bytes[]","name":"func","type":"bytes[]"}],"name":"executeBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"dest","type":"address[]"},{"internalType":"uint256[]","name":"value","type":"uint256[]"},{"internalType":"bytes[]","name":"func","type":"bytes[]"}],"name":"executeBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"message","type":"bytes"}],"name":"getMessageHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner_","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct
PackedUserOperation","name":"userOp","type":"tuple"},{"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"internalType":"uint256","name":"missingAccountFunds","type":"uint256"}],"name":"validateUserOp","outputs":[{"internalType":"uint256","name":"validationData","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address
payable","name":"withdrawAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawDepositTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]*/
47 changes: 46 additions & 1 deletion gas/lightaccount-v2/LightAccount.gas.t.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

import {ILightAccountFactory} from "./ILightAccountFactory.sol";
import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol";
import {VmSafe} from "forge-std/src/Vm.sol";
import {console} from "forge-std/src/console.sol";

import {ILightAccount} from "./ILightAccount.sol";
import {ILightAccountFactory} from "./ILightAccountFactory.sol";

import {MockERC20} from "../../test/mocks/MockERC20.sol";

contract LightAccountGasTest is GasSnapshot {
address internal constant _LIGHT_ACCOUNT_FACTORY = 0x0000000000400CdFef5E2714E63d8040b700BC24;

Expand Down Expand Up @@ -36,4 +40,45 @@ contract LightAccountGasTest is GasSnapshot {

snap("LightAccount_Runtime_AccountCreation", gas.gasTotalUsed);
}

function test_lightAccountGas_runtimeNativeTransfer() public {
address owner = makeAddr("owner");
address recipient = makeAddr("recipient");
vm.deal(recipient, 1 wei);

ILightAccount account = ILightAccount(payable(_factory.createAccount(owner, 0)));

vm.deal(address(account), 1 ether);

vm.prank(owner);
account.execute(recipient, 0.1 ether, "");

VmSafe.Gas memory gas = vm.lastCallGas();

console.log("Runtime: native transfer");
console.log("gasTotalUsed: ", gas.gasTotalUsed);

snap("LightAccount_Runtime_NativeTransfer", gas.gasTotalUsed);
}

function test_lightAccountGas_Erc20Transfer() public {
address owner = makeAddr("owner");
address recipient = makeAddr("recipient");
vm.deal(recipient, 1 wei);

ILightAccount account = ILightAccount(payable(_factory.createAccount(owner, 0)));

MockERC20 erc20 = new MockERC20();
erc20.mint(address(account), 100 ether);

vm.prank(owner);
account.execute(address(erc20), 0, abi.encodeWithSelector(erc20.transfer.selector, recipient, 10 ether));

VmSafe.Gas memory gas = vm.lastCallGas();

console.log("Runtime: erc20 transfer");
console.log("gasTotalUsed: ", gas.gasTotalUsed);

snap("LightAccount_Runtime_Erc20Transfer", gas.gasTotalUsed);
}
}
59 changes: 59 additions & 0 deletions gas/modular-account/BenchmarkBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol";
import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol";
import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol";

import {AccountFactory} from "../../src/account/AccountFactory.sol";
import {ModularAccount} from "../../src/account/ModularAccount.sol";
import {SemiModularAccount} from "../../src/account/SemiModularAccount.sol";

import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol";
import {SingleSignerValidationModule} from "../../src/modules/validation/SingleSignerValidationModule.sol";

import {MockERC20} from "../../test/mocks/MockERC20.sol";

import {ModuleSignatureUtils} from "../../test/utils/ModuleSignatureUtils.sol";
import {OptimizedTest} from "../../test/utils/OptimizedTest.sol";

abstract contract ModularAccountBenchmarkBase is GasSnapshot, OptimizedTest, ModuleSignatureUtils {
EntryPoint public entryPoint;

AccountFactory public factory;
ModularAccount public accountImpl;
SemiModularAccount public semiModularImpl;
SingleSignerValidationModule public singleSignerValidationModule;

ModularAccount public account1;

address public owner1;
uint256 public owner1Key;

address public recipient;
MockERC20 public mockErc20;

ModuleEntity public signerValidation;

constructor() {
(owner1, owner1Key) = makeAddrAndKey("owner1");

recipient = makeAddr("recipient");
vm.deal(recipient, 1 wei);

entryPoint = _deployEntryPoint070();
accountImpl = _deployModularAccount(IEntryPoint(entryPoint));
semiModularImpl = _deploySemiModularAccount(IEntryPoint(entryPoint));
singleSignerValidationModule = _deploySingleSignerValidationModule();
mockErc20 = new MockERC20();

factory = new AccountFactory(
entryPoint, accountImpl, semiModularImpl, address(singleSignerValidationModule), address(this)
);
}

function _deployAccount1() internal {
account1 = factory.createAccount(owner1, 0, 0);
signerValidation = ModuleEntityLib.pack(address(singleSignerValidationModule), 0);
}
}
66 changes: 66 additions & 0 deletions gas/modular-account/ModularAccountRuntime.gas.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

import {VmSafe} from "forge-std/src/Vm.sol";
import {console} from "forge-std/src/console.sol";

import {ModularAccount} from "../../src/account/ModularAccount.sol";

import {ModularAccountBenchmarkBase} from "./BenchmarkBase.sol";

contract ModularAccountRuntimeTest is ModularAccountBenchmarkBase {
function test_modularAccountGas_runtimeAccountCreation() public {
uint256 salt = 0;
uint32 entityId = 0;

factory.createAccount(owner1, salt, entityId);

VmSafe.Gas memory gas = vm.lastCallGas();

console.log("Runtime: account creation: ");
console.log("gasTotalUsed: %d", gas.gasTotalUsed);

snap("ModularAccount_Runtime_AccountCreation", gas.gasTotalUsed);
}

function test_modularAccountGas_runtimeNativeTransfer() public {
_deployAccount1();

vm.deal(address(account1), 1 ether);

vm.prank(owner1);
account1.executeWithAuthorization(
abi.encodeCall(ModularAccount.execute, (recipient, 0.1 ether, "")),
_encodeSignature(signerValidation, GLOBAL_VALIDATION, "")
);

VmSafe.Gas memory gas = vm.lastCallGas();

console.log("Runtime: native transfer: ");
console.log("gasTotalUsed: %d", gas.gasTotalUsed);

snap("ModularAccount_Runtime_NativeTransfer", gas.gasTotalUsed);
}

function test_modularAccountGas_runtimeErc20Transfer() public {
_deployAccount1();

mockErc20.mint(address(account1), 100 ether);

vm.prank(owner1);
account1.executeWithAuthorization(
abi.encodeCall(
ModularAccount.execute,
(address(mockErc20), 0, abi.encodeWithSelector(mockErc20.transfer.selector, recipient, 10 ether))
),
_encodeSignature(signerValidation, GLOBAL_VALIDATION, "")
);

VmSafe.Gas memory gas = vm.lastCallGas();

console.log("Runtime: erc20 transfer: ");
console.log("gasTotalUsed: %d", gas.gasTotalUsed);

snap("ModularAccount_Runtime_Erc20Transfer", gas.gasTotalUsed);
}
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"solhint": "^3.6.2"
},
"scripts": {
"clean": "forge clean && FOUNDRY_PROFILE=optimized-build forge clean",
"prep": "pnpm fmt && forge b && pnpm lint && pnpm test && pnpm gas",
"fmt": "forge fmt && FOUNDRY_PROFILE=gas forge fmt",
"fmt:check": "forge fmt --check && FOUNDRY_PROFILE=gas forge fmt --check",
"gas": "FOUNDRY_PROFILE=gas forge test -vv",
Expand All @@ -20,6 +22,7 @@
"lint:src": "solhint --max-warnings 0 -c ./config/solhint-src.json './src/**/*.sol'",
"lint:test": "solhint --max-warnings 0 -c ./config/solhint-test.json './test/**/*.sol'",
"lint:gas": "solhint --max-warnings 0 -c ./config/solhint-gas.json './gas/**/*.sol'",
"lint:script": "solhint --max-warnings 0 -c ./config/solhint-script.json './script/**/*.sol'"
"lint:script": "solhint --max-warnings 0 -c ./config/solhint-script.json './script/**/*.sol'",
"test": "forge test && SMA_TEST=true forge test"
}
}
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ account-abstraction/=node_modules/account-abstraction/contracts/
@alchemy/light-account/=lib/light-account/
solady=node_modules/solady/src/
@erc-6900/reference-implementation/=node_modules/reference-implementation/src/
forge-gas-snapshot/=lib/forge-gas-snapshot/src/
forge-gas-snapshot/=lib/forge-gas-snapshot/src/
forge-std/=lib/forge-std/
2 changes: 1 addition & 1 deletion test/account/ModularAccount.t.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.26;

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

import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol";
import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";
Expand Down
Loading

0 comments on commit 5903b04

Please sign in to comment.