Skip to main content

Gas & Performance

How gas limits work

Every ModuleConfigInput includes gasLimit. During execution the aggregator wraps every call with that limit:

(bool ok, bytes memory ret) = config.module.call{gas: config.gasLimit}(payload);
  • gasLimit = 0 means "use whatever gas remains".
  • If a module exhausts its allowance and critical = true, the entire hook reverts.
  • If critical = false, the aggregator emits ModuleExecutionSkipped and continues to the next module.

Patterns

Module typeSuggested gas limitCritical?Notes
Stateless policies20k–40kUsuallyMapping lookups, cheap branching.
Logging / analytics15k–30kNoNon-critical data capture.
Fee controllers40k–80kYesOften decode hook data, adjust LP fees.
Limit orders / MEV120k+MixedDependent on tick scans and ERC1155 ops.

Keep _modules.length small (≤5) to minimize per-hook iteration overhead.

Measuring modules

Use Foundry's gas report to benchmark:

forge test --match-contract DynamicFeeModuleTest --gas-report

This surfaces the cost of each hook function so you can set realistic limits. For heavy modules, split "read and check" logic (before hooks) from "write and settle" logic (after hooks) to better control variance.

Example: best-effort analytics

configs.push(MODLAggregator.ModuleConfigInput({
module: analyticsModule,
hooks: hooks,
critical: false,
priority: 50,
gasLimit: 25_000
}));

If the module reverts, swaps still complete and the protocol emits ModuleExecutionSkipped with the revert reason. This pattern lets you experiment without endangering core functionality.

Example: defending against griefing

Set a tight limit for untrusted modules:

configs[i].gasLimit = 60_000;
configs[i].critical = true;

If a third-party module surges in cost (e.g., due to expensive storage writes), it simply reverts without draining the aggregator's remaining gas or bricking the pool.