diff --git a/.gasestimates.md b/.gasestimates.md index 0d971c6..79611e1 100644 --- a/.gasestimates.md +++ b/.gasestimates.md @@ -4,104 +4,128 @@ Generated via `bash utils/inspect.sh`. --- `forge test --gas-report --no-match-path "test/script/**/*"` -| PRECOMPILES contract | | | | | | -|----------------------|-----------------|------|--------|------|---------| -| Deployment Cost | Deployment Size | | | | | -| 0 | 0 | | | | | -| Function Name | min | avg | median | max | # calls | -| ecrecover | 3000 | 3000 | 3000 | 3000 | 5 | - - | lib/account-abstraction/contracts/core/EntryPoint.sol:EntryPoint contract | | | | | | |---------------------------------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 4781904 | 23781 | | | | | +| 3641750 | 16650 | | | | | | Function Name | min | avg | median | max | # calls | -| balanceOf | 587 | 987 | 587 | 2587 | 5 | -| depositTo | 22498 | 23831 | 24498 | 24498 | 3 | -| getUserOpHash | 2142 | 2144 | 2142 | 2151 | 4 | -| handleOps | 67324 | 119845 | 132443 | 147171 | 4 | -| innerHandleOp | 52568 | 52568 | 52568 | 52568 | 1 | -| receive | 22103 | 22103 | 22103 | 22103 | 1 | -| withdrawTo | 36957 | 36957 | 36957 | 36957 | 1 | +| balanceOf | 572 | 1572 | 1572 | 2572 | 16 | +| getDepositInfo | 1162 | 1162 | 1162 | 1162 | 10 | +| getUserOpHash | 1534 | 1575 | 1570 | 1624 | 20 | +| handleOps | 46248 | 148656 | 159651 | 204479 | 23 | | lib/account-abstraction/contracts/samples/SimpleAccount.sol:SimpleAccount contract | | | | | | |------------------------------------------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 1676343 | 8675 | | | | | +| 1532409 | 7166 | | | | | | Function Name | min | avg | median | max | # calls | -| entryPoint | 404 | 404 | 404 | 404 | 1 | -| initialize | 26214 | 26214 | 26214 | 26214 | 1 | -| proxiableUUID | 384 | 384 | 384 | 384 | 1 | +| entryPoint | 382 | 382 | 382 | 382 | 6 | +| initialize | 48079 | 48079 | 48079 | 48079 | 6 | +| proxiableUUID | 337 | 337 | 337 | 337 | 6 | | lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy contract | | | | | | |-------------------------------------------------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 120773 | 1228 | | | | | +| 145164 | 862 | | | | | | Function Name | min | avg | median | max | # calls | -| addDeposit | 30214 | 36214 | 39214 | 39214 | 3 | -| disableInitializers | 11109 | 11109 | 11109 | 11109 | 1 | -| entryPoint | 720 | 3014 | 3014 | 5308 | 2 | -| execute | 7811 | 23164 | 26434 | 39767 | 5 | -| executeBatch(address[],bytes[]) | 7732 | 20570 | 20570 | 33409 | 2 | -| executeBatch(address[],uint256[],bytes[]) | 8039 | 24265 | 24265 | 40492 | 2 | -| getDeposit | 1861 | 4861 | 1861 | 10861 | 3 | -| getInitializedVersion | 677 | 677 | 677 | 677 | 1 | -| getMessageHash | 1665 | 4665 | 6165 | 6165 | 3 | -| isValidSignature | 3990 | 7475 | 7537 | 10898 | 3 | -| owner | 867 | 867 | 867 | 867 | 1 | -| transferOwnership | 7725 | 9803 | 7948 | 12446 | 7 | -| upgradeToAndCall | 4572 | 17748 | 13221 | 42178 | 7 | -| validateUserOp | 32648 | 39525 | 40699 | 44056 | 4 | -| withdrawDepositTo | 2967 | 21632 | 21632 | 40298 | 2 | +| disableInitializers | 32143 | 32143 | 32143 | 32143 | 1 | +| getInitializedVersion | 659 | 659 | 659 | 659 | 1 | +| upgradeToAndCall | 32662 | 38282 | 37813 | 41604 | 5 | | src/LightAccount.sol:LightAccount contract | | | | | | |--------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 1947528 | 10021 | | | | | +| 0 | 0 | | | | | | Function Name | min | avg | median | max | # calls | -| addDeposit | 29901 | 32901 | 34401 | 34401 | 3 | -| entryPoint | 492 | 492 | 492 | 492 | 1 | -| execute | 2967 | 19223 | 26097 | 34930 | 5 | -| executeBatch(address[],bytes[]) | 2867 | 15710 | 15710 | 28554 | 2 | -| executeBatch(address[],uint256[],bytes[]) | 3156 | 19387 | 19387 | 35619 | 2 | -| getDeposit | 1545 | 3045 | 1545 | 6045 | 3 | -| getMessageHash | 1337 | 1337 | 1337 | 1337 | 3 | -| initialize | 23308 | 48934 | 49711 | 49711 | 34 | -| isValidSignature | 3650 | 7131 | 7191 | 10552 | 3 | -| owner | 551 | 551 | 551 | 551 | 1 | -| transferOwnership | 2902 | 4983 | 3125 | 7630 | 7 | -| upgradeToAndCall | 3293 | 20317 | 20317 | 37341 | 2 | -| validateUserOp | 32181 | 35677 | 35723 | 39083 | 4 | -| withdrawDepositTo | 2641 | 21310 | 21310 | 39979 | 2 | +| addDeposit | 34019 | 34019 | 34019 | 34019 | 5 | +| eip712Domain | 1270 | 1270 | 1270 | 1270 | 9 | +| entryPoint | 448 | 448 | 448 | 448 | 1 | +| execute | 2968 | 25453 | 26074 | 57668 | 9 | +| executeBatch(address[],bytes[]) | 2886 | 15726 | 15726 | 28567 | 2 | +| executeBatch(address[],uint256[],bytes[]) | 3165 | 19406 | 19406 | 35647 | 2 | +| getDeposit | 1463 | 2963 | 1463 | 5963 | 3 | +| initialize | 23064 | 48839 | 49453 | 49453 | 43 | +| isValidSignature | 1059 | 2748 | 1126 | 7091 | 10 | +| owner | 532 | 532 | 532 | 532 | 3 | +| transferOwnership | 2915 | 5294 | 5385 | 7532 | 10 | +| upgradeToAndCall | 2874 | 43271 | 56021 | 58168 | 4 | +| validateUserOp | 620 | 26385 | 35542 | 36228 | 10 | +| withdrawDepositTo | 2641 | 31501 | 38139 | 47086 | 4 | | src/LightAccountFactory.sol:LightAccountFactory contract | | | | | | |----------------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 2440855 | 12523 | | | | | +| 2482191 | 11934 | | | | | | Function Name | min | avg | median | max | # calls | -| createAccount | 4821 | 154660 | 161129 | 163629 | 35 | -| getAddress | 4672 | 4672 | 4672 | 4672 | 1 | +| acceptOwnership | 28076 | 28076 | 28076 | 28076 | 1 | +| addStake | 60756 | 60756 | 60756 | 60756 | 4 | +| createAccount | 24600 | 140503 | 143831 | 143831 | 44 | +| getAddress | 625 | 625 | 625 | 625 | 1 | +| owner | 387 | 1053 | 387 | 2387 | 3 | +| renounceOwnership | 2360 | 2360 | 2360 | 2360 | 1 | +| transferOwnership | 47557 | 47557 | 47557 | 47557 | 1 | +| unlockStake | 33544 | 33544 | 33544 | 33544 | 2 | +| withdraw | 31284 | 31284 | 31284 | 31284 | 1 | +| withdrawStake | 29698 | 33088 | 33088 | 36478 | 2 | + + +| src/MultiOwnerLightAccount.sol:MultiOwnerLightAccount contract | | | | | | +|----------------------------------------------------------------|-----------------|--------|--------|---------|---------| +| Deployment Cost | Deployment Size | | | | | +| 0 | 0 | | | | | +| Function Name | min | avg | median | max | # calls | +| addDeposit | 34047 | 34047 | 34047 | 34047 | 5 | +| eip712Domain | 1284 | 1284 | 1284 | 1284 | 13 | +| entryPoint | 486 | 486 | 486 | 486 | 1 | +| execute | 3058 | 28642 | 27215 | 57706 | 10 | +| executeBatch(address[],bytes[]) | 2936 | 15776 | 15776 | 28617 | 2 | +| executeBatch(address[],uint256[],bytes[]) | 3243 | 19484 | 19484 | 35725 | 2 | +| getDeposit | 1499 | 2999 | 1499 | 5999 | 3 | +| initialize | 74505 | 118169 | 74505 | 2432375 | 54 | +| isValidSignature | 1050 | 4182 | 3914 | 7839 | 14 | +| owners | 1567 | 1567 | 1567 | 1567 | 4 | +| updateOwners | 3501 | 25162 | 36037 | 38231 | 17 | +| upgradeToAndCall | 2941 | 43308 | 56029 | 58235 | 4 | +| validateUserOp | 652 | 26100 | 35598 | 37219 | 13 | +| withdrawDepositTo | 2712 | 31542 | 38151 | 47157 | 4 | + + +| src/MultiOwnerLightAccountFactory.sol:MultiOwnerLightAccountFactory contract | | | | | | +|------------------------------------------------------------------------------|-----------------|--------|--------|---------|---------| +| Deployment Cost | Deployment Size | | | | | +| 3163318 | 15122 | | | | | +| Function Name | min | avg | median | max | # calls | +| acceptOwnership | 28076 | 28076 | 28076 | 28076 | 1 | +| addStake | 60778 | 60778 | 60778 | 60778 | 4 | +| createAccount | 22252 | 370044 | 25206 | 2620802 | 8 | +| createAccountSingle | 21926 | 162007 | 170029 | 170029 | 55 | +| getAddress | 700 | 8926 | 1816 | 48969 | 8 | +| owner | 365 | 1031 | 365 | 2365 | 3 | +| renounceOwnership | 2360 | 2360 | 2360 | 2360 | 1 | +| transferOwnership | 47579 | 47579 | 47579 | 47579 | 1 | +| unlockStake | 33566 | 33566 | 33566 | 33566 | 2 | +| withdraw | 31298 | 31298 | 31298 | 31298 | 1 | +| withdrawStake | 29720 | 33107 | 33107 | 36495 | 2 | | test/CustomSlotInitializable.t.sol:DisablesInitializersWhileInitializing contract | | | | | | |-----------------------------------------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 125208 | 697 | | | | | +| 187824 | 697 | | | | | | Function Name | min | avg | median | max | # calls | -| initialize | 22720 | 22720 | 22720 | 22720 | 1 | +| initialize | 43784 | 43784 | 43784 | 43784 | 1 | | test/CustomSlotInitializable.t.sol:IsInitializingChecker contract | | | | | | |-------------------------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 146241 | 809 | | | | | +| 210601 | 809 | | | | | | Function Name | min | avg | median | max | # calls | -| initialize | 46108 | 46108 | 46108 | 46108 | 1 | +| initialize | 67172 | 67172 | 67172 | 67172 | 1 | | isInitializing | 306 | 306 | 306 | 306 | 1 | | wasInitializing | 271 | 271 | 271 | 271 | 1 | @@ -109,103 +133,103 @@ Generated via `bash utils/inspect.sh`. | test/CustomSlotInitializable.t.sol:V1 contract | | | | | | |------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 768527 | 3951 | | | | | +| 595231 | 2640 | | | | | | Function Name | min | avg | median | max | # calls | | disableInitializers | 6296 | 6296 | 6296 | 6296 | 1 | | getInitializedVersion | 388 | 388 | 388 | 388 | 1 | -| initialize | 509 | 19454 | 23926 | 23926 | 10 | -| proxiableUUID | 270 | 270 | 270 | 270 | 2 | -| upgradeToAndCall | 8386 | 13630 | 14397 | 17343 | 4 | +| initialize | 2487 | 21739 | 23904 | 44968 | 10 | +| proxiableUUID | 246 | 246 | 246 | 246 | 2 | +| upgradeToAndCall | 5957 | 11696 | 12962 | 14903 | 4 | -| test/CustomSlotInitializable.t.sol:V2 contract | | | | | | -|------------------------------------------------|-----------------|------|--------|------|---------| -| Deployment Cost | Deployment Size | | | | | -| 687034 | 3536 | | | | | -| Function Name | min | avg | median | max | # calls | -| getInitializedVersion | 361 | 361 | 361 | 361 | 1 | -| initialize | 423 | 4381 | 6360 | 6360 | 3 | -| proxiableUUID | 243 | 243 | 243 | 243 | 3 | -| upgradeToAndCall | 3390 | 3390 | 3390 | 3390 | 1 | +| test/CustomSlotInitializable.t.sol:V2 contract | | | | | | +|------------------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 508142 | 2226 | | | | | +| Function Name | min | avg | median | max | # calls | +| getInitializedVersion | 361 | 361 | 361 | 361 | 1 | +| initialize | 2401 | 5025 | 6338 | 6338 | 3 | +| proxiableUUID | 219 | 219 | 219 | 219 | 3 | +| upgradeToAndCall | 11108 | 11108 | 11108 | 11108 | 1 | -| test/LightAccount.t.sol:LightSwitch contract | | | | | | -|----------------------------------------------|-----------------|-------|--------|-------|---------| -| Deployment Cost | Deployment Size | | | | | -| 51305 | 284 | | | | | -| Function Name | min | avg | median | max | # calls | -| on | 267 | 267 | 267 | 267 | 5 | -| turnOn | 22259 | 22259 | 22259 | 22259 | 5 | +| test/LightAccount.t.sol:LightSwitch contract | | | | | | +|----------------------------------------------|-----------------|-----|--------|-----|---------| +| Deployment Cost | Deployment Size | | | | | +| 108729 | 284 | | | | | +| Function Name | min | avg | median | max | # calls | +| on | 267 | 267 | 267 | 267 | 6 | | test/LightAccount.t.sol:Owner contract | | | | | | |----------------------------------------|-----------------|-----|--------|-----|---------| | Deployment Cost | Deployment Size | | | | | -| 159608 | 825 | | | | | +| 224260 | 828 | | | | | | Function Name | min | avg | median | max | # calls | -| isValidSignature | 767 | 767 | 767 | 767 | 2 | -| sign | 623 | 623 | 623 | 623 | 2 | +| isValidSignature | 767 | 767 | 767 | 767 | 3 | +| sign | 623 | 623 | 623 | 623 | 3 | -| test/LightAccount.t.sol:Reverter contract | | | | | | -|-------------------------------------------|-----------------|-----|--------|-----|---------| -| Deployment Cost | Deployment Size | | | | | -| 46905 | 261 | | | | | -| Function Name | min | avg | median | max | # calls | -| doRevert | 201 | 201 | 201 | 201 | 1 | +| test/MultiOwnerLightAccount.t.sol:LightSwitch contract | | | | | | +|--------------------------------------------------------|-----------------|-----|--------|------|---------| +| Deployment Cost | Deployment Size | | | | | +| 108729 | 284 | | | | | +| Function Name | min | avg | median | max | # calls | +| on | 267 | 711 | 267 | 2267 | 9 | + + +| test/MultiOwnerLightAccount.t.sol:Owner contract | | | | | | +|--------------------------------------------------|-----------------|-----|--------|-----|---------| +| Deployment Cost | Deployment Size | | | | | +| 224272 | 828 | | | | | +| Function Name | min | avg | median | max | # calls | +| isValidSignature | 767 | 767 | 767 | 767 | 7 | +| sign | 623 | 623 | 623 | 623 | 8 | - -Ran 3 test suites: 39 tests passed, 0 failed, 0 skipped (39 total tests) -`forge inspect src/CustomSlotInitializable.sol:CustomSlotInitializable gasestimates` -```json -null -``` +Ran 5 test suites in 1.52s (3.44s CPU time): 118 tests passed, 0 failed, 0 skipped (118 total tests) `forge inspect src/LightAccount.sol:LightAccount gasestimates` ```json { "creation": { - "codeDepositCost": "1921800", + "codeDepositCost": "1503600", "executionCost": "infinite", "totalCost": "infinite" }, "external": { "addDeposit()": "infinite", - "domainSeparator()": "912", - "encodeMessageData(bytes)": "infinite", + "eip712Domain()": "infinite", "entryPoint()": "infinite", "execute(address,uint256,bytes)": "infinite", "executeBatch(address[],bytes[])": "infinite", "executeBatch(address[],uint256[],bytes[])": "infinite", "getDeposit()": "infinite", - "getMessageHash(bytes)": "infinite", "getNonce()": "infinite", "initialize(address)": "infinite", "isValidSignature(bytes32,bytes)": "infinite", "onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)": "infinite", - "onERC1155Received(address,address,uint256,uint256,bytes)": "infinite", - "onERC721Received(address,address,uint256,bytes)": "infinite", - "owner()": "2551", + "onERC1155Received(address,address,uint256,uint256,bytes)": "876", + "onERC721Received(address,address,uint256,bytes)": "497", + "owner()": "2532", "proxiableUUID()": "infinite", - "supportsInterface(bytes4)": "329", - "tokensReceived(address,address,address,uint256,bytes,bytes)": "infinite", - "transferOwnership(address)": "30830", - "upgradeTo(address)": "infinite", + "supportsInterface(bytes4)": "316", + "transferOwnership(address)": "infinite", "upgradeToAndCall(address,bytes)": "infinite", - "validateUserOp((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes32,uint256)": "infinite", + "validateUserOp((address,uint256,bytes,bytes,bytes32,uint256,bytes32,bytes,bytes),bytes32,uint256)": "infinite", "withdrawDepositTo(address,uint256)": "infinite" }, "internal": { - "_authorizeUpgrade(address)": "infinite", - "_call(address,uint256,bytes memory)": "infinite", + "_domainNameAndVersion()": "infinite", "_getStorage()": "infinite", "_initialize(address)": "infinite", - "_onlyOwner()": "infinite", - "_requireFromEntryPointOrOwner()": "infinite", + "_isFromOwner()": "infinite", + "_isValidContractOwnerSignatureNow(bytes32,bytes memory)": "infinite", + "_isValidEOAOwnerSignature(bytes32,bytes memory)": "infinite", + "_isValidSignature(bytes32,bytes calldata)": "infinite", "_transferOwnership(address)": "infinite", - "_validateSignature(struct UserOperation calldata,bytes32)": "infinite" + "_validateSignature(struct PackedUserOperation calldata,bytes32)": "infinite" } } ``` @@ -214,16 +238,136 @@ null ```json { "creation": { - "codeDepositCost": "458600", + "codeDepositCost": "673600", "executionCost": "infinite", "totalCost": "infinite" }, "external": { - "accountImplementation()": "infinite", + "ACCOUNT_IMPLEMENTATION()": "infinite", + "ENTRY_POINT()": "infinite", + "acceptOwnership()": "50212", + "addStake(uint32,uint256)": "infinite", "createAccount(address,uint256)": "infinite", - "getAddress(address,uint256)": "infinite" + "getAddress(address,uint256)": "infinite", + "owner()": "2387", + "pendingOwner()": "2505", + "renounceOwnership()": "2375", + "transferOwnership(address)": "30453", + "unlockStake()": "infinite", + "withdraw(address,address,uint256)": "infinite", + "withdrawStake(address)": "infinite" + }, + "internal": { + "_getCombinedSalt(address,uint256)": "infinite" + } +} +``` + +`forge inspect src/MultiOwnerLightAccount.sol:MultiOwnerLightAccount gasestimates` +```json +{ + "creation": { + "codeDepositCost": "1840600", + "executionCost": "infinite", + "totalCost": "infinite" }, - "internal": {} + "external": { + "addDeposit()": "infinite", + "eip712Domain()": "infinite", + "entryPoint()": "infinite", + "execute(address,uint256,bytes)": "infinite", + "executeBatch(address[],bytes[])": "infinite", + "executeBatch(address[],uint256[],bytes[])": "infinite", + "getDeposit()": "infinite", + "getNonce()": "infinite", + "initialize(address[])": "infinite", + "isValidSignature(bytes32,bytes)": "infinite", + "onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)": "infinite", + "onERC1155Received(address,address,uint256,uint256,bytes)": "884", + "onERC721Received(address,address,uint256,bytes)": "494", + "owners()": "infinite", + "proxiableUUID()": "infinite", + "supportsInterface(bytes4)": "315", + "updateOwners(address[],address[])": "infinite", + "upgradeToAndCall(address,bytes)": "infinite", + "validateUserOp((address,uint256,bytes,bytes,bytes32,uint256,bytes32,bytes,bytes),bytes32,uint256)": "infinite", + "withdrawDepositTo(address,uint256)": "infinite" + }, + "internal": { + "_addOwnersOrRevert(address[] memory)": "infinite", + "_domainNameAndVersion()": "infinite", + "_getStorage()": "infinite", + "_initialize(address[] calldata)": "infinite", + "_isFromOwner()": "infinite", + "_isValidContractOwnerSignatureNowLoop(bytes32,bytes memory)": "infinite", + "_isValidContractOwnerSignatureNowSingle(address,bytes32,bytes memory)": "infinite", + "_isValidEOAOwnerSignature(bytes32,bytes memory)": "infinite", + "_isValidSignature(bytes32,bytes calldata)": "infinite", + "_removeOwnersOrRevert(address[] memory)": "infinite", + "_updateOwners(address[] memory,address[] memory)": "infinite", + "_validateSignature(struct PackedUserOperation calldata,bytes32)": "infinite" + } } ``` +`forge inspect src/MultiOwnerLightAccountFactory.sol:MultiOwnerLightAccountFactory gasestimates` +```json +{ + "creation": { + "codeDepositCost": "969400", + "executionCost": "infinite", + "totalCost": "infinite" + }, + "external": { + "ACCOUNT_IMPLEMENTATION()": "infinite", + "ENTRY_POINT()": "infinite", + "acceptOwnership()": "50212", + "addStake(uint32,uint256)": "infinite", + "createAccount(address[],uint256)": "infinite", + "createAccountSingle(address,uint256)": "infinite", + "getAddress(address[],uint256)": "infinite", + "owner()": "2365", + "pendingOwner()": "2527", + "renounceOwnership()": "2375", + "transferOwnership(address)": "30475", + "unlockStake()": "infinite", + "withdraw(address,address,uint256)": "infinite", + "withdrawStake(address)": "infinite" + }, + "internal": { + "_getCombinedSalt(address[] memory,uint256)": "infinite", + "_validateOwnersArray(address[] memory)": "infinite" + } +} +``` + +`forge inspect src/common/BaseLightAccount.sol:BaseLightAccount gasestimates` +```json +null +``` + +`forge inspect src/common/BaseLightAccountFactory.sol:BaseLightAccountFactory gasestimates` +```json +null +``` + +`forge inspect src/common/CustomSlotInitializable.sol:CustomSlotInitializable gasestimates` +```json +null +``` + +`forge inspect src/common/ERC1271.sol:ERC1271 gasestimates` +```json +null +``` + +`forge inspect src/external/solady/EIP712.sol:EIP712 gasestimates` +```json +null +``` + +`forge inspect src/external/solady/UUPSUpgradeable.sol:UUPSUpgradeable gasestimates` +```json +null +``` + diff --git a/.storagelayout.md b/.storagelayout.md index 26af865..1ed23eb 100644 --- a/.storagelayout.md +++ b/.storagelayout.md @@ -3,15 +3,49 @@ Generated via `bash utils/inspect.sh`. --- -`forge inspect --pretty src/CustomSlotInitializable.sol:CustomSlotInitializable storage-layout` +`forge inspect --pretty src/LightAccount.sol:LightAccount storage-layout` | Name | Type | Slot | Offset | Bytes | Contract | |------|------|------|--------|-------|----------| -`forge inspect --pretty src/LightAccount.sol:LightAccount storage-layout` +`forge inspect --pretty src/LightAccountFactory.sol:LightAccountFactory storage-layout` +| Name | Type | Slot | Offset | Bytes | Contract | +|---------------|---------|------|--------|-------|-------------------------------------------------| +| _owner | address | 0 | 0 | 20 | src/LightAccountFactory.sol:LightAccountFactory | +| _pendingOwner | address | 1 | 0 | 20 | src/LightAccountFactory.sol:LightAccountFactory | + +`forge inspect --pretty src/MultiOwnerLightAccount.sol:MultiOwnerLightAccount storage-layout` | Name | Type | Slot | Offset | Bytes | Contract | |------|------|------|--------|-------|----------| -`forge inspect --pretty src/LightAccountFactory.sol:LightAccountFactory storage-layout` +`forge inspect --pretty src/MultiOwnerLightAccountFactory.sol:MultiOwnerLightAccountFactory storage-layout` +| Name | Type | Slot | Offset | Bytes | Contract | +|---------------|---------|------|--------|-------|---------------------------------------------------------------------| +| _owner | address | 0 | 0 | 20 | src/MultiOwnerLightAccountFactory.sol:MultiOwnerLightAccountFactory | +| _pendingOwner | address | 1 | 0 | 20 | src/MultiOwnerLightAccountFactory.sol:MultiOwnerLightAccountFactory | + +`forge inspect --pretty src/common/BaseLightAccount.sol:BaseLightAccount storage-layout` +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +`forge inspect --pretty src/common/BaseLightAccountFactory.sol:BaseLightAccountFactory storage-layout` +| Name | Type | Slot | Offset | Bytes | Contract | +|---------------|---------|------|--------|-------|----------------------------------------------------------------| +| _owner | address | 0 | 0 | 20 | src/common/BaseLightAccountFactory.sol:BaseLightAccountFactory | +| _pendingOwner | address | 1 | 0 | 20 | src/common/BaseLightAccountFactory.sol:BaseLightAccountFactory | + +`forge inspect --pretty src/common/CustomSlotInitializable.sol:CustomSlotInitializable storage-layout` +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +`forge inspect --pretty src/common/ERC1271.sol:ERC1271 storage-layout` +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +`forge inspect --pretty src/external/solady/EIP712.sol:EIP712 storage-layout` +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +`forge inspect --pretty src/external/solady/UUPSUpgradeable.sol:UUPSUpgradeable storage-layout` | Name | Type | Slot | Offset | Bytes | Contract | |------|------|------|--------|-------|----------| diff --git a/script/Deploy_LightAccountFactory.s.sol b/script/Deploy_LightAccountFactory.s.sol index 73354fd..84b93a5 100644 --- a/script/Deploy_LightAccountFactory.s.sol +++ b/script/Deploy_LightAccountFactory.s.sol @@ -39,9 +39,10 @@ contract Deploy_LightAccountFactory is Script { console.log("******** Deploy ...... *********"); console.log("********************************"); + // TODO: Use environment variable for factory owner. LightAccountFactory factory = new LightAccountFactory{ salt: 0x4e59b44847b379578588920ca78fbf26c0b4956c5528f3e2f146000008fabf77 - }(entryPoint); + }(msg.sender, entryPoint); // Deployed address check if (address(factory) != 0x00004EC70002a32400f8ae005A26081065620D20) { @@ -52,7 +53,7 @@ contract Deploy_LightAccountFactory is Script { console.logAddress(address(factory)); console.log("Implementation address:"); - console.logAddress(address(factory.accountImplementation())); + console.logAddress(address(factory.ACCOUNT_IMPLEMENTATION())); vm.stopBroadcast(); } } diff --git a/src/LightAccountFactory.sol b/src/LightAccountFactory.sol index c554989..69cd602 100644 --- a/src/LightAccountFactory.sol +++ b/src/LightAccountFactory.sol @@ -1,8 +1,10 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.23; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; +import {BaseLightAccountFactory} from "./common/BaseLightAccountFactory.sol"; import {LibClone} from "./external/solady/LibClone.sol"; import {LightAccount} from "./LightAccount.sol"; @@ -10,11 +12,12 @@ import {LightAccount} from "./LightAccount.sol"; /// @dev A UserOperations "initCode" holds the address of the factory, and a method call (`createAccount`). The /// factory's `createAccount` returns the target account address even if it is already installed. This way, /// `entryPoint.getSenderAddress()` can be called either before or after the account is created. -contract LightAccountFactory { - LightAccount public immutable accountImplementation; +contract LightAccountFactory is BaseLightAccountFactory { + LightAccount public immutable ACCOUNT_IMPLEMENTATION; - constructor(IEntryPoint entryPoint) { - accountImplementation = new LightAccount(entryPoint); + constructor(address owner, IEntryPoint entryPoint) Ownable(owner) { + ACCOUNT_IMPLEMENTATION = new LightAccount(entryPoint); + ENTRY_POINT = entryPoint; } /// @notice Create an account, and return its address. Returns the address even if the account is already deployed. @@ -26,7 +29,7 @@ contract LightAccountFactory { /// @return account The address of either the newly deployed account or an existing account with this owner and salt. function createAccount(address owner, uint256 salt) public returns (LightAccount account) { (bool alreadyDeployed, address accountAddress) = - LibClone.createDeterministicERC1967(address(accountImplementation), _getCombinedSalt(owner, salt)); + LibClone.createDeterministicERC1967(address(ACCOUNT_IMPLEMENTATION), _getCombinedSalt(owner, salt)); account = LightAccount(payable(accountAddress)); @@ -41,7 +44,7 @@ contract LightAccountFactory { /// @return The address of the account that would be created with `createAccount`. function getAddress(address owner, uint256 salt) public view returns (address) { return LibClone.predictDeterministicAddressERC1967( - address(accountImplementation), _getCombinedSalt(owner, salt), address(this) + address(ACCOUNT_IMPLEMENTATION), _getCombinedSalt(owner, salt), address(this) ); } diff --git a/src/MultiOwnerLightAccountFactory.sol b/src/MultiOwnerLightAccountFactory.sol index e4b184b..7ea0dde 100644 --- a/src/MultiOwnerLightAccountFactory.sol +++ b/src/MultiOwnerLightAccountFactory.sol @@ -1,8 +1,10 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.23; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; +import {BaseLightAccountFactory} from "./common/BaseLightAccountFactory.sol"; import {LibClone} from "./external/solady/LibClone.sol"; import {MultiOwnerLightAccount} from "./MultiOwnerLightAccount.sol"; @@ -10,16 +12,17 @@ import {MultiOwnerLightAccount} from "./MultiOwnerLightAccount.sol"; /// @dev A UserOperations "initCode" holds the address of the factory, and a method call (`createAccount` or /// `createAccountSingle`). The factory returns the target account address even if it is already deployed. This way, /// `entryPoint.getSenderAddress()` can be called either before or after the account is created. -contract MultiOwnerLightAccountFactory { +contract MultiOwnerLightAccountFactory is BaseLightAccountFactory { uint256 internal constant _MAX_OWNERS_ON_CREATION = 100; - MultiOwnerLightAccount public immutable accountImplementation; + MultiOwnerLightAccount public immutable ACCOUNT_IMPLEMENTATION; error InvalidOwners(); error OwnersArrayEmpty(); error OwnersLimitExceeded(); - constructor(IEntryPoint entryPoint) { - accountImplementation = new MultiOwnerLightAccount(entryPoint); + constructor(address owner, IEntryPoint entryPoint) Ownable(owner) { + ACCOUNT_IMPLEMENTATION = new MultiOwnerLightAccount(entryPoint); + ENTRY_POINT = entryPoint; } /// @notice Create an account, and return its address. Returns the address even if the account is already deployed. @@ -33,7 +36,7 @@ contract MultiOwnerLightAccountFactory { _validateOwnersArray(owners); (bool alreadyDeployed, address accountAddress) = - LibClone.createDeterministicERC1967(address(accountImplementation), _getCombinedSalt(owners, salt)); + LibClone.createDeterministicERC1967(address(ACCOUNT_IMPLEMENTATION), _getCombinedSalt(owners, salt)); account = MultiOwnerLightAccount(payable(accountAddress)); @@ -54,7 +57,7 @@ contract MultiOwnerLightAccountFactory { _validateOwnersArray(owners); (bool alreadyDeployed, address accountAddress) = - LibClone.createDeterministicERC1967(address(accountImplementation), _getCombinedSalt(owners, salt)); + LibClone.createDeterministicERC1967(address(ACCOUNT_IMPLEMENTATION), _getCombinedSalt(owners, salt)); account = MultiOwnerLightAccount(payable(accountAddress)); @@ -71,7 +74,7 @@ contract MultiOwnerLightAccountFactory { _validateOwnersArray(owners); return LibClone.predictDeterministicAddressERC1967( - address(accountImplementation), _getCombinedSalt(owners, salt), address(this) + address(ACCOUNT_IMPLEMENTATION), _getCombinedSalt(owners, salt), address(this) ); } diff --git a/src/common/BaseLightAccountFactory.sol b/src/common/BaseLightAccountFactory.sol new file mode 100644 index 0000000..f3d3b25 --- /dev/null +++ b/src/common/BaseLightAccountFactory.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.23; + +import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; + +abstract contract BaseLightAccountFactory is Ownable2Step { + IEntryPoint public immutable ENTRY_POINT; + + error InvalidAction(); + error TransferFailed(); + + /// @notice Allow contract to receive native currency. + receive() external payable {} + + /// @notice Add stake to an entry point. + /// @dev Only callable by owner. + /// @param unstakeDelay Unstake delay for the stake. + /// @param amount Amount of native currency to stake. + function addStake(uint32 unstakeDelay, uint256 amount) external payable onlyOwner { + ENTRY_POINT.addStake{value: amount}(unstakeDelay); + } + + /// @notice Start unlocking stake for an entry point. + /// @dev Only callable by owner. + function unlockStake() external onlyOwner { + ENTRY_POINT.unlockStake(); + } + + /// @notice Withdraw stake from an entry point. + /// @dev Only callable by owner. + /// @param to Address to send native currency to. + function withdrawStake(address payable to) external onlyOwner { + ENTRY_POINT.withdrawStake(to); + } + + /// @notice Withdraw funds from this contract. + /// @dev Can withdraw stuck erc20s or native currency. + /// @param to Address to send erc20s or native currency to. + /// @param token Address of the token to withdraw, 0 address for native currency. + /// @param amount Amount of the token to withdraw in case of rebasing tokens. + function withdraw(address payable to, address token, uint256 amount) external onlyOwner { + if (token == address(0)) { + (bool success,) = to.call{value: address(this).balance}(""); + if (!success) { + revert TransferFailed(); + } + } else { + SafeERC20.safeTransfer(IERC20(token), to, amount); + } + } + + /// @notice Overriding to disable renounce ownership in Ownable. + function renounceOwnership() public view override onlyOwner { + revert InvalidAction(); + } +} diff --git a/test/LightAccount.t.sol b/test/LightAccount.t.sol index 899da5e..ce39117 100644 --- a/test/LightAccount.t.sol +++ b/test/LightAccount.t.sol @@ -38,7 +38,7 @@ contract LightAccountTest is Test { function setUp() public { eoaAddress = vm.addr(EOA_PRIVATE_KEY); entryPoint = new EntryPoint(); - LightAccountFactory factory = new LightAccountFactory(entryPoint); + LightAccountFactory factory = new LightAccountFactory(address(this), entryPoint); account = factory.createAccount(eoaAddress, 1); vm.deal(address(account), 1 << 128); lightSwitch = new LightSwitch(); @@ -173,14 +173,14 @@ contract LightAccountTest is Test { } function testInitialize() public { - LightAccountFactory factory = new LightAccountFactory(entryPoint); + LightAccountFactory factory = new LightAccountFactory(address(this), entryPoint); vm.expectEmit(true, false, false, false); emit Initialized(0); account = factory.createAccount(eoaAddress, 1); } function testCannotInitializeWithZeroOwner() public { - LightAccountFactory factory = new LightAccountFactory(entryPoint); + LightAccountFactory factory = new LightAccountFactory(address(this), entryPoint); vm.expectRevert(abi.encodeWithSelector(LightAccount.InvalidOwner.selector, (address(0)))); account = factory.createAccount(address(0), 1); } @@ -487,7 +487,7 @@ contract LightAccountTest is Test { bytes32(uint256(uint160(0x0000000071727De22E5E9d8BAf0edAc6f37da032))) ) ), - 0x6aa8e89cd8a2df2df153f6959aace173a48e4ecedde04f7dc09add425d88a7e2 + 0x10f16a2b2ab8a4a6c680a35494382da995eea00c40bed1fc5391774da7de7fc9 ); } diff --git a/test/LightAccountFactory.t.sol b/test/LightAccountFactory.t.sol index 69b9c0e..487d4fc 100644 --- a/test/LightAccountFactory.t.sol +++ b/test/LightAccountFactory.t.sol @@ -5,10 +5,11 @@ import "forge-std/Test.sol"; import {EntryPoint} from "account-abstraction/core/EntryPoint.sol"; +import {BaseLightAccountFactory} from "../src/common/BaseLightAccountFactory.sol"; import {LightAccount} from "../src/LightAccount.sol"; import {LightAccountFactory} from "../src/LightAccountFactory.sol"; -contract LightAccountTest is Test { +contract LightAccountFactoryTest is Test { using stdStorage for StdStorage; address public constant OWNER_ADDRESS = address(0x100); @@ -17,7 +18,7 @@ contract LightAccountTest is Test { function setUp() public { entryPoint = new EntryPoint(); - factory = new LightAccountFactory(entryPoint); + factory = new LightAccountFactory(address(this), entryPoint); } function testReturnsAddressWhenAccountAlreadyExists() public { @@ -33,4 +34,53 @@ contract LightAccountTest is Test { assertTrue(address(factual).codehash != bytes32(0)); assertEq(counterfactual, address(factual)); } + + function testAddStake() public { + assertEq(entryPoint.balanceOf(address(factory)), 0); + vm.deal(address(this), 100 ether); + factory.addStake{value: 10 ether}(10 hours, 10 ether); + assertEq(entryPoint.getDepositInfo(address(factory)).stake, 10 ether); + } + + function testUnlockStake() public { + testAddStake(); + factory.unlockStake(); + assertEq(entryPoint.getDepositInfo(address(factory)).withdrawTime, block.timestamp + 10 hours); + } + + function testWithdrawStake() public { + testUnlockStake(); + vm.warp(10 hours); + vm.expectRevert("Stake withdrawal is not due"); + factory.withdrawStake(payable(address(this))); + assertEq(address(this).balance, 90 ether); + vm.warp(10 hours + 1); + factory.withdrawStake(payable(address(this))); + assertEq(address(this).balance, 100 ether); + } + + function testWithdraw() public { + factory.addStake{value: 10 ether}(10 hours, 1 ether); + assertEq(address(factory).balance, 9 ether); + factory.withdraw(payable(address(this)), address(0), 0); // amount = balance if native currency + assertEq(address(factory).balance, 0); + } + + function test2StepOwnershipTransfer() public { + address owner1 = address(0x200); + assertEq(factory.owner(), address(this)); + factory.transferOwnership(owner1); + assertEq(factory.owner(), address(this)); + vm.prank(owner1); + factory.acceptOwnership(); + assertEq(factory.owner(), owner1); + } + + function testCannotRenounceOwnership() public { + vm.expectRevert(BaseLightAccountFactory.InvalidAction.selector); + factory.renounceOwnership(); + } + + /// @dev Receive funds from withdraw. + receive() external payable {} } diff --git a/test/MultiOwnerLightAccount.t.sol b/test/MultiOwnerLightAccount.t.sol index 5c91e81..42146e3 100644 --- a/test/MultiOwnerLightAccount.t.sol +++ b/test/MultiOwnerLightAccount.t.sol @@ -41,7 +41,7 @@ contract MultiOwnerLightAccountTest is Test { function setUp() public { eoaAddress = vm.addr(EOA_PRIVATE_KEY); entryPoint = new EntryPoint(); - MultiOwnerLightAccountFactory factory = new MultiOwnerLightAccountFactory(entryPoint); + MultiOwnerLightAccountFactory factory = new MultiOwnerLightAccountFactory(address(this), entryPoint); account = factory.createAccountSingle(eoaAddress, 1); vm.deal(address(account), 1 << 128); lightSwitch = new LightSwitch(); @@ -223,14 +223,14 @@ contract MultiOwnerLightAccountTest is Test { } function testInitialize() public { - MultiOwnerLightAccountFactory factory = new MultiOwnerLightAccountFactory(entryPoint); + MultiOwnerLightAccountFactory factory = new MultiOwnerLightAccountFactory(address(this), entryPoint); vm.expectEmit(true, false, false, false); emit Initialized(0); account = factory.createAccountSingle(eoaAddress, 1); } function testCannotInitializeWithZeroOwner() public { - MultiOwnerLightAccountFactory factory = new MultiOwnerLightAccountFactory(entryPoint); + MultiOwnerLightAccountFactory factory = new MultiOwnerLightAccountFactory(address(this), entryPoint); vm.expectRevert(MultiOwnerLightAccountFactory.InvalidOwners.selector); account = factory.createAccountSingle(address(0), 1); } @@ -662,7 +662,7 @@ contract MultiOwnerLightAccountTest is Test { bytes32(uint256(uint160(0x0000000071727De22E5E9d8BAf0edAc6f37da032))) ) ), - 0x5f04ff963bc2b9faa9a30beff91afc7cdf84f8a52f369b70060fe0f41b0504df + 0x04ef3a9310d1a2d867807831aa0361b20aad25421de58cf37457fcf7f9567b6a ); } diff --git a/test/MultiOwnerLightAccountFactory.t.sol b/test/MultiOwnerLightAccountFactory.t.sol index 127c960..ce0ca47 100644 --- a/test/MultiOwnerLightAccountFactory.t.sol +++ b/test/MultiOwnerLightAccountFactory.t.sol @@ -5,6 +5,7 @@ import "forge-std/Test.sol"; import {EntryPoint} from "account-abstraction/core/EntryPoint.sol"; +import {BaseLightAccountFactory} from "../src/common/BaseLightAccountFactory.sol"; import {MultiOwnerLightAccount} from "../src/MultiOwnerLightAccount.sol"; import {MultiOwnerLightAccountFactory} from "../src/MultiOwnerLightAccountFactory.sol"; @@ -17,7 +18,7 @@ contract MultiOwnerLightAccountFactoryTest is Test { function setUp() public { entryPoint = new EntryPoint(); - factory = new MultiOwnerLightAccountFactory(entryPoint); + factory = new MultiOwnerLightAccountFactory(address(this), entryPoint); owners = new address[](1); owners[0] = address(1); } @@ -109,4 +110,53 @@ contract MultiOwnerLightAccountFactoryTest is Test { vm.expectRevert(MultiOwnerLightAccountFactory.OwnersArrayEmpty.selector); factory.createAccount(owners, 1); } + + function testAddStake() public { + assertEq(entryPoint.balanceOf(address(factory)), 0); + vm.deal(address(this), 100 ether); + factory.addStake{value: 10 ether}(10 hours, 10 ether); + assertEq(entryPoint.getDepositInfo(address(factory)).stake, 10 ether); + } + + function testUnlockStake() public { + testAddStake(); + factory.unlockStake(); + assertEq(entryPoint.getDepositInfo(address(factory)).withdrawTime, block.timestamp + 10 hours); + } + + function testWithdrawStake() public { + testUnlockStake(); + vm.warp(10 hours); + vm.expectRevert("Stake withdrawal is not due"); + factory.withdrawStake(payable(address(this))); + assertEq(address(this).balance, 90 ether); + vm.warp(10 hours + 1); + factory.withdrawStake(payable(address(this))); + assertEq(address(this).balance, 100 ether); + } + + function testWithdraw() public { + factory.addStake{value: 10 ether}(10 hours, 1 ether); + assertEq(address(factory).balance, 9 ether); + factory.withdraw(payable(address(this)), address(0), 0); // amount = balance if native currency + assertEq(address(factory).balance, 0); + } + + function test2StepOwnershipTransfer() public { + address owner1 = address(0x200); + assertEq(factory.owner(), address(this)); + factory.transferOwnership(owner1); + assertEq(factory.owner(), address(this)); + vm.prank(owner1); + factory.acceptOwnership(); + assertEq(factory.owner(), owner1); + } + + function testCannotRenounceOwnership() public { + vm.expectRevert(BaseLightAccountFactory.InvalidAction.selector); + factory.renounceOwnership(); + } + + /// @dev Receive funds from withdraw. + receive() external payable {} }