Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: signature call buffers #240

Merged
merged 1 commit into from
Oct 15, 2024
Merged

feat: signature call buffers #240

merged 1 commit into from
Oct 15, 2024

Conversation

adamegyed
Copy link
Contributor

Motivation

The last external call that the account makes without call buffers is signature validation, and signature validation hooks.

Solution

Implement call buffers for signature validation.

Add a fuzz test for signature validation inputs.

Reset memory after signature validation in deferred actions to reuse the memory during execution.

@adamegyed adamegyed requested a review from a team October 12, 2024 02:29
@adamegyed adamegyed force-pushed the adam/call-buffers-sig branch from 1a83dd3 to dc22b1e Compare October 12, 2024 02:32
@adamegyed adamegyed force-pushed the adam/deferred-action branch from 1aef4f5 to dea11e5 Compare October 14, 2024 15:52
@adamegyed adamegyed force-pushed the adam/call-buffers-sig branch from dc22b1e to 5826833 Compare October 14, 2024 16:01
@@ -541,9 +540,12 @@ abstract contract ModularAccountBase is
uoValidation
);

// Clear the memory after performing signature validation
MemSnapshot memSnapshot = MemManagementLib.freezeFMP();
Copy link
Contributor

@Zer0dot Zer0dot Oct 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dude this feels like a sick pattern we could probably use pretty often, but is it safe? Are we sure we're not making obsolete some memory we might not know about?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't really think of another place to use it: it doesn't matter for UO validation, and for RT validation, we only allocate the call buffer after loading the pre-validation hooks, and that buffer may be reused for pre exec hooks, so we can't easily free the memory used to hold the pre RT validation hooks.

In theory, the only memory this operation makes unsafe are blocks newly allocated in between the freeze and the restore. Within the internal function _isValidSignature, we allocate an array for the signature validation hooks, and a call buffer for signature validation. Afaict neither of these are reused, nor make it out of the internal function _isValidSignature, so I think we should be fine to deallocate them and reuse that space for the UO validation hooks + UO call buffer..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, my concern is if Solc adds some memory allocations in between. I think it's fine if our tests pass, though.

mstore(add(buffer, 0x44), entityId)

// Copy in the signature segment
// Since the buffer's copy of the signature exceeds the length of any sub-segments, we can safely write
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually @adamegyed is it possible to fuck this up with some sneaky encoding from the user?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e. I encode a length longer than the entire sig for a single segment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thinking! This worried me at first, but I think we're ok.

This should be caught by the behavior of the calldata slices: https://docs.soliditylang.org/en/v0.8.26/types.html#array-slices

If start is greater than end or if end is greater than the length of the array, an exception is thrown.

Looking at our code:

We pass in to SparseCalldataSegmentLib a bounded bytes calldata type, which is either userOp.signature[25:], the version that skips past the deferred action data, bytes calldata authorization, or bytes calldata signature.

In SparseCalldataSegmentLib:
Loading the length:

