Light Mode

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: remove intent struct#365

Merged
howydev merged 19 commits intov1.0.0from
howy/optimize-intent-struct
Sep 26, 2025
Merged

feat: remove intent struct#365
howydev merged 19 commits intov1.0.0from
howy/optimize-intent-struct

Conversation

Copy link
Contributor

howydev commented Sep 14, 2025 *
edited
Loading

Instead of using an intent struct, we use a efficient custom encoding instead. This reduces gas by ~12k for single intents and ~10k for intents in a batch

Gas savings come from:

  1. Smaller intent size (by ~5-10 words). This results in less memory usage, less memory expansion, and less intrinsic calldata cost paid
  2. Removing custom offset checks LibBytes.checkInCalldata (~2-3k gas alone)
  3. Not requiring just-in-time checks on struct offsets when accessing any struct field (solc adds this under the hood)
  4. Optimizing the args for _pay to only take in what we currently use today instead of the full intent
  5. Removing fields from the EIP-712 digest - 2 less keccak operations

The new format is abi.encodePacked, but with some nuances:

  1. All static fields go before all dynamic fields, so all static fields have known offsets
  2. The static fields that go into the EIP712 digest are ordered continuously
  3. The static fields that are in the EIP712 digest are padded to 32 bytes, then with the previous property this allows us to do a single calldatacopy for digest calculations
    (The relayer has the freedom to use dirty bytes on these fields, but then the digest would compute to something else and this would fail verification, so there's no incentive to do so)
  4. Dynamic fields are prefixed with uint256 field.length to allow us to use them as a bytes calldata directly
  5. We order the dynamic fields in the order that they will be used in the orchestrator flow

We rely on the helper function _getNextBytes(ptr) for efficient traversal through the dynamic fields. The helper function assumes that the pointer already points to the start of a dynamic byte range. It parses that out, then updates the CalldataPointer in memory to point to the start of the next dynamic byte range

howydev changed the base branch from main to howy/remove-multichain-bool September 14, 2025 02:19
howydev force-pushed the howy/optimize-intent-struct branch from b891f19 to 6945da1 Compare September 14, 2025 02:20
Copy link
Contributor

github-actions bot commented Sep 14, 2025

Bytecode changes detected! EIP-712 domain versions have been automatically updated for: IthacaAccount,Orchestrator,SimpleFunder,Simulator

howydev force-pushed the howy/optimize-intent-struct branch from bbb01eb to 5a155c3 Compare September 15, 2025 23:35
Copy link
Contributor

github-actions bot commented Sep 15, 2025

Bytecode changes detected! EIP-712 domain versions have been automatically updated for: IthacaAccount,Orchestrator,SimpleFunder,Simulator

howydev force-pushed the howy/optimize-intent-struct branch from a3e28e6 to db35ed5 Compare September 15, 2025 23:52
Copy link
Contributor

github-actions bot commented Sep 15, 2025

Bytecode changes detected! EIP-712 domain versions have been automatically updated for: IthacaAccount,Orchestrator,SimpleFunder,Simulator

howydev force-pushed the howy/optimize-intent-struct branch from a69256f to bde24a4 Compare September 16, 2025 00:42
Copy link
Contributor

github-actions bot commented Sep 16, 2025

Bytecode changes detected! EIP-712 domain versions have been automatically updated for: IthacaAccount,Orchestrator,SimpleFunder,Simulator

howydev commented Sep 16, 2025
function simulateExecute(bytes calldata encodedIntent) external payable returns (uint256) {
bool isStateOverride;
uint256 combinedGasOverride;
assembly ("memory-safe") {
Copy link
Contributor Author

howydev Sep 16, 2025

Choose a reason for hiding this comment

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

note to reviewer - we need to do this b/c adding another arg changes the calldata layout, making encodedIntent have an offset that is not 0x40

Copy link
Collaborator

legion2002 Sep 16, 2025

Choose a reason for hiding this comment

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

Think this can be solved by just seeding all functions of the IntentHelpers lib with the starting offset of the encodedIntent?

howydev reacted with thumbs up emoji
Copy link
Contributor Author

howydev Sep 16, 2025 *
edited
Loading

Choose a reason for hiding this comment

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

Yup, we'd have to bubble that into the internal function. I think this is the best approach for us to to support batch executions without a separate call frame though, great idea

howydev force-pushed the howy/optimize-intent-struct branch from b215dee to 135ef20 Compare September 16, 2025 00:51
howydev commented Sep 16, 2025
i.paymentRecipient
);

{
Copy link
Contributor Author

howydev Sep 16, 2025

Choose a reason for hiding this comment

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

a little ugly, but we're running into stack too deep if we don't do this

howydev commented Sep 16, 2025
howydev commented Sep 16, 2025
howydev commented Sep 16, 2025
u.signature = _sig(alice, u);

assertEq(oc.execute(abi.encode(u)), bytes4(keccak256("PaymentError()")));
assertEq(oc.execute(encodeIntent(u)), bytes4(keccak256("Unauthorized()")));
Copy link
Contributor Author

howydev Sep 16, 2025

Choose a reason for hiding this comment

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

found it funny that a testUnauthorized was erroring with PaymentError

howydev commented Sep 16, 2025
howydev marked this pull request as ready for review September 16, 2025 01:11
howydev changed the title [DRAFT] feat: optimize intent struct feat: remove intent struct Sep 16, 2025
ithacaxyz deleted a comment from github-actions bot Sep 16, 2025
ithacaxyz deleted a comment from github-actions bot Sep 16, 2025
howydev commented Sep 16, 2025
howydev commented Sep 16, 2025
howydev commented Sep 16, 2025
Copy link
Contributor

github-actions bot commented Sep 16, 2025

Bytecode changes detected! EIP-712 domain versions have been automatically updated for: IthacaAccount,Simulator

Copy link
Contributor

github-actions bot commented Sep 16, 2025

Bytecode changes detected! EIP-712 domain versions have been automatically updated for: IthacaAccount,SimpleFunder,Simulator

Copy link
Contributor

github-actions bot commented Sep 16, 2025

Bytecode changes detected! EIP-712 domain versions have been automatically updated for: IthacaAccount,Orchestrator,SimpleFunder,Simulator

legion2002 added this to the v1.0.0 milestone Sep 16, 2025
legion2002 assigned howydev Sep 16, 2025
legion2002 reviewed Sep 16, 2025
// This generates an unnecessary check for `i < encodedIntents.length`, but helps
// generate all the implicit calldata bound checks on `encodedIntents[i]`.
(, errs[i]) = _execute(encodedIntents[i], 0, _NORMAL_MODE_FLAG);
errs[i] = this.execute(encodedIntents[i]);
Copy link
Collaborator

legion2002 Sep 16, 2025

Choose a reason for hiding this comment

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

Why add self calls to batching?
Can't we update the IntentHelpers lib, to start at a particular calldata offset instead?

Copy link
Contributor Author

howydev Sep 16, 2025

Choose a reason for hiding this comment

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

yup, its possible, this approach is less efficient

right now its 10k cheaper in a batch and 12k in a single intent, i think that 2k difference is coming from this

let's include that in a separate PR, it might be a medium-sized effort if we're going to go through with some of the other planned changes

function simulateExecute(bytes calldata encodedIntent) external payable returns (uint256) {
bool isStateOverride;
uint256 combinedGasOverride;
assembly ("memory-safe") {
Copy link
Collaborator

legion2002 Sep 16, 2025

Choose a reason for hiding this comment

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

Think this can be solved by just seeding all functions of the IntentHelpers lib with the starting offset of the encodedIntent?

howydev reacted with thumbs up emoji
howydev commented Sep 16, 2025
howydev force-pushed the howy/remove-multichain-bool branch from 78b2398 to bac1c67 Compare September 26, 2025 15:58
howydev force-pushed the howy/optimize-intent-struct branch from 2684f1d to b5432dc Compare September 26, 2025 16:10
howydev changed the base branch from howy/remove-multichain-bool to v1.0.0 September 26, 2025 16:14
howydev merged commit 4cde57e into v1.0.0 Sep 26, 2025
howydev deleted the howy/optimize-intent-struct branch September 26, 2025 16:14
Copy link
Contributor Author

howydev commented Sep 26, 2025

targets: #337

legion2002 pushed a commit that referenced this pull request Sep 29, 2025
* feat: simplify multichain nonce design

* chore: readd merkle verification prefix

* chore: undo blank line addns

* chore: lint

* .

* ~50 failing tests down to 5

* down to 1 failing test

* fixed failing test

* chore: remove console logs and bench

* .

* Update test/Base.t.sol

* Update src/Orchestrator.sol

* Update test/utils/mocks/MockPayerWithSignatureOptimized.sol

* chore: final cleanup, rebench

* Update src/Orchestrator.sol

* chore: bump contract versions due to bytecode changes - Contracts updated: IthacaAccount,Orchestrator,SimpleFunder,Simulator

* chore: cleanup

* rebase

* fix

---------

Co-authored-by: GitHub Action
howydev added a commit that referenced this pull request Oct 9, 2025
* feat: simplify multichain nonce design

* chore: readd merkle verification prefix

* chore: undo blank line addns

* chore: lint

* .

* ~50 failing tests down to 5

* down to 1 failing test

* fixed failing test

* chore: remove console logs and bench

* .

* Update test/Base.t.sol

* Update src/Orchestrator.sol

* Update test/utils/mocks/MockPayerWithSignatureOptimized.sol

* chore: final cleanup, rebench

* Update src/Orchestrator.sol

* chore: bump contract versions due to bytecode changes - Contracts updated: IthacaAccount,Orchestrator,SimpleFunder,Simulator

* chore: cleanup

* rebase

* fix

---------

Co-authored-by: GitHub Action
howydev added a commit that referenced this pull request Oct 9, 2025
* feat: remove intent struct (#365)

* feat: simplify multichain nonce design

* chore: readd merkle verification prefix

* chore: undo blank line addns

* chore: lint

* .

* ~50 failing tests down to 5

* down to 1 failing test

* fixed failing test

* chore: remove console logs and bench

* .

* Update test/Base.t.sol

* Update src/Orchestrator.sol

* Update test/utils/mocks/MockPayerWithSignatureOptimized.sol

* chore: final cleanup, rebench

* Update src/Orchestrator.sol

* chore: bump contract versions due to bytecode changes - Contracts updated: IthacaAccount,Orchestrator,SimpleFunder,Simulator

* chore: cleanup

* rebase

* fix

---------

Co-authored-by: GitHub Action

* chore: update benchmarks

* chore: update to correct app sponsor cost

* chore: rename benchmarks for clarity

* chore: readme

* cache

* revert

* .

* chore: lint

* chore: lint, use diff tokens for erc20 transfers

* .

* chore: add native transfer and uni v2 swap benchmarks, standardize to use different recipients/tokens for benchmarks

* chore: lint

* feat: add 7702 account benchmarks

* chore: update readme

---------

Co-authored-by: GitHub Action
howydev added a commit that referenced this pull request Oct 9, 2025
* feat: simplify multichain nonce design

* chore: readd merkle verification prefix

* chore: undo blank line addns

* chore: lint

* .

* ~50 failing tests down to 5

* down to 1 failing test

* fixed failing test

* chore: remove console logs and bench

* .

* Update test/Base.t.sol

* Update src/Orchestrator.sol

* Update test/utils/mocks/MockPayerWithSignatureOptimized.sol

* chore: final cleanup, rebench

* Update src/Orchestrator.sol

* chore: bump contract versions due to bytecode changes - Contracts updated: IthacaAccount,Orchestrator,SimpleFunder,Simulator

* chore: cleanup

* rebase

* fix

---------

Co-authored-by: GitHub Action
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

legion2002 legion2002 left review comments

Assignees

howydev

Labels

None yet

Projects

None yet

Milestone

v1.0.0

Development

Successfully merging this pull request may close these issues.

3 participants