Skip to content

feat(llm): Yearn timelock context — nested executions, allowlist scope, timelock ops - #414

Merged
spalen0 merged 4 commits into
mainfrom
feat/llm-yearn-timelock-context
Oct 5, 2026
Merged

spalen0 merged 4 commits into
mainfrom
feat/llm-yearn-timelock-context

Conversation

@spalen0

@spalen0 spalen0 commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Two YEARN_MS AI alerts had wrong or thin summaries:

  • Nonce 3356 (recovery vault / Flex): the protocol context said update_debt(Flex) would revert on an inactive strategy. Flex is registered in the same transaction, by an add_strategy nested inside the timelock executeBatch, which the adapters never saw. The report also left the WETH-2 Spark Looper unlabeled, and it never mentioned that call 2 releases an already-queued timelock operation (0x25864e54465a13f34e204660f5803e80ec9cd14c0d5b82bdbfffd2859b2b377d).
  • Nonce 3357 (TKSRelayerMulticall keeper): it said the keeper scope "is not shown". The verified source shows harvest/tend/report entry points plus forwardCall(address,bytes), an arbitrary call made as the relayer.

What

  • expand_executed_calls (protocol_context.py, wrappers.py): adapters and address labelling see the inner calls of executed execute/executeBatch/executeTransaction wrappers, recursively, after the wrapper. Scheduled-only wrappers are not expanded.
  • Executor label: the Safe that sends the tx is labeled in the reference table.
  • Yearn V3 adapter:
    • tracks the default queue through the batch;
    • set_default_queue shows the running queue → the new queue and flags removed strategies that still hold debt;
    • strategies that are vaults report auto_allocate and whether they route back into a strategy the vault already funds.
  • New permission_grant_context.py: for keeper/executor/allowlist setters it reads the verified source and reports:
    • which functions the entry unlocks (via modifiers, helpers or inline checks);
    • any arbitrary-call forwarders (an ARBITRARY CALL warning);
    • what the holder is (EOA, Safe, or a contract and its owner);
    • members before and after, when the set can be enumerated.
  • New timelock_execution_context.py: for execute/executeBatch it gives the operation ID, the timelock's min delay, and the status (not scheduled / done / not ready / ready since X, i.e. scheduled by X − delay). It also follows forwarding executors via TIMELOCK().
  • README updated (5f-3a, 5f-3b, 5g).

Testing

  • New tests:
    • test_permission_grant_context.py
    • test_timelock_execution_context.py: the operation ID matches the on-chain hash.
    • Nonce 3356 batch replay in test_yearn_v3_context.py.
    • TestExpandExecutedCalls and TestUnwrapExecutedCalls.
  • Ran both alert transactions end-to-end against mainnet without the LLM. The prompts now contain the facts listed above.
  • ruff format, ruff check and ty check pass.
  • 4 tests fail in tests/test_safe_main.py::TestSafeApiQuota. They fail without this change too: a local SAFE_API_KEY_3 leaks into those tests.

Follow-ups in this PR

Review fixes (permission_grant_context.py, timelock_execution_context.py):

  • A forwarder counts as an arbitrary call only when it sends its own bytes argument to its own address argument. If a require/if or a modifier argument checks the target or calldata, it is reported as a restricted forward instead, with those checks quoted.
  • Each check's polarity is read, so require(!blocked[msg.sender]) makes a denylist: "BLOCKS" / "Entry points it closes". Lists whose checks disagree or can't be read are skipped.
  • Within a batch, each call starts from the membership the previous call to the same allowlist left.
  • "Sat in the queue for the delay" is stated only for ready operations.

New yearn_strategy_context.py (from Safe nonce 3359, two Grove compounders + a Spark compounder): the report had called the earlier strategy settings "not provided". The new adapter:

  • reads each tokenized strategy's settings and renders each call in batch order as old → new;
  • explains setProfitMaxUnlockTime(0): the self-held share burn, the profit credited at once, the price-per-share change, and the deposit gate;
  • gives report() timing, using the unlock time set earlier in the batch;
  • shows custom report triggers (current → new, plus minReportDelay).

🤖 Generated with Claude Code

spalen0 and others added 4 commits October 5, 2026 16:41
…ope and timelock ops

- Feed the inner calls of executed timelock batches to protocol-context adapters
  and address labelling, so a nested add_strategy is no longer reported as an
  inactive-strategy revert.
- Label the executing Safe in the reference table.
- Yearn V3: track the default queue through the batch, flag removed strategies
  that still hold debt, and describe allocator vaults (auto_allocate, overlap).
- New permission-grant adapter: from verified source, list what an allowlist
  entry (keeper/executor) unlocks, flag arbitrary-call forwarders, describe the
  holder and show enumerable members before/after.
- New timelock-execution adapter: operation ID, min delay and readiness for
  execute/executeBatch, including through forwarding executors.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…batch membership, gate timelock queue claim

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Strategy setters live in the shared TokenizedStrategy implementation, so the
generic before-state reader missed them. The new adapter reads management,
keeper, emergency admin, performance fee and recipient, unlock time, last
report and the deposit gate, then renders each call in batch order as
old → new. It explains setProfitMaxUnlockTime(0) (the self-held share burn
and the profit credited at once), report() timing, and custom report
triggers with their minReportDelay.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…xt bits, trim tests

- utils/eth_view.call_view replaces three private eth_call helpers
  (strategy, timelock, control-transfer contexts).
- utils/formatting gains format_asset_amount and format_utc. The strategy and
  timelock contexts now use the existing format_duration instead of local copies.
- Yearn context: removed StrategyState.in_default_queue (never read), the
  strategy adapter's setAllowed path (the generic before-state reader already
  covers it) and the TriggerInfo dataclass.
- Tests: dropped unit tests duplicated by resolve-level tests, folded
  single-assert variants together, and removed context tests that only re-check
  the batch model's arithmetic (covered in test_yearn_v3_batch).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@spalen0
spalen0 marked this pull request as ready for review October 5, 2026 19:15
@spalen0
spalen0 merged commit 9de6fcb into main Oct 5, 2026
3 checks passed
@spalen0
spalen0 deleted the feat/llm-yearn-timelock-context branch October 5, 2026 19:17
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.

1 participant