diff --git a/.gitignore b/.gitignore index 49e2b37..bab69d5 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,6 @@ artifacts flattened.txt # Ignore Broadcasts -broadcast \ No newline at end of file +broadcast + +.gas-snapshot \ No newline at end of file diff --git a/README.md b/README.md index 8a59c2c..192c50d 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,11 @@ The most gas optimized ERC-4337 account - written in Huff > > The main usage of these contracts is to benchmark other ERC-4337 accounts against the lowest possible gas cost for an account. -## Gas calculations (as of Sep 10, 2023) +## Gas calculations (as of Sep 12, 2023) | | Creation | Native transfer | ERC20 transfer | Total | | ---------------- | -------- | --------------- | -------------- | ------ | -| MinimalAccount | 243481 | 94023 | 82760 | 420264 | +| MinimalAccount | 236573 | 93580 | 82317 | 412470 | | SimpleAccount | 410061 | 97690 | 86754 | 594505 | | Biconomy | 296892 | 100780 | 89577 | 487249 | | Etherspot | 305769 | 100091 | 89172 | 495032 | @@ -26,7 +26,7 @@ Gas difference between MinimalAccount and cheapest account in each category: | | Creation | Native transfer | ERC20 transfer | Total | | ------------- | -------- | --------------- | -------------- | ----- | -| MA cheaper by | 13484 | 3308 | 3361 | 20153 | +| MA cheaper by | 20392 | 3751 | 3804 | 27947 | Calculations are based on ZeroDev's [AA Benchmark](https://github.com/zerodevapp/aa-benchmark) @@ -61,6 +61,7 @@ For more information on how to use Foundry, check out the [Foundry Github Reposi ## Todo - [x] Dynamically splice owner account into bytecode +- [x] Remove unnecessary jumps ## License diff --git a/src/MinimalAccount.huff b/src/MinimalAccount.huff index 89038e7..95fa9d1 100644 --- a/src/MinimalAccount.huff +++ b/src/MinimalAccount.huff @@ -17,33 +17,12 @@ ///@dev Ethereum Signed Message header #define constant SIG_HEADER = 0x0000000019457468657265756d205369676e6564204d6573736167653a0a3332 -/* Helpers */ -#define macro REQUIRE_FROM_ENTRYPOINT() = takes (0) returns (0) { - caller [ENTRYPOINT_ADDRESS] eq fromEntrypoint jumpi - - 0x00 0x00 revert - - fromEntrypoint: -} - -#define macro PAY_PREFUND() = takes (0) returns (0) { - 0x44 calldataload // [value] - dup1 // [value, value] - 0x00 eq noPrefund jumpi // [value] - - 0x00 // [retOffset, value] - 0x00 // [retSize, retOffset, value] - 0x00 // [argSize, retOffset, retSize, value] - 0x00 // [argOffset, argSize, retOffset, retSize, value] - swap4 // [value, argOffset, argSize, retOffset, retSize] - caller // [target, value, argOffset, argSize, retOffset, retSize] - gas // [gas, target, value, argOffset, argSize, retOffset, retSize] - call // [success] - - noPrefund: -} - -#define macro VALIDATE_SIGNATURE() = takes (0) returns (0) { +/* External functions */ +#define macro VALIDATE_USEROP() = takes (0) returns (0) { + // 1. Require from entrypoint + caller [ENTRYPOINT_ADDRESS] eq iszero notEntryPoint jumpi + + // 2. Validate signature 0x1a4 calldataload // [sigSlot] 0xa4 add // [sSlot] dup1 calldataload // [s, sSlot] @@ -67,7 +46,7 @@ // jump to `zero`. [MALLEABILITY_THRESHOLD] // [malleability_threshold, hash, v, r, s] dup5 gt // [s > malleability_threshold, hash, v, r, s] - zero jumpi // [hash, v, r, s] + invalidSignature jumpi // [hash, v, r, s] 0x00 mstore // [v, r, s] 0x20 mstore // [r, s] @@ -77,37 +56,43 @@ 0x20 0x40 0x80 0x00 0x01 // [0x01, 0x00, 0x80, 0x40, 0x20] gas staticcall pop // [] - // Restore the zero slot - 0x00 0x60 mstore // [] returndatasize 0x60 sub // [0x60 - returndatasize] mload // [result] - end jump - - zero: - pop pop pop pop 0x00 // [0x00] - end: - [OWNER_ADDRESS] eq success jumpi + [OWNER_ADDRESS] eq iszero invalidSignature jumpi - 0x01 0x00 mstore - 0x20 0x00 return + // 3. Pay prefund + 0x44 calldataload // [value] + dup1 // [value, value] + iszero noPrefund jumpi // [value] - success: -} + 0x00 // [retOffset, value] + 0x00 // [retSize, retOffset, value] + 0x00 // [argSize, retOffset, retSize, value] + 0x00 // [argOffset, argSize, retOffset, retSize, value] + swap4 // [value, argOffset, argSize, retOffset, retSize] + caller // [target, value, argOffset, argSize, retOffset, retSize] + gas // [gas, target, value, argOffset, argSize, retOffset, retSize] + call // [success] -/* External functions */ -#define macro VALIDATE_USEROP() = takes (0) returns (0) { - REQUIRE_FROM_ENTRYPOINT() - VALIDATE_SIGNATURE() - PAY_PREFUND() + noPrefund: 0x20 0x80 return + + invalidSignature: + 0x01 0x00 mstore + 0x20 0x00 return + + notEntryPoint: + 0x00 0x00 revert } #define macro EXECUTE() = takes (0) returns (0) { - REQUIRE_FROM_ENTRYPOINT() + // 1. Require from entrypoint + caller [ENTRYPOINT_ADDRESS] eq iszero notEntryPoint jumpi + // 2. Execute call 0x64 calldataload // [argSize] dup1 // [argSize, argSize] 0x84 // [offset, argSize, argSize] @@ -122,6 +107,11 @@ 0x04 calldataload // [target, value, argOffset, argSize, retOffset, retSize] gas // [gas, target, value, argOffset, argSize, retOffset, retSize] call // [success] + + stop + + notEntryPoint: + 0x00 0x00 revert } #define macro MAIN() = takes (0) returns (0) { diff --git a/src/MinimalAccountFactory.huff b/src/MinimalAccountFactory.huff index b1959c3..066c002 100644 --- a/src/MinimalAccountFactory.huff +++ b/src/MinimalAccountFactory.huff @@ -8,29 +8,25 @@ #define function getAddress(address,uint256) view returns (address) /* Constants */ -#define constant ACCOUNT_INITCODE = 0x61015880600a3d393df360003560e01c80633a871cdd14610021578063b61d27f61461011e5760006000fd5b33735ff137d4b0fdcd49dca30c7cf57e578a026d2789146100425760006000fd5b6101a43560a401803590602090038035906040013560f81c6024356020527b19457468657265756d205369676e6564204d6573736167653a0a3332600052603c6004207f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084116100d457600052602052604052606052602060406080600060015afa5060006060523d606003516100db565b5050505060005b737e5f4552091a69125d5dfcb7b8c2659029395bdf1461010057600160005260206000f35b6044358060001461011857600060006000600093335af15b60206080f35b33735ff137d4b0fdcd49dca30c7cf57e578a026d27891461013f5760006000fd5b606435806084600037600060209160006024356004355af1 -#define constant ACCOUNT_INITCODE_LENGTH = 0x164 -#define constant ACCOUNT_INITCODE_HASH = 0xf62ad50462f93b6a5d1b025f45d9b88bb5ea39d677f69a666816cfdf628fd446 +#define constant ACCOUNT_INITCODE = 0x61014980600a3d393df360003560e01c80633a871cdd14610021578063b61d27f61461010d5760006000fd5b33735ff137d4b0fdcd49dca30c7cf57e578a026d27891415610107576101a43560a401803590602090038035906040013560f81c6024356020527b19457468657265756d205369676e6564204d6573736167653a0a3332600052603c6004207f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084116100fc57600052602052604052606052602060406080600060015afa503d60600351737e5f4552091a69125d5dfcb7b8c2659029395bdf14156100fc5760443580156100f657600060006000600093335af15b60206080f35b600160005260206000f35b60006000fd5b33735ff137d4b0fdcd49dca30c7cf57e578a026d2789141561014357606435806084600037600060209160006024356004355af1005b60006000fd +#define constant ACCOUNT_INITCODE_LENGTH = 0x153 +#define constant ACCOUNT_INITCODE_HASH = 0x009996837662192b2f225191e8b1a3222f8c66e7c2a1609ef951ce4ccc32cd98 /* Internal functions */ #define macro GET_ACCOUNT_INITCODE() = takes (1) returns (0) { // [ownerAddress] - 0x61015880600a3d393df360003560e01c80633a871cdd14610021578063b61d27 0x00 mstore // [ownerAddress] - 0xf61461011e5760006000fd5b33735ff137d4b0fdcd49dca30c7cf57e578a026d 0x20 mstore // [ownerAddress] - 0x2789146100425760006000fd5b6101a43560a401803590602090038035906040 0x40 mstore // [ownerAddress] - 0x013560f81c6024356020527b19457468657265756d205369676e6564204d6573 0x60 mstore // [ownerAddress] - 0x736167653a0a3332600052603c6004207f7fffffffffffffffffffffffffffff 0x80 mstore // [ownerAddress] - 0xff5d576e7357a4501ddfe92f46681b20a084116100d457600052602052604052 0xa0 mstore // [ownerAddress] - 0x606052602060406080600060015afa5060006060523d606003516100db565b50 0xc0 mstore // [ownerAddress] - - __RIGHTPAD(0x50505060005b73) 0xe0 mstore // [ownerAddress] - 0x60 shl 0xe7 mstore - __RIGHTPAD(0x1461010057) 0xfb mstore - - 0x600160005260206000f35b604435806000146101185760006000600060009333 0x100 mstore - 0x5af15b60206080f35b33735ff137d4b0fdcd49dca30c7cf57e578a026d278914 0x120 mstore - 0x61013f5760006000fd5b60643580608460003760006020916000602435600435 0x140 mstore - 0x5af1000000000000000000000000000000000000000000000000000000000000 0x160 mstore + 0x61014980600a3d393df360003560e01c80633a871cdd14610021578063b61d27 0x00 mstore // [ownerAddress] + 0xf61461010d5760006000fd5b33735ff137d4b0fdcd49dca30c7cf57e578a026d 0x20 mstore // [ownerAddress] + 0x27891415610107576101a43560a401803590602090038035906040013560f81c 0x40 mstore // [ownerAddress] + 0x6024356020527b19457468657265756d205369676e6564204d6573736167653a 0x60 mstore // [ownerAddress] + 0x0a3332600052603c6004207f7fffffffffffffffffffffffffffffff5d576e73 0x80 mstore // [ownerAddress] + 0x57a4501ddfe92f46681b20a084116100fc576000526020526040526060526020 0xa0 mstore // [ownerAddress] + __RIGHTPAD(0x60406080600060015afa503d6060035173) 0xc0 mstore // [ownerAddress] + 0x60 shl 0xd1 mstore + __RIGHTPAD(0x14156100fc5760443580156100f657600060006000600093335af1) 0xe5 mstore // [] + 0x5b60206080f35b600160005260206000f35b60006000fd5b33735ff137d4b0fd 0x100 mstore // [] + 0xcd49dca30c7cf57e578a026d2789141561014357606435806084600037600060 0x120 mstore // [] + __RIGHTPAD(0x209160006024356004355af1005b60006000fd) 0x140 mstore // [] } /* External functions */ diff --git a/test/MinimalAccount.t.sol b/test/MinimalAccount.t.sol index a8c6e88..1434e20 100644 --- a/test/MinimalAccount.t.sol +++ b/test/MinimalAccount.t.sol @@ -29,8 +29,8 @@ contract MinimalAccountTest is Test { minimalAccountFactory = MinimalAccountFactory(HuffDeployer.deploy("MinimalAccountFactory")); // Get bytecode of MinimalAccount and MinimalAccountFactory for gas calculations - // console.logBytes(address(minimalAccount).code); - // console.logBytes(address(minimalAccountFactory).code); + console.logBytes(address(minimalAccount).code); + console.logBytes(address(minimalAccountFactory).code); } function testCreateAccount() public {