Skip to content

Commit

Permalink
Merge pull request #1 from leekt/main
Browse files Browse the repository at this point in the history
Optimized to reduce the redundancy and use v,r,s as signature instead of r,s,v
  • Loading branch information
kopy-kat authored Sep 13, 2023
2 parents 1f44f2c + efdd5db commit 328f5e4
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 126 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The most gas optimized ERC-4337 account - written in Huff

| | Creation | Native transfer | ERC20 transfer | Total |
| ---------------- | -------- | --------------- | -------------- | ------ |
| MinimalAccount | 236573 | 93580 | 82317 | 412470 |
| MinimalAccount | 220508 | 93545 | 82282 | 396335 |
| SimpleAccount | 410061 | 97690 | 86754 | 594505 |
| Biconomy | 296892 | 100780 | 89577 | 487249 |
| Etherspot | 305769 | 100091 | 89172 | 495032 |
Expand All @@ -26,7 +26,7 @@ Gas difference between MinimalAccount and cheapest account in each category:

| | Creation | Native transfer | ERC20 transfer | Total |
| ------------- | -------- | --------------- | -------------- | ----- |
| MA cheaper by | 20392 | 3751 | 3804 | 27947 |
| MA cheaper by | 36457 | 3786 | 3839 | 44082 |

Calculations are based on ZeroDev's [AA Benchmark](https://github.com/zerodevapp/aa-benchmark)

Expand Down
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

[profile.default]
solc_version = '0.8.20'
evm_version = 'shanghai'
evm_version = 'paris'
auto_detect_solc = false
optimizer = true
optimizer_runs = 200 # Default amount
Expand Down
185 changes: 82 additions & 103 deletions src/MinimalAccount.huff
Original file line number Diff line number Diff line change
Expand Up @@ -17,113 +17,92 @@
///@dev Ethereum Signed Message header
#define constant SIG_HEADER = 0x0000000019457468657265756d205369676e6564204d6573736167653a0a3332

/* 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]
swap1 // [sSlot, s]
0x20 swap1 sub // [rSlot, s]
dup1 calldataload // [r, rSlot, s]
swap1 // [rSlot, r, s]
0x40 add // [vSlot, r, s]
calldataload // [v, r, s]
0xF8 shr

0x24 calldataload // [hash, v, r, s]

// Store in scratch space for hashing.
0x20 mstore // [v, r, s]
[SIG_HEADER] 0x00 mstore // [v, r, s]

0x3c 0x04 sha3 // [newHash, v, r, s]

// If `s` is not in lower half order, such that the signature is malleable,
// jump to `zero`.
[MALLEABILITY_THRESHOLD] // [malleability_threshold, hash, v, r, s]
dup5 gt // [s > malleability_threshold, hash, v, r, s]
invalidSignature jumpi // [hash, v, r, s]

0x00 mstore // [v, r, s]
0x20 mstore // [r, s]
0x40 mstore // [s]
0x60 mstore // []

0x20 0x40 0x80 0x00 0x01 // [0x01, 0x00, 0x80, 0x40, 0x20]
gas staticcall pop // []

returndatasize 0x60 sub // [0x60 - returndatasize]
mload // [result]

[OWNER_ADDRESS] eq iszero invalidSignature jumpi

// 3. Pay prefund
0x44 calldataload // [value]
dup1 // [value, value]
iszero 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:

0x20 0x80
return

invalidSignature:
0x01 0x00 mstore
0x20 0x00 return

notEntryPoint:
0x00 0x00 revert
}

#define macro EXECUTE() = takes (0) returns (0) {
// 1. Require from entrypoint
caller [ENTRYPOINT_ADDRESS] eq iszero notEntryPoint jumpi

// 2. Execute call
0x64 calldataload // [argSize]
dup1 // [argSize, argSize]
0x84 // [offset, argSize, argSize]
0x00 // [destOffset, offset, argSize, argSize]
calldatacopy // [argSize]

0x00 // [retOffset, argSize]
0x20 // [retSize, retOffset, argSize]
swap2 // [argSize, retOffset, retSize]
0x00 // [argOffset, argSize, retOffset, retSize]
#define macro MAIN() = takes (0) returns (0) {
calldatasize iszero finish jumpi
// check entrypoint
caller [ENTRYPOINT_ADDRESS] eq iszero revertError jumpi
callvalue calldataload 0xE0 shr
0x3a871cdd eq validate jumpi
//__FUNC_SIG(execute) eq iszero revertError jumpi *assume it will always be execute

0x20 // [retSize]
callvalue // [retOffset, retSize]
0x64 calldataload // [argSize, retOffset, retSize]
dup1 // [argSize, argSize, retOffset, retSize]
0x84 // [offset, argSize, argSize, retOffset, retSize]
callvalue // [destOffset, offset, argSize, argSize, retOffset, retSize]
calldatacopy // [argSize, retOffset, retSize]
callvalue // [argOffset, argSize, retOffset, retSize]
0x24 calldataload // [value, argOffset, argSize, retOffset, retSize]
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) {
0x00 calldataload 0xE0 shr
dup1 0x3a871cdd eq validate jumpi
dup1 __FUNC_SIG(execute) eq execute jumpi

0x00 0x00 revert

validate:
VALIDATE_USEROP()
execute:
EXECUTE()

}
// mstore userOpHash to scratch space
0x40
0x20 // [0x20]
dup1 // [hashSize, 0x20]
0x24 // [hashOffset, hashSize, 0x20]
dup2 // [hashMemOffset, hashOffset, hashSize, 0x20]
calldatacopy // [0x20]
// mstore SIG_HEADER to scratch space
[SIG_HEADER] callvalue mstore // [0x20]
// mstore newHash to callvalue
0x3c 0x04 sha3 // [newHash, 0x20]
callvalue // [0x00, newHash, 0x20]
mstore // [0x20]
// reset 0x20
callvalue // [0x00, 0x20]
dup2 // [0x20, 0x00, 0x20]
mstore // [0x20]

// signature verification
// get sSlot to verify s value
0x41 // [0x41, 0x20]
0x21 // [0x21, 0x41, 0x20]
0x1a4 calldataload // [sigSlot, 0x21, 0x41, 0x20]
0xa5 add // [sSlot, 0x21, 0x41, 0x20]
// If `s` is not in lower half order, such that the signature is malleable,
// jump to `zero`.
[MALLEABILITY_THRESHOLD] // [malleability_threshold, sSlot, 0x21, 0x41, 0x20]
dup2 calldataload // [s, malleability_threshold, sSlot, 0x21, 0x41, 0x20]
gt // [s > malleability_threshold, sSlot, 0x21, 0x41, 0x20]
invalidSignature jumpi // [sSlot, 0x21, 0x41, 0x20]

// mstore signature(v,r,s) to 0x3f
sub // [vSlot, 0x41, 0x20]
0x3f // [0x3f, vSlot, 0x41, 0x20]
calldatacopy // [0x20]

dup2 0x80 callvalue 0x01 // [0x01, 0x00, 0x80, 0x40, 0x20]
gas staticcall pop // []

mload // [result]

[OWNER_ADDRESS] eq iszero invalidSignature jumpi // []

// 3. Pay prefund
0x44 calldataload // [value]
dup1 // [value, value]
iszero finish jumpi // [value]

callvalue // [retOffset, value]
callvalue // [retSize, retOffset, value]
callvalue // [argSize, retOffset, retSize, value]
callvalue // [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]

finish: // [value, 0x20]
0x20 0x80
return
invalidSignature:
0x01 callvalue mstore
0x20 callvalue return
revertError:
callvalue callvalue revert
}
31 changes: 15 additions & 16 deletions src/MinimalAccountFactory.huff
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,24 @@
#define function getAddress(address,uint256) view returns (address)

/* Constants */
#define constant ACCOUNT_INITCODE = 0x61014980600a3d393df360003560e01c80633a871cdd14610021578063b61d27f61461010d5760006000fd5b33735ff137d4b0fdcd49dca30c7cf57e578a026d27891415610107576101a43560a401803590602090038035906040013560f81c6024356020527b19457468657265756d205369676e6564204d6573736167653a0a3332600052603c6004207f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084116100fc57600052602052604052606052602060406080600060015afa503d60600351737e5f4552091a69125d5dfcb7b8c2659029395bdf14156100fc5760443580156100f657600060006000600093335af15b60206080f35b600160005260206000f35b60006000fd5b33735ff137d4b0fdcd49dca30c7cf57e578a026d2789141561014357606435806084600037600060209160006024356004355af1005b60006000fd
#define constant ACCOUNT_INITCODE_LENGTH = 0x153
#define constant ACCOUNT_INITCODE_HASH = 0x009996837662192b2f225191e8b1a3222f8c66e7c2a1609ef951ce4ccc32cd98
#define constant ACCOUNT_INITCODE = 0x60f980600a3d393df336156100e65733735ff137d4b0fdcd49dca30c7cf57e578a026d278914156100f557343560e01c633a871cdd14610046576020346064358060843437346024356004355af1005b6040602080602481377b19457468657265756d205369676e6564204d6573736167653a0a33323452603c6004203452348152604160216101a43560a5017f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08135116100ec5703603f378160803460015afa5051737e5f4552091a69125d5dfcb7b8c2659029395bdf14156100ec5760443580156100e6573434343493335af15b60206080f35b60013452602034f35b3434fd

#define constant ACCOUNT_INITCODE_LENGTH = 0x102

/* Internal functions */
#define macro GET_ACCOUNT_INITCODE() = takes (1) returns (0) {
// [ownerAddress]
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 // []
0x60f98060093d393df336156100e65733735ff137d4b0fdcd49dca30c7cf57e57 0x00 mstore // [ownerAddress]
0x8a026d278914156100f557343560e01c633a871cdd1461004657602034606435 0x20 mstore // [ownerAddress]
0x8060843437346024356004355af1005b6040602080602481377b194574686572 0x40 mstore // [ownerAddress]
0x65756d205369676e6564204d6573736167653a0a33323452603c600420345234 0x60 mstore // [ownerAddress]
0x8152604160216101a43560a5017f7fffffffffffffffffffffffffffffff5d57 0x80 mstore // [ownerAddress]
0x6e7357a4501ddfe92f46681b20a08135116100ec5703603f378160803460015a 0xa0 mstore // [ownerAddress]
__RIGHTPAD(0xfa505173) 0xc0 mstore
0x60 shl 0xc4 mstore
__RIGHTPAD(0x14156100ec576044) 0xd8 mstore // []
0x3580156100e6573434343493335af15b60206080f35b60013452602034f35b34 0xe0 mstore // []
__RIGHTPAD(0x34fd) 0x100 mstore // []
}

/* External functions */
Expand Down Expand Up @@ -85,4 +84,4 @@
getAddress:
GET_ADDRESS()

}
}
12 changes: 9 additions & 3 deletions test/MinimalAccount.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ contract MinimalAccountTest is Test {
function setUp() public {
owner = Owner({key: uint256(1), addr: vm.addr(uint256(1))});
minimalAccount = MinimalAccount(HuffDeployer.deploy("MinimalAccount"));
minimalAccountFactory = MinimalAccountFactory(HuffDeployer.deploy("MinimalAccountFactory"));
minimalAccountFactory = MinimalAccountFactory(HuffDeployer.config().with_evm_version("paris").deploy("MinimalAccountFactory"));

// Get bytecode of MinimalAccount and MinimalAccountFactory for gas calculations
console.logBytes(address(minimalAccount).code);
Expand All @@ -38,6 +38,12 @@ contract MinimalAccountTest is Test {
assertEq(address(minimalAccount).code, address(account).code);
}

function testReceiveETH() public {
address account = minimalAccountFactory.createAccount(address(this), 0);
(bool success, ) = account.call{value:1e18}("");
assertTrue(success);
}

function testGetAccountAddress() public {
address account = minimalAccountFactory.createAccount(address(this), 0);
address accountAddress = minimalAccountFactory.getAddress(address(this), 0);
Expand Down Expand Up @@ -66,7 +72,7 @@ contract MinimalAccountTest is Test {

bytes32 opHash = entryPoint.getUserOpHash(userOp);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(owner.key, ECDSA.toEthSignedMessageHash(opHash));
bytes memory signature = abi.encodePacked(r, s, v);
bytes memory signature = abi.encodePacked(v, r, s);
userOp.signature = signature;

uint256 missingAccountFunds = 420 wei;
Expand Down Expand Up @@ -103,7 +109,7 @@ contract MinimalAccountTest is Test {

bytes32 opHash = entryPoint.getUserOpHash(userOp);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(newKey, ECDSA.toEthSignedMessageHash(opHash));
bytes memory signature = abi.encodePacked(r, s, v);
bytes memory signature = abi.encodePacked(v, r, s);
userOp.signature = signature;

uint256 missingAccountFunds = 420 wei;
Expand Down

0 comments on commit 328f5e4

Please sign in to comment.