if (nextIndex == index) {
(bytes calldata segment, bytes calldata remainder) = getNextSegment(source);

Getting the new slice:
segment = source[5:remainderOffset];

So if the reported length exceeds the actual length, the slice creation step should revert.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, good stuff! This means the segment could be incorrect in that it could overreach into other segments (fine, no danger afaik), but it cannot under any circumstance break the invariant of being less than the total signature length. Neato.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think if it "overreaches into the other segments", the other segments would fail to load, because the index and length is only read from after the length of the segment. So the definition of the format makes that kind of overlap impossible.


if iszero(success) {
// Bubble up the revert if the call reverts.
let m := mload(0x40)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reasoning behind calling the fmp "m?"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's just "memory", like "memory we can use". iirc this was a convention that solady was doing for a bit, but I can see how it's not great for readability. What about "mem" or "freeMem"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

either way works with me. Happy to keep it as is, it's informational.

if (!_storage.validationData[sigValidation].isSignatureValidation) {
(address module, uint32 entityId) = sigValidation.unpack();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small idea but can we just emit the module entity without unpacking, what are the implications? (I know we do this in other places too, just wondering if we could save a handful of codesize)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably, we should go back and review our error types to make sure the data included is correct. Like here, I agree we should keep it packed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way nbd, I don't think it's going to make much difference, but devX is nicer if we don't have to do extra decoding upon receiving an error. Fine either way.

Copy link
Contributor

@Zer0dot Zer0dot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comments, lgtm!

@adamegyed adamegyed force-pushed the adam/call-buffers-sig branch from 5826833 to 8c8d350 Compare October 14, 2024 21:32
@adamegyed adamegyed force-pushed the adam/deferred-action branch from c419355 to 92e2bbb Compare October 15, 2024 18:19
Base automatically changed from adam/deferred-action to develop October 15, 2024 18:28
@adamegyed adamegyed force-pushed the adam/call-buffers-sig branch from 8c8d350 to e441f28 Compare October 15, 2024 18:30
Copy link

Summary by Octane

New Contracts

No new contracts were added in this PR.

Updated Contracts

  • ModularAccountBase.sol: Removed dependencies on IValidationHookModule and IValidationModule, added signature validation optimization with SigCallBuffer, and improved memory management by freezing and restoring memory snapshots.
  • SemiModularAccountBase.sol: Added SigCallBuffer for enhanced signature validation functionality within _exec1271Validation.
  • ExecutionLib.sol: The smart contract was modified to include new pre-validation and signature handling features with the addition of SigCallBuffer.
  • MemManagementLib.sol: Added memory snapshot functions freezeFMP and restoreFMP for memory management.

🔗 Commit Hash: e441f28

Copy link

Contract sizes:

 | Contract                      | Size (B) | Margin (B) |
 |-------------------------------|----------|------------|
 | AccountFactory                |    4,814 |     19,762 |
 | AllowlistModule               |    9,230 |     15,346 |
 | ECDSAValidationModule         |    3,646 |     20,930 |
-| ModularAccount                |   25,737 |     -1,161 |
+| ModularAccount                |   25,574 |       -998 |
 | NativeTokenLimitModule        |    4,714 |     19,862 |
 | PaymasterGuardModule          |    1,797 |     22,779 |
-| SemiModularAccountBytecode    |   27,064 |     -2,488 |
-| SemiModularAccountStorageOnly |   27,561 |     -2,985 |
+| SemiModularAccountBytecode    |   27,562 |     -2,986 |
+| SemiModularAccountStorageOnly |   28,080 |     -3,504 |
 | TimeRangeModule               |    2,000 |     22,576 |
 | WebauthnValidationModule      |    7,854 |     16,722 |

Code coverage:

File % Lines % Statements % Branches % Funcs
src/account/AccountStorageInitializable.sol 89.47% (17/19) 96.15% (25/26) 80.00% (4/5) 100.00% (2/2)
src/account/BaseAccount.sol 87.50% (7/8) 85.71% (6/7) 50.00% (1/2) 100.00% (4/4)
src/account/ModularAccount.sol 100.00% (1/1) 100.00% (1/1) 100.00% (0/0) 100.00% (2/2)
src/account/ModularAccountBase.sol 95.39% (290/304) 93.51% (360/385) 67.80% (40/59) 100.00% (38/38)
src/account/ModularAccountView.sol 100.00% (26/26) 100.00% (33/33) 100.00% (2/2) 100.00% (2/2)
src/account/ModuleManagerInternals.sol 86.99% (107/123) 85.89% (140/163) 40.00% (6/15) 100.00% (12/12)
src/account/SemiModularAccountBase.sol 87.69% (57/65) 91.21% (83/91) 66.67% (10/15) 94.74% (18/19)
src/account/SemiModularAccountBytecode.sol 100.00% (5/5) 100.00% (6/6) 100.00% (1/1) 50.00% (1/2)
src/account/SemiModularAccountStorageOnly.sol 100.00% (4/4) 100.00% (5/5) 100.00% (0/0) 100.00% (2/2)
src/account/TokenReceiver.sol 33.33% (1/3) 33.33% (1/3) 100.00% (0/0) 33.33% (1/3)
src/factory/AccountFactory.sol 70.59% (24/34) 76.09% (35/46) 40.00% (2/5) 58.33% (7/12)
src/libraries/ExecutionLib.sol 98.39% (244/248) 97.18% (241/248) 88.00% (22/25) 100.00% (22/22)
src/libraries/KnownSelectorsLib.sol 100.00% (29/29) 100.00% (64/64) 100.00% (0/0) 100.00% (3/3)
src/libraries/LinkedListSetLib.sol 94.00% (47/50) 96.25% (77/80) 66.67% (4/6) 100.00% (8/8)
src/libraries/MemManagementLib.sol 100.00% (54/54) 100.00% (70/70) 100.00% (0/0) 100.00% (12/12)
src/libraries/SemiModularKnownSelectorsLib.sol 100.00% (6/6) 100.00% (12/12) 100.00% (0/0) 100.00% (1/1)
src/libraries/SparseCalldataSegmentLib.sol 100.00% (17/17) 100.00% (21/21) 100.00% (4/4) 100.00% (4/4)
src/modules/BaseModule.sol 100.00% (13/13) 94.12% (16/17) 100.00% (2/2) 100.00% (3/3)
src/modules/permissions/AllowlistModule.sol 86.05% (74/86) 85.71% (96/112) 78.26% (18/23) 50.00% (9/18)
src/modules/permissions/NativeTokenLimitModule.sol 87.80% (36/41) 89.66% (52/58) 88.89% (8/9) 54.55% (6/11)
src/modules/permissions/PaymasterGuardModule.sol 80.00% (8/10) 66.67% (10/15) 100.00% (2/2) 57.14% (4/7)
src/modules/permissions/TimeRangeModule.sol 83.33% (10/12) 80.00% (16/20) 100.00% (1/1) 75.00% (6/8)
src/modules/validation/ECDSAValidationModule.sol 92.00% (23/25) 81.58% (31/38) 62.50% (5/8) 90.00% (9/10)
src/modules/validation/WebauthnValidationModule.sol 61.11% (11/18) 66.67% (18/27) 100.00% (3/3) 60.00% (6/10)
Total 92.51% (1111/1201) 91.67% (1419/1548) 72.19% (135/187) 84.65% (182/215)

This comment was marked as resolved.

@adamegyed adamegyed merged commit 028240d into develop Oct 15, 2024
6 checks passed
@adamegyed adamegyed deleted the adam/call-buffers-sig branch October 15, 2024 18:57
howydev pushed a commit that referenced this pull request Oct 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants