ExecutionOptions::with_memory_budget accepts a budget that nothing
enforces. A caller can ask for a 1 KB ceiling on a large boolean and
the operation runs to completion, allocating far more, with no error
and no diagnostic.
Evidence
The machinery exists. ScratchRequirement::fits_budget is implemented
and unit-tested in crates/contracts/common/base/:
$ grep -rn "fits_budget" crates/ --include=*.rs
crates/contracts/common/base/tests/execution_contracts.rs:158
crates/contracts/common/base/tests/execution_contracts.rs:164
crates/contracts/common/base/tests/execution_contracts.rs:167
crates/contracts/common/base/tests/execution_contracts.rs:177
crates/contracts/common/base/tests/execution_contracts.rs:178
Every hit is a test. There is no production caller anywhere in
crates/, so no operation consults memory_budget_bytes().
Measured by the new benchmark scorecard:
1 KB memory budget rec ignored
The workload is a 20-unit bar minus 24 tools, which allocates on the
order of 85 KB for a far smaller union. It completes successfully under
a 1024-byte budget.
Why it matters
A silently inert option is worse than an absent one. A caller that sets
a budget reasonably believes it is protected, and an embedder using it
to bound memory on untrusted input gets no protection at all. The
failure is invisible: no error, no warning, no evidence field.
This is the same shape as the cancellation contract, which IS honoured
and IS declared through cancellation_granularity() -- the boolean
provider refuses a pre-cancelled batch correctly. Budget has the
declaration machinery but no enforcement.
Suggested resolution
Either enforce it or refuse it, not silence. Two defensible options:
- Call
fits_budget at dispatch, where the doc comment already says
budget checks belong ("once per dispatch, not on a hot path"), and
return a typed refusal when the requirement exceeds the budget.
- If a provider cannot estimate its scratch requirement, declare that
explicitly the way cancellation granularity is declared, so callers
can tell "unbounded" from "bounded and checked".
Option 1 matches the existing design intent; the doc comment on
fits_budget reads as though dispatch was the intended caller.
Found by: axiolid-benchmarks --only=cancel
ExecutionOptions::with_memory_budgetaccepts a budget that nothingenforces. A caller can ask for a 1 KB ceiling on a large boolean and
the operation runs to completion, allocating far more, with no error
and no diagnostic.
Evidence
The machinery exists.
ScratchRequirement::fits_budgetis implementedand unit-tested in
crates/contracts/common/base/:Every hit is a test. There is no production caller anywhere in
crates/, so no operation consultsmemory_budget_bytes().Measured by the new benchmark scorecard:
The workload is a 20-unit bar minus 24 tools, which allocates on the
order of 85 KB for a far smaller union. It completes successfully under
a 1024-byte budget.
Why it matters
A silently inert option is worse than an absent one. A caller that sets
a budget reasonably believes it is protected, and an embedder using it
to bound memory on untrusted input gets no protection at all. The
failure is invisible: no error, no warning, no evidence field.
This is the same shape as the cancellation contract, which IS honoured
and IS declared through
cancellation_granularity()-- the booleanprovider refuses a pre-cancelled batch correctly. Budget has the
declaration machinery but no enforcement.
Suggested resolution
Either enforce it or refuse it, not silence. Two defensible options:
fits_budgetat dispatch, where the doc comment already saysbudget checks belong ("once per dispatch, not on a hot path"), and
return a typed refusal when the requirement exceeds the budget.
explicitly the way cancellation granularity is declared, so callers
can tell "unbounded" from "bounded and checked".
Option 1 matches the existing design intent; the doc comment on
fits_budgetreads as though dispatch was the intended caller.Found by:
axiolid-benchmarks --only=cancel