From 7f94afaaca5339909b6745d6b192e79651c4f2b1 Mon Sep 17 00:00:00 2001 From: spalen0 Date: Tue, 27 Oct 2026 15:12:51 +0100 Subject: [PATCH] fix(llm): drop Protocol Context section from gist report The protocol context is still passed to the LLM prompt and its addresses still feed the Reference table; only the standalone gist section is removed. Prompt lines that relied on the report publishing that section are updated. With nothing rendering it, the per-adapter report formatters, the ResolvedProtocolContext.report field, the resolver's labels parameter and their tests are removed. Co-Authored-By: Claude Opus 5.5 --- tests/test_control_transfer_context.py | 8 --- tests/test_infinifi_context.py | 24 -------- tests/test_infinifi_farm_context.py | 7 --- tests/test_infinifi_oracle_context.py | 9 +-- tests/test_infinifi_outland_context.py | 6 -- tests/test_llm_report.py | 8 --- tests/test_pendle_context.py | 2 - tests/test_permission_grant_context.py | 13 ----- tests/test_protocol_context.py | 23 +------- tests/test_threejane_account_context.py | 29 ---------- tests/test_threejane_context.py | 27 --------- tests/test_timelock_execution_context.py | 6 -- tests/test_yearn_auction_context.py | 5 -- tests/test_yearn_strategy_context.py | 10 +--- tests/test_yearn_v3_context.py | 10 ---- utils/llm/README.md | 8 +-- utils/llm/ai_explainer.py | 13 ++--- utils/llm/control_transfer_context.py | 52 ----------------- utils/llm/infinifi_context.py | 37 +----------- utils/llm/infinifi_farm_context.py | 30 ---------- utils/llm/infinifi_oracle_context.py | 23 -------- utils/llm/infinifi_outland_context.py | 42 -------------- utils/llm/pendle_context.py | 21 ++----- utils/llm/permission_grant_context.py | 68 +--------------------- utils/llm/protocol_context.py | 48 +++------------- utils/llm/report.py | 6 -- utils/llm/threejane_account_context.py | 61 +------------------- utils/llm/threejane_context.py | 73 ------------------------ utils/llm/timelock_execution_context.py | 27 --------- utils/llm/yearn_auction_context.py | 21 ------- utils/llm/yearn_strategy_context.py | 22 ------- utils/llm/yearn_v3_context.py | 35 ------------ 32 files changed, 34 insertions(+), 740 deletions(-) diff --git a/tests/test_control_transfer_context.py b/tests/test_control_transfer_context.py index 0c4c28f6..c42fbe96 100644 --- a/tests/test_control_transfer_context.py +++ b/tests/test_control_transfer_context.py @@ -13,7 +13,6 @@ ControllerInfo, ControlTransferContext, format_control_transfer_prompt, - format_control_transfer_report, resolve_control_transfer_context, ) @@ -271,13 +270,6 @@ def test_prompt(self) -> None: self.assertIn("Two-step", prompt) self.assertIn("a LOWER signing threshold. Effective once accepted.", prompt) - def test_report_links_every_controller(self) -> None: - report = format_control_transfer_report([self.CONTEXT], 1, {}) - for address in (TARGET, YCHAD, EXECUTOR, EXEC_SAFE): - self.assertIn(f"https://etherscan.io/address/{address}", report) - self.assertIn("**2-of-4**", report) - self.assertIn("- **Two-step:** this call only nominates", report) - def test_labels_and_addresses_include_the_hop(self) -> None: self.assertEqual(self.CONTEXT.labels, {YCHAD: "Safe 6-of-9", EXEC_SAFE: "Safe 2-of-4"}) self.assertEqual(self.CONTEXT.addresses, [TARGET, YCHAD, EXECUTOR, EXEC_SAFE]) diff --git a/tests/test_infinifi_context.py b/tests/test_infinifi_context.py index bbcb5e40..69adf4f8 100644 --- a/tests/test_infinifi_context.py +++ b/tests/test_infinifi_context.py @@ -25,7 +25,6 @@ _resolve_whitelist, _TokenCandidate, format_infinifi_prompt, - format_infinifi_report, resolve_infinifi_context, ) @@ -185,15 +184,6 @@ def test_prompt_names_farm_and_drop_token(self) -> None: self.assertIn("3,003,294.554623 USDC", result) self.assertIn("Whitelisted ERC20 call target", result) - def test_report_links_all_context_addresses(self) -> None: - context = _resolved_context() - report = format_infinifi_report([context], 1, context.labels) - self.assertIn("**Farm:** New Silver 2 Senior", report) - self.assertIn(f"https://etherscan.io/address/{FARM}", report) - self.assertIn(f"https://etherscan.io/address/{DROP}", report) - self.assertIn("New Silver Series 2 DROP", report) - self.assertIn("Whitelisted call targets", report) - class TestReadToken(unittest.TestCase): @patch.object(infinifi_context.ChainManager, "get_client") @@ -262,20 +252,6 @@ def test_fetch_farm_records_empty_on_bad_response(self, _mock_fetch: MagicMock) class TestFormattingEdgeCases(unittest.TestCase): - def test_report_omits_configured_tokens_section_when_empty(self) -> None: - context = InfinifiEscrowContext( - escrow_address=ESCROW, - farm_address=FARM, - farm_name="New Silver 2 Senior", - farm_slug="new-silver-senior", - accounting_asset=TokenContext(USDC, "USD Coin", "USDC", 6), - total_assets_raw=3_003_294_554_623, - configured_tokens=(), - ) - report = format_infinifi_report([context], 1, context.labels) - self.assertIn("**Farm:** New Silver 2 Senior", report) - self.assertNotIn("Configured non-accounting ERC-20 targets", report) - @patch.object(infinifi_context, "_fetch_farm_records") @patch.object(infinifi_context, "_read_token", return_value=None) @patch.object(infinifi_context, "_read_escrow_state", return_value=_EscrowState(ESCROW, FARM, USDC, 1)) diff --git a/tests/test_infinifi_farm_context.py b/tests/test_infinifi_farm_context.py index c040326e..2d6ebe01 100644 --- a/tests/test_infinifi_farm_context.py +++ b/tests/test_infinifi_farm_context.py @@ -13,7 +13,6 @@ PendleDiscountContext, SwapPairContext, format_infinifi_farm_prompt, - format_infinifi_farm_report, resolve_infinifi_farm_context, ) @@ -205,11 +204,6 @@ def test_already_supported_asset_reverts(self) -> None: (enable,) = _resolve([(SWAP_FARM, ENABLE_REUSD)], reusd_supported=True, reusd_oracle=ORACLE) self.assertIn("is already supported, so the call reverts (InvalidAsset)", format_infinifi_farm_prompt([enable])) - def test_report_flags_the_missing_oracle(self) -> None: - enable, _pair = _resolve(BATCH) - report = format_infinifi_farm_report([enable], 1, {}) - self.assertIn("**no Accounting oracle — reverts until one is set**", report) - class TestPendleDiscount(unittest.TestCase): def test_no_pts_held_means_no_immediate_change(self) -> None: @@ -227,7 +221,6 @@ def test_held_pts_change_reported_assets(self) -> None: prompt = format_infinifi_farm_prompt([context]) self.assertIn("reported assets change immediately by 1,500 USDC", prompt) self.assertIn("the farm is registered in FarmRegistry", prompt) - self.assertIn("assets() changes by `1,500 USDC`", format_infinifi_farm_report([context], 1, {})) def test_add_farms_earlier_in_the_batch_registers_it(self) -> None: (context,) = _resolve([(REGISTRY, ADD_FARM), (PENDLE_FARM, DISCOUNT)]) diff --git a/tests/test_infinifi_oracle_context.py b/tests/test_infinifi_oracle_context.py index ca91d490..625a8f7b 100644 --- a/tests/test_infinifi_oracle_context.py +++ b/tests/test_infinifi_oracle_context.py @@ -12,7 +12,6 @@ FeedDetails, OracleAssignmentContext, format_infinifi_oracle_prompt, - format_infinifi_oracle_report, resolve_infinifi_oracle_context, ) @@ -87,12 +86,6 @@ def test_feed_identity_and_freshness_rule_are_stated(self) -> None: self.assertIn("price() reverts (StalePrice) once the feed is older than the heartbeat of 48h", prompt) self.assertIn("The wrapper's name refers to the AggregatorV3 interface it reads", prompt) - def test_report_lists_the_feed(self) -> None: - (context,) = _resolve() - report = format_infinifi_oracle_report([context], 1, {}) - self.assertIn("= `1.1053024341839888` reference units", report) - self.assertIn('"reUSD NAV / USD"; heartbeat `48h`, updated 12.4h before this alert', report) - def test_oracle_without_a_feed_still_renders_its_price(self) -> None: (context,) = _resolve(with_feed=False) self.assertIsNone(context.feed) @@ -101,7 +94,7 @@ def test_oracle_without_a_feed_still_renders_its_price(self) -> None: def test_usdc_scale_reads_as_parity(self) -> None: # IOracle convention: a 6-decimal stable at parity is quoted at 1e30. context = OracleAssignmentContext(ACCOUNTING, USDC, "USDC", 6, ORACLE, 10**30) - self.assertIn("= `1` reference units", format_infinifi_oracle_report([context], 1, {})) + self.assertEqual(context.unit_price_text, "1") def test_removing_an_oracle_is_not_priced(self) -> None: call = _call("setOracle", ("address", REUSD), ("address", ZERO_ADDRESS)) diff --git a/tests/test_infinifi_outland_context.py b/tests/test_infinifi_outland_context.py index d0cc00cd..ef80a74b 100644 --- a/tests/test_infinifi_outland_context.py +++ b/tests/test_infinifi_outland_context.py @@ -13,7 +13,6 @@ OutlandMessageContext, RouteConfig, format_outland_prompt, - format_outland_report, resolve_outland_context, ) @@ -138,7 +137,6 @@ def test_unconfigured_route_is_not_live(self) -> None: prompt = format_outland_prompt(contexts) self.assertIn("NOT configured", prompt) self.assertIn("route is not live after this batch", prompt) - self.assertIn("**not configured**", format_outland_report(contexts, 1, {})) def test_configured_route_names_the_peer(self) -> None: contexts = self._resolve([_call("setCctpDomain", ("uint256", 143), ("uint32", 15))], (PEER, 200_000, 99)) @@ -171,15 +169,11 @@ def test_reconfiguring_a_live_route_reports_the_new_peer_not_the_old(self) -> No self.assertIn(f"to peer {NEW_PEER}, gas limit 300,000", prompt) self.assertIn(f"Before the batch: peer {PEER}", prompt) self.assertNotIn("unchanged", prompt) - report = format_outland_report(contexts, 1, {}) - self.assertIn(f"set by this batch — peer [`{NEW_PEER}`]", report) - self.assertIn(f"(was peer [`{PEER}`]", report) self.assertEqual(contexts[0].addresses, [CONNECTOR, PEER, NEW_PEER]) def test_clearing_a_live_route_is_flagged(self) -> None: contexts = self._resolve([self._set_configuration(ZERO_ADDRESS, 0, 0)], (PEER, 200_000, 99)) self.assertIn("this batch CLEARS the route", format_outland_prompt(contexts)) - self.assertIn("**cleared** by this batch", format_outland_report(contexts, 1, {})) def test_last_configuration_for_a_chain_wins(self) -> None: calls = [self._set_configuration(PEER, 99, 200_000), self._set_configuration(NEW_PEER, 99, 300_000)] diff --git a/tests/test_llm_report.py b/tests/test_llm_report.py index b1f09f0a..0d864363 100644 --- a/tests/test_llm_report.py +++ b/tests/test_llm_report.py @@ -452,14 +452,6 @@ def test_sections_in_order(self) -> None: self.assertLess(report.index("## Analysis"), report.index("## Call Flow")) self.assertLess(report.index("## Call Flow"), report.index("## Reference")) - def test_protocol_context_is_deterministic_section_after_call_flow(self) -> None: - ctx = _add_farms_ctx(protocol_context="- **Farm:** New Silver 2 Senior", related_addresses=[DROP]) - report = build_report("Updates the rate.", "Long analysis.", ctx, "LOW") - self.assertIn("## Protocol Context\n\n- **Farm:** New Silver 2 Senior", report) - self.assertLess(report.index("## Analysis"), report.index("## Call Flow")) - self.assertLess(report.index("## Call Flow"), report.index("## Protocol Context")) - self.assertLess(report.index("## Protocol Context"), report.index("## Reference")) - def test_metadata_header(self) -> None: report = build_report("Summary.", "Analysis.", _add_farms_ctx(label_address=TIMELOCK), "HIGH") self.assertIn("- **Protocol:** INFINIFI", report) diff --git a/tests/test_pendle_context.py b/tests/test_pendle_context.py index 92fb54c9..923065b0 100644 --- a/tests/test_pendle_context.py +++ b/tests/test_pendle_context.py @@ -104,10 +104,8 @@ def test_upgrade_includes_scope_controls_and_route_semantics(boundaries: Boundar assert "SwapType.ODOS" not in after assert "assert(false)" in after assert "_transferOut(tokenOut, msg.sender, netOut)" in after - explorer = "etherscan.io" if chain_id == 1 else "arbiscan.io" for address in (PENDLE_SWAP, OLD, NEW, OWNER): assert address in resolved.addresses - assert f"https://{explorer}/address/{address}" in resolved.report assert resolved.labels[PENDLE_SWAP] == "PendleSwap" contract_call = boundaries[2].return_value.eth.contract.call_args assert contract_call.kwargs["address"] == PENDLE_SWAP diff --git a/tests/test_permission_grant_context.py b/tests/test_permission_grant_context.py index 49a9a3fd..eda45520 100644 --- a/tests/test_permission_grant_context.py +++ b/tests/test_permission_grant_context.py @@ -8,7 +8,6 @@ from utils.llm.control_transfer_context import ControllerInfo from utils.llm.permission_grant_context import ( format_permission_grant_prompt, - format_permission_grant_report, resolve_permission_grant_context, ) from utils.verified_contract import VerifiedContract @@ -254,9 +253,6 @@ def test_keeper_grant_lists_scope_and_holder_forwarders(self, mock_client: Magic self.assertIn("forwardCall(address,bytes) [onlyKeepers]", prompt) mock_client.return_value.get_contract.assert_not_called() # a mapping has no member getter - report = format_permission_grant_report([context], 1, {}) - self.assertIn("`forwardCall(address,bytes)` (modifier onlyKeepers) — **arbitrary call**", report) - def test_restricted_forward_is_not_reported_as_arbitrary(self, mock_client: MagicMock, *_mocks: MagicMock) -> None: call = DecodedCall("setKeeper", "setKeeper(address,bool)", [("address", OPS_EOA), ("bool", True)]) (context,) = resolve_permission_grant_context("YEARN_MS", 1, [(HARVESTER, call)]) @@ -265,9 +261,6 @@ def test_restricted_forward_is_not_reported_as_arbitrary(self, mock_client: Magi self.assertIn("RESTRICTED FORWARD: forward(address,bytes) checks", prompt) self.assertIn('`require(_target == strategy, "!strategy")`', prompt) self.assertIn("`require(bytes4(_data) == IStrategy.report.selector", prompt) - report = format_permission_grant_report([context], 1, {}) - self.assertNotIn("every permission", report) - self.assertIn("forwards calls, restricted by", report) def test_denylist_entry_blocks_instead_of_granting(self, mock_client: MagicMock, *_mocks: MagicMock) -> None: call = DecodedCall("setBlocked", "setBlocked(address,bool)", [("address", OPS_EOA), ("bool", True)]) @@ -275,9 +268,6 @@ def test_denylist_entry_blocks_instead_of_granting(self, mock_client: MagicMock, self.assertIn(f"BLOCKS {OPS_EOA}", context.lines()[0]) self.assertIn("gates withdraw(uint256)", context.lines()[1]) self.assertIn("a denylist: listed callers are refused", context.lines()[1]) - report = format_permission_grant_report([context], 1, {}) - self.assertIn("**Entry points it closes:**", report) - self.assertNotIn("opens", report) def test_executor_removals_carry_membership_through_the_batch( self, mock_client: MagicMock, *_mocks: MagicMock @@ -319,9 +309,6 @@ def test_a_batch_states_scope_and_members_once(self, mock_client: MagicMock, *_m self.assertIn(f" 10. removeExecutor(address) REVOKES {executors[-1]}.", prompt) # Each change adds a line, not another copy of the scope and membership: linear, not quadratic. self.assertLess(len(prompt), 2_800) - report = format_permission_grant_report(contexts, 1, {}) - self.assertEqual(report.count("**Entry points it closes:**"), 1) - self.assertEqual(report.count("**Members after:**"), 1) def test_ignores_non_setters_without_source_lookups(self, mock_client: MagicMock, *_mocks: MagicMock) -> None: call = DecodedCall("transfer", "transfer(address,uint256)", [("address", OPS_EOA), ("uint256", 1)]) diff --git a/tests/test_protocol_context.py b/tests/test_protocol_context.py index 2d323fc8..c3a571d7 100644 --- a/tests/test_protocol_context.py +++ b/tests/test_protocol_context.py @@ -27,7 +27,6 @@ def _adapter(name: str, contexts: list[_FakeContext]) -> _Adapter: name=name, resolve=lambda protocol, chain_id, calls: contexts, format_prompt=lambda ctxs: f"{name} prompt", - format_report=lambda ctxs, chain_id, labels: f"{name} report", ) @@ -43,7 +42,6 @@ def test_adapters_are_combined(self) -> None: resolved = resolve_protocol_context("3JANE", 1, [(TARGET, _call())]) self.assertEqual(resolved.prompt, "alpha prompt\n\nbeta prompt") - self.assertEqual(resolved.report, "alpha report\n\nbeta report") self.assertEqual(resolved.addresses, [TARGET, TOKEN]) self.assertEqual(resolved.labels, {TARGET: "Config", TOKEN: "Token"}) @@ -52,14 +50,13 @@ def test_empty_adapter_contributes_nothing(self) -> None: resolved = resolve_protocol_context("3JANE", 1, [(TARGET, _call())]) self.assertEqual(resolved.prompt, "") - self.assertEqual(resolved.report, "") self.assertEqual(resolved.addresses, []) def test_failing_adapter_does_not_block_the_others(self) -> None: def explode(protocol: str, chain_id: int, calls: list) -> list: raise RuntimeError("etherscan down") - broken = _Adapter("broken", explode, lambda c: "x", lambda contexts, chain_id, labels: "x") + broken = _Adapter("broken", explode, lambda c: "x") adapters = (broken, _adapter("beta", [_FakeContext([TOKEN], {})])) with patch.object(protocol_context, "_ADAPTERS", adapters): resolved = resolve_protocol_context("3JANE", 1, [(TARGET, _call())]) @@ -74,28 +71,14 @@ def explode(contexts: object) -> str: "broken", lambda p, c, t: [_FakeContext([TOKEN], {TOKEN: "Broken"})], explode, - lambda contexts, chain_id, labels: "x", ) adapters = (broken, _adapter("alpha", [_FakeContext([TARGET], {TARGET: "Timelock"})])) with patch.object(protocol_context, "_ADAPTERS", adapters): resolved = resolve_protocol_context("x", 1, [(TARGET, _call())]) - self.assertEqual((resolved.prompt, resolved.report), ("alpha prompt", "alpha report")) - # Nothing from the adapter that failed to render reaches the report. + self.assertEqual(resolved.prompt, "alpha prompt") + # Nothing from the adapter that failed to render is kept. self.assertEqual((resolved.addresses, resolved.labels), ([TARGET], {TARGET: "Timelock"})) - def test_existing_labels_reach_the_report_renderer(self) -> None: - seen: dict[str, str] = {} - - def capture(contexts: list, chain_id: int, labels: dict[str, str]) -> str: - seen.update(labels) - return "report" - - adapter = _Adapter("alpha", lambda p, c, t: [_FakeContext([TOKEN], {TOKEN: "Token"})], lambda c: "p", capture) - with patch.object(protocol_context, "_ADAPTERS", (adapter,)): - resolve_protocol_context("3JANE", 1, [(TARGET, _call())], {TARGET: "Timelock"}) - - self.assertEqual(seen, {TARGET: "Timelock", TOKEN: "Token"}) - def test_registered_adapters_cover_the_known_protocols(self) -> None: self.assertEqual( {adapter.name for adapter in protocol_context._ADAPTERS}, diff --git a/tests/test_threejane_account_context.py b/tests/test_threejane_account_context.py index f4641b40..eed440ef 100644 --- a/tests/test_threejane_account_context.py +++ b/tests/test_threejane_account_context.py @@ -15,7 +15,6 @@ SupplyCapExemptContext, TokenUnit, format_account_prompt, - format_account_report, resolve_account_contexts, ) @@ -176,12 +175,6 @@ def test_prompt_states_units_and_raw_value(self) -> None: self.assertIn("Commitment to remove: 499,999.999986 USDC (raw 499999999986)", prompt) self.assertIn("State is read at scheduling time", prompt) - def test_report_links_vault_user_and_assets(self) -> None: - report = format_account_report(_bounce(), 1, {VAULT: "LCCVault"}) - for address in (VAULT, USER, USDC.address, WAEUSDC.address): - self.assertIn(f"https://etherscan.io/address/{address}", report) - self.assertIn("`499,999.999986 USDC`", report) - def test_phase_blockers_come_first(self) -> None: """bounceCommitment reverts InvalidPhase during a pending auction or an unsettled call.""" self.assertIn("shortfall auction is pending", _bounce(auction_pending=True).blocker()) @@ -289,7 +282,6 @@ def test_purpose_line_states_the_documented_intent(self) -> None: self.assertIn("per-wallet right to grow USD3 past the cap", line) self.assertIn("adds no credit exposure", line) self.assertEqual(format_account_prompt(_exemption()).count("Why it matters"), 1) - self.assertEqual(format_account_report(_exemption(), 1, {}).count("Why it matters"), 1) def test_supply_line_above_cap_blocks_non_exempt(self) -> None: line = _exemption().supply_line() @@ -317,23 +309,6 @@ def test_prompt_states_shared_facts_once_and_lists_every_account(self) -> None: self.assertIn(f"- {SAFE} (contract): true → false; ringFenceConduit false; holds 85,185.325821 USD3", prompt) self.assertEqual(sum(1 for line in prompt.splitlines() if line.startswith("- 0x")), 4) - def test_report_tabulates_accounts_with_links(self) -> None: - report = format_account_report(self._revocation(), 1, {USD3_ADDRESS: "USD3"}) - self.assertEqual(report.count("What the flag does"), 1) - self.assertIn("| # | Account | Type | supplyCapExempt | ringFenceConduit | USD3 balance |", report) - self.assertIn(f"EOA, EIP-7702 → [`{DELEGATE}`](https://etherscan.io/address/{DELEGATE})", report) - for address in (OTHER, DELEGATED, SAFE, USER): - self.assertIn(f"https://etherscan.io/address/{address}", report) - self.assertIn("`1,271,316.91341 USD3`", report) - - def test_report_flags_out_of_step_accounts(self) -> None: - conduit_revoke = _account(proposed_exempt=False, current_exempt=True, ring_fence_conduit=True) - self.assertIn( - "⚠️ exemption revoked while ringFenceConduit stays true", - format_account_report(_exemption(conduit_revoke), 1, {}), - ) - self.assertNotIn("⚠️", format_account_report(_exemption(), 1, {})) - def test_history_appears_only_when_read(self) -> None: account = _account(address=REGRANTED, last_change=ExemptionChange(26091398, False, REVOKE_TX)) context = _exemption(account, history_read=True, exempt_before=5, exempt_conduits_before=2) @@ -342,13 +317,9 @@ def test_history_appears_only_when_read(self) -> None: "Exempt before this transaction (replayed from SupplyCapExemptUpdated): 5 account(s), 2 of them", prompt ) self.assertIn(f"re-grants an exemption revoked at block 26091398 (tx {REVOKE_TX})", prompt) - report = format_account_report(context, 1, {}) - self.assertIn("| USD3 balance | Last change |", report) - self.assertIn(f"set false at [block 26091398](https://etherscan.io/tx/{REVOKE_TX})", report) unread = _exemption(account) self.assertNotIn("re-grants", format_account_prompt(unread)) - self.assertNotIn("Last change", format_account_report(unread, 1, {})) def test_addresses_include_every_account(self) -> None: self.assertEqual(self._revocation().addresses, [USD3_ADDRESS, OTHER, DELEGATED, SAFE, USER]) diff --git a/tests/test_threejane_context.py b/tests/test_threejane_context.py index 64d53d79..1c7a1ff3 100644 --- a/tests/test_threejane_context.py +++ b/tests/test_threejane_context.py @@ -16,7 +16,6 @@ _proposed_config_values, _requested_epochs, format_threejane_prompt, - format_threejane_report, resolve_threejane_context, ) @@ -197,17 +196,8 @@ def test_role_hash_has_no_value_line(self) -> None: note="minter role: can mint new JANE", ) prompt = format_threejane_prompt([context]) - report = format_threejane_report([context], 1, {}) self.assertIn('keccak256("MINTER_ROLE")', prompt) self.assertNotIn("value stored on-chain", prompt) - self.assertNotIn("Value stored on-chain", report) - - def test_report_links_the_target(self) -> None: - context = HashedLabelContext(PROTOCOL_CONFIG, "0xabc", "DEBT_CAP", "ceiling", True, 63_366_281_225_814) - report = format_threejane_report([context], 1, {PROTOCOL_CONFIG: "3Jane ProtocolConfig"}) - self.assertIn(f"https://etherscan.io/address/{PROTOCOL_CONFIG}", report) - self.assertIn("3Jane ProtocolConfig", report) - self.assertIn("`63,366,281,225,814`", report) def test_capped_quantity_without_a_unit_stays_raw(self) -> None: context = HashedLabelContext( @@ -221,10 +211,8 @@ def test_capped_quantity_without_a_unit_stays_raw(self) -> None: current_usage=75_045_566_960_234, ) prompt = format_threejane_prompt([context]) - report = format_threejane_report([context], 1, {}) self.assertIn("USD3 totalAssets right now: 75045566960234", prompt) self.assertIn("directly comparable", prompt) - self.assertIn("`75,045,566,960,234`", report) def test_supply_cap_raise_renders_in_the_asset_with_headroom(self) -> None: """The alert that read "80,000,000,000,000 asset units" now reads in USDC.""" @@ -243,7 +231,6 @@ def test_supply_cap_raise_renders_in_the_asset_with_headroom(self) -> None: usage_enforcement=usage_read.enforcement, ) prompt = format_threejane_prompt([context]) - report = format_threejane_report([context], 1, {}) self.assertIn("value stored on-chain right now: 80,000,000 USDC (raw 80000000000000)", prompt) self.assertIn("this transaction sets it to: 100,000,000 USDC (raw 100000000000000)", prompt) self.assertIn("USD3 totalAssets right now: 80,354,041.482181 USDC", prompt) @@ -251,8 +238,6 @@ def test_supply_cap_raise_renders_in_the_asset_with_headroom(self) -> None: self.assertIn("Against the proposed cap: 19,645,958.517819 USDC of headroom", prompt) self.assertIn("compares this cap directly against USD3 totalAssets", prompt) self.assertNotIn("directly comparable", prompt) - self.assertIn("Set by this transaction to: `100,000,000 USDC`", report) - self.assertIn("Against the proposed cap: 19,645,958.517819 USDC of headroom", report) def test_proposed_config_values_are_keyed_by_hash(self) -> None: calls = [_set_config_call("USD3_SUPPLY_CAP", 5), _set_config_call("MAX_LTV", 7)] @@ -263,7 +248,6 @@ def test_proposed_config_values_are_keyed_by_hash(self) -> None: def test_usage_line_absent_for_keys_without_one(self) -> None: context = HashedLabelContext(PROTOCOL_CONFIG, "0xabc", "MAX_LTV", "ltv", True, 350000000000000000) self.assertNotIn("directly comparable", format_threejane_prompt([context])) - self.assertNotIn("totalAssets", format_threejane_report([context], 1, {})) def test_usage_reads_are_registered_for_known_keys_only(self) -> None: for as_hex in threejane_context._USAGE_READS: @@ -345,12 +329,6 @@ def test_amounts_are_truncated_to_whole_tokens(self) -> None: def test_outstanding_is_allocated_minus_claimed(self) -> None: self.assertEqual(_distributor_context().outstanding_raw, (84_649_011 - 38_919_583) * WAD) - def test_report_lists_accounting_and_links_token(self) -> None: - report = format_threejane_report([_distributor_context()], 1, {}) - self.assertIn("**Distribution mode:**", report) - self.assertIn("maxClaimable 84,649,011 JANE", report) - self.assertIn(f"https://etherscan.io/address/{JANE}", report) - def test_cadence_line_compares_against_the_previous_epoch(self) -> None: prompt = format_threejane_prompt([_distributor_context()]) self.assertIn("Epoch 45 is the current epoch.", prompt) @@ -373,11 +351,6 @@ def test_prompt_states_ownership_direction(self) -> None: self.assertIn(f"RewardsDistributor.owner() = {TIMELOCK}", prompt) self.assertIn("owned BY this address", prompt) - def test_report_links_the_owner(self) -> None: - report = format_threejane_report([_distributor_context()], 1, {TIMELOCK: "3Jane 24h TimelockController"}) - self.assertIn("**Owner**", report) - self.assertIn(f"https://etherscan.io/address/{TIMELOCK}", report) - def test_context_contributes_addresses_and_labels(self) -> None: context = _distributor_context() self.assertEqual(context.addresses, [DISTRIBUTOR, TIMELOCK, JANE]) diff --git a/tests/test_timelock_execution_context.py b/tests/test_timelock_execution_context.py index 2634a7b4..abdca3fd 100644 --- a/tests/test_timelock_execution_context.py +++ b/tests/test_timelock_execution_context.py @@ -7,7 +7,6 @@ from utils.llm import timelock_execution_context from utils.llm.timelock_execution_context import ( TimelockExecutionContext, - format_timelock_execution_report, operation_id, resolve_timelock_execution_context, ) @@ -86,11 +85,6 @@ def test_unscheduled_done_and_early_make_no_queue_claim(self) -> None: for text in (unscheduled, early, done): self.assertNotIn("sat publicly in the timelock queue", text) - def test_report(self) -> None: - report = format_timelock_execution_report([_context(READY_AT)], 1, {}) - self.assertIn(f"**Operation ID:** `{OPERATION_ID}` (4 call(s))", report) - self.assertIn("**Min delay:** 7d", report) - class TestResolve(unittest.TestCase): @patch.object(timelock_execution_context, "get_contract_label", return_value="") diff --git a/tests/test_yearn_auction_context.py b/tests/test_yearn_auction_context.py index d878222a..ae54c27c 100644 --- a/tests/test_yearn_auction_context.py +++ b/tests/test_yearn_auction_context.py @@ -9,7 +9,6 @@ from utils.llm import yearn_auction_context from utils.llm.yearn_auction_context import ( format_yearn_auction_prompt, - format_yearn_auction_report, resolve_yearn_auction_context, ) @@ -165,10 +164,6 @@ def test_grove_batch_lowers_the_auction_floor_and_kicks_the_whole_balance( self.assertNotIn("proceeds the auction paid it", prompt) self.assertIn("(all of it would be 0.088% of totalAssets)", prompt) - report = format_yearn_auction_report(contexts, 1, {}) - self.assertIn(f"[`{BIG_AUCTION}`](https://etherscan.io/address/{BIG_AUCTION})", report) - self.assertIn("- **Proposed:**", report) - def test_report_before_the_setter_kicks_at_the_old_floor(self, mock_cm: MagicMock, *_: MagicMock) -> None: (context,) = self._resolve(mock_cm, [(BIG, REPORT), (BIG, SET_FLOOR)]) kick, setter = context.proposal_lines(str) diff --git a/tests/test_yearn_strategy_context.py b/tests/test_yearn_strategy_context.py index 1bb3a4e5..4f5b96f8 100644 --- a/tests/test_yearn_strategy_context.py +++ b/tests/test_yearn_strategy_context.py @@ -8,7 +8,6 @@ from utils.llm import yearn_strategy_context from utils.llm.yearn_strategy_context import ( format_yearn_strategy_prompt, - format_yearn_strategy_report, resolve_yearn_strategy_context, ) @@ -137,9 +136,6 @@ def test_grove_batch_shows_old_to_new_and_the_unlock_burn(self, mock_cm: MagicMo self.assertIn(f"default trigger → {FIXED_TRIGGER} (StrategyFixedReportTrigger)", prompt) self.assertIn("report only once 3d (259200 s) have passed since lastReport (last report 3h 26m ago)", prompt) - report = format_yearn_strategy_report([context], 1, {}) - self.assertIn(f"[`{SAM}`](https://etherscan.io/address/{SAM}) (GnosisSafeProxy)", report) - def test_report_timing_follows_the_unlock_time_set_earlier_in_the_batch( self, mock_cm: MagicMock, *_: MagicMock ) -> None: @@ -152,9 +148,9 @@ def test_report_timing_follows_the_unlock_time_set_earlier_in_the_batch( def test_report_transfers_note_is_said_once(self, mock_cm: MagicMock, *_: MagicMock) -> None: contexts = self._resolve(mock_cm, [(GROVE, REPORT), (SPARK, REPORT)]) - for rendered in (format_yearn_strategy_prompt(contexts), format_yearn_strategy_report(contexts, 1, {})): - self.assertEqual(rendered.count("Token transfers inside report()"), 1) - self.assertTrue(rendered.endswith("not payments.")) + prompt = format_yearn_strategy_prompt(contexts) + self.assertEqual(prompt.count("Token transfers inside report()"), 1) + self.assertTrue(prompt.endswith("not payments.")) (context,) = self._resolve(mock_cm, [(GROVE, _unlock(0))]) self.assertNotIn("Token transfers", format_yearn_strategy_prompt([context])) diff --git a/tests/test_yearn_v3_context.py b/tests/test_yearn_v3_context.py index 0555d395..1eb9828d 100644 --- a/tests/test_yearn_v3_context.py +++ b/tests/test_yearn_v3_context.py @@ -14,7 +14,6 @@ StrategyState, YearnV3VaultContext, format_yearn_v3_prompt, - format_yearn_v3_report, resolve_yearn_v3_context, ) @@ -160,13 +159,6 @@ def test_prompt_states_units_as_verified(self) -> None: self.assertIn("False only permits custom queues — the default queue still applies", prompt) self.assertIn(f"1. Spark Accumulator {PEER}: current_debt 921 WETH / max_debt 10,000 WETH", prompt) - def test_report_links_queue_and_lists_proposals(self) -> None: - report = format_yearn_v3_report([_context([ADD_LOOPER, CAP_LOOPER])], 1, {}) - self.assertIn(f"https://etherscan.io/address/{VAULT}", report) - self.assertIn(f"https://etherscan.io/address/{PEER}", report) - self.assertIn("- **Proposed:**", report) - self.assertIn("update_max_debt_for_strategy(wstETH/WETH Spark Looper): 10,000 WETH", report) - def test_labels_name_vault_and_strategies(self) -> None: labels = _context([]).labels self.assertEqual(labels[VAULT], "WETH-2 yVault (yvWETH-2)") @@ -570,8 +562,6 @@ def test_look_through_exposure(self) -> None: self.assertIn("stETH Accumulator 189.02 WETH (12.6%) → 89.17 WETH (6.0%)", line) self.assertIn("Yearn OG WETH 46.11 WETH (3.1%) → 21.75 WETH (1.5%)", line) self.assertFalse([line for line in self.weth1.outcome_lines if line.startswith("Look-through")]) - report = format_yearn_v3_report([self.weth2], 1, {}) - self.assertIn("- Look-through exposure of WETH-2 yVault", report) def test_exit_liquidity_drops_by_the_off_queue_deposit(self) -> None: (line,) = [line for line in self.weth2.outcome_lines if line.startswith("Exit liquidity")] diff --git a/utils/llm/README.md b/utils/llm/README.md index 4c1ac8fe..96721aac 100644 --- a/utils/llm/README.md +++ b/utils/llm/README.md @@ -227,7 +227,7 @@ For Infinifi mainnet alerts, the adapter: 4. Reconstructs the escrow's current whitelist from `WhitelistUpdated` events. ERC20 targets are listed as tokens, and other targets (redemption vaults, swappers) are listed with their verified contract name; these used to be dropped. For `setWhitelist` calls, it reads each target's flag before the call and, for a token, the router's balance of it: once whitelisted, `externalCall` can transfer or approve that balance. 5. Decodes `RWAEscrowRateManager` calls. `setRate(escrow, rate)` is an annual accrual rate in WAD around 1e18, bounded to ±20%. The context shows the stored rate before and the proposed rate after, as signed annual percentages, plus the yearly amount on current `totalAssets`. Reports had read these as relative changes of the raw number ("raises the rate by 1.09%" for 7.51% → 8.683% a year). `governanceUpdateTotalAssets(escrow, assets)` is shown as an unbounded overwrite of `totalAssets`, with the profit or loss it books. -The result is added to the LLM prompt as verified protocol context and rendered independently in the Wavey Gist under `## Protocol Context`. Semantics are read from the verified `RWAEscrow`, `RWAEscrowRouter` and `RWAEscrowRateManager` sources. Failures are best-effort and never block the governance alert. +The result is added to the LLM prompt as verified protocol context. Semantics are read from the verified `RWAEscrow`, `RWAEscrowRouter` and `RWAEscrowRateManager` sources. Failures are best-effort and never block the governance alert. ### 5e-2. Infinifi Outland Context (`utils/llm/infinifi_outland_context.py`) @@ -399,7 +399,7 @@ ownership guard are also included when uniquely resolved in the bundle. This exp semantic enum changes despite an unchanged ABI, unsupported scaling reverts, and unchanged authorization/approval code that a diff alone omits. Missing source or owner reads remain explicitly unavailable; a replacement with a different contract name is not assumed to -retain PendleSwap behavior. Context is rendered in both the prompt and gist and sets no +retain PendleSwap behavior. Context is rendered in the prompt and sets no fixed risk tag or storage-safety verdict. Nested governance wrappers are not resolved by this adapter. @@ -409,7 +409,7 @@ verdict is not evidence that collisions are more likely. ### 5g. Adapter Registry (`utils/llm/protocol_context.py`) -Adapters register in `_ADAPTERS`; `resolve_protocol_context()` fans one call out to all of them and merges the rendered prompt text, report text, introduced addresses, and address labels. Each adapter guards itself, so registration order carries no meaning. An adapter that raises, while resolving or while rendering, is logged and skipped (with its addresses and labels) rather than dropping the alert. Most guard on protocol and chain. The Yearn V3 adapter guards on call shape and `apiVersion()`; the tokenized-strategy and auction adapters on call shape and their getters. The control-transfer, allowlist-scope and timelock-execution adapters guard on call shape alone. +Adapters register in `_ADAPTERS`; `resolve_protocol_context()` fans one call out to all of them and merges the rendered prompt text, introduced addresses, and address labels. Each adapter guards itself, so registration order carries no meaning. An adapter that raises, while resolving or while rendering, is logged and skipped (with its addresses and labels) rather than dropping the alert. Most guard on protocol and chain. The Yearn V3 adapter guards on call shape and `apiVersion()`; the tokenized-strategy and auction adapters on call shape and their getters. The control-transfer, allowlist-scope and timelock-execution adapters guard on call shape alone. Adapters receive the inner calls of executed governance wrappers too. `expand_executed_calls()` keeps each wrapper and splices in its decoded inner calls (`execute`, `executeBatch`, `executeTransaction`, up to two levels deep). Address labels are collected over the same list. Scheduling wrappers are not expanded, because their inner calls run in a later transaction. Before this, a Safe batch registered Flex inside a timelock `executeBatch` and then funded it in a top-level `update_debt`. The V3 adapter never saw the registration and reported the funding call as "REVERTS — inactive strategy". The model then flagged the context as contradicting the simulation. The executing address (the Safe) is labeled as well, so it no longer appears unlabeled in the reference table. @@ -609,7 +609,7 @@ The report can end with a code-generated `## Reference` table after Call Flow. I | [`0xE4C72b4dE5b0F9ACcEA880Ad0b1F944F85A9dAA0`](https://etherscan.io/address/0xE4C72b4dE5b0F9ACcEA880Ad0b1F944F85A9dAA0) | New Silver Series 2 DROP | Protocol context | ``` -It used to list every address with a Description column that restated the call flow ("Passed as `_asset` to `setOracle`"). That doubled the report without adding information. The DETAIL prompt likewise asks for limitations once, in a final `### Not verified` list of at most four bullets. It also asks for no standalone address lists and no tables repeating Current State or Protocol Context. A report had spent about 40% of its text on per-paragraph caveats. +It used to list every address with a Description column that restated the call flow ("Passed as `_asset` to `setOracle`"). That doubled the report without adding information. The DETAIL prompt likewise asks for limitations once, in a final `### Not verified` list of at most four bullets. It also asks for no standalone address lists and no tables repeating Current State. A report had spent about 40% of its text on per-paragraph caveats. **Call Flow is built in Python, not asked of the LLM** — it comes straight from the input calls (`CallEntry` per call: original 1-based index, target, signature or diff --git a/utils/llm/ai_explainer.py b/utils/llm/ai_explainer.py index 31ddae27..71d3ed69 100644 --- a/utils/llm/ai_explainer.py +++ b/utils/llm/ai_explainer.py @@ -93,12 +93,11 @@ happened to omit unless it changes the risk. - Do not add a standalone list of contracts or addresses (the report's Call Flow and Reference sections already link them), and do not rebuild tables of values the Current - State or Protocol Context sections already show — quote the before→after in prose. + State section already shows — quote the before→after in prose. - When the same calls repeat across several contracts (a setter and a report() per strategy), explain the pattern once, then give each contract's own numbers — do not write a section per contract that repeats the same explanation. -- Do not restate Protocol Context settings that do not bear on the verdict; the report - already publishes that section. +- Do not restate Protocol Context settings that do not bear on the verdict. Address hyperlink rule (applies to DETAIL only): - EVERY address you mention must be a markdown link to the block explorer, never a @@ -294,7 +293,7 @@ why, parameter values and significance, asset/token flow, state changes, and an explicit risk rationale. Keep it lean: put limitations once in a final `### Not verified` list (at most 4 bullets, omitted when nothing material is unverified), no standalone address -lists, no tables that repeat the Current State or Protocol Context sections. It MUST stay fully consistent with the TLDR above — same magnitudes, +lists, no tables that repeat the Current State section. It MUST stay fully consistent with the TLDR above — same magnitudes, same risk level. Do not contradict its numbers or verdict and do not restate it verbatim; expand on the reasoning. @@ -2235,7 +2234,7 @@ def explain_transaction( roles_by_target = _collect_role_names([(target, decoded)], chain_id) token_flows = _collect_token_flows([(target, decoded)], chain_id, address_labels) related_tokens = _collect_related_tokens([(target, decoded)], chain_id) - protocol_ctx = resolve_protocol_context(protocol, chain_id, executed_calls, address_labels) + protocol_ctx = resolve_protocol_context(protocol, chain_id, executed_calls) for address, context_label in protocol_ctx.labels.items(): address_labels.setdefault(address, context_label) @@ -2319,7 +2318,6 @@ def explain_transaction( label=label, from_address=from_address, label_address=label_address or from_address, - protocol_context=protocol_ctx.report, related_addresses=protocol_ctx.addresses, state_read_notes=state_reads.report_text(chain_id, address_labels), context_note=context_note, @@ -2642,7 +2640,7 @@ def explain_batch_transaction( roles_by_target = _collect_role_names(decoded_with_target, chain_id) token_flows = _collect_token_flows(decoded_with_target, chain_id, address_labels) related_tokens = _collect_related_tokens(decoded_with_target, chain_id) - protocol_ctx = resolve_protocol_context(protocol, chain_id, executed_calls, address_labels) + protocol_ctx = resolve_protocol_context(protocol, chain_id, executed_calls) for address, context_label in protocol_ctx.labels.items(): address_labels.setdefault(address, context_label) @@ -2704,7 +2702,6 @@ def explain_batch_transaction( label=label, from_address=from_address, label_address=label_address or from_address, - protocol_context=protocol_ctx.report, related_addresses=protocol_ctx.addresses, state_read_notes=state_reads.report_text(chain_id, address_labels), context_note=context_note, diff --git a/utils/llm/control_transfer_context.py b/utils/llm/control_transfer_context.py index c16a0043..cbd53c19 100644 --- a/utils/llm/control_transfer_context.py +++ b/utils/llm/control_transfer_context.py @@ -30,7 +30,6 @@ from utils.chains import Chain from utils.eth_view import call_view from utils.llm.abi_exposure import exposes -from utils.llm.report import address_link from utils.logger import get_logger from utils.on_chain_state import resolve_function_source from utils.source_context import find_state_var_writes, get_contract_label @@ -394,54 +393,3 @@ def resolve_control_transfer_context( def format_control_transfer_prompt(contexts: list[ControlTransferContext]) -> str: """Render verified control-transfer facts for the LLM prompt.""" return "\n\n".join("\n".join(context.lines()) for context in contexts) - - -def describe_controller_markdown(info: ControllerInfo, chain_id: int, labels: dict[str, str]) -> str: - """Markdown twin of ``ControllerInfo.describe`` with explorer links.""" - link = address_link(info.address, chain_id, labels) - if info.kind == "none": - return f"{link} — zero address: control is **renounced**" - if info.kind == "eoa": - return f"{link} — **EOA** (single private key)" - if info.kind == "eip7702": - return f"{link} — **EOA with EIP-7702 delegated code** (single private key)" - if info.kind == "safe": - return f"{link} — Safe multisig, **{info.threshold}-of-{info.owner_count}**" - parts = [f"{link} — contract"] - if info.controlled_by is not None: - parts.append( - f"its `{info.controller_getter}()` is {describe_controller_markdown(info.controlled_by, chain_id, labels)}" - ) - if info.has_operators: - parts.append("keeps an **operator whitelist** (approved addresses act through it)") - return "; ".join(parts) - - -def format_control_transfer_report( - contexts: list[ControlTransferContext], - chain_id: int, - labels: dict[str, str], -) -> str: - """Render the deterministic control-transfer section for the gist report.""" - sections: list[str] = [] - for context in contexts: - target = address_link(context.target, chain_id, labels) - if context.role_hash: - lines = [f"**`{context.signature}`** on {target} — grants role `{context.role_hash}`"] - else: - lines = [f"**`{context.signature}`** on {target} — hands over **{context.role}**"] - if context.current is not None: - lines.append( - f"- **Current {context.role}:** {describe_controller_markdown(context.current, chain_id, labels)}" - ) - who = "Grantee" if context.role_hash else f"Proposed {context.role}" - lines.append(f"- **{who}:** {describe_controller_markdown(context.proposed, chain_id, labels)}") - timing = context.timing_note() - if timing: - heading, _, detail = timing.partition(": ") - lines.append(f"- **{heading}:** {detail}" if detail else f"- **Timing:** {timing}") - change = context.threshold_change() - if change: - lines.append(f"- **Threshold:** {change}") - sections.append("\n".join(lines)) - return "\n\n".join(sections) diff --git a/utils/llm/infinifi_context.py b/utils/llm/infinifi_context.py index 70838e1e..a62aa767 100644 --- a/utils/llm/infinifi_context.py +++ b/utils/llm/infinifi_context.py @@ -39,7 +39,7 @@ from utils.formatting import format_decimal_amount, normalize_token_amount from utils.http_client import fetch_json from utils.infinifi_escrow import fetch_whitelist_targets -from utils.llm.report import address_link, iter_address_values +from utils.llm.report import iter_address_values from utils.logger import get_logger from utils.source_context import fetch_abi_entries, get_contract_label from utils.web3_wrapper import ChainManager @@ -657,41 +657,6 @@ def format_infinifi_prompt(contexts: list[InfinifiEscrowContext]) -> str: return "\n\n".join(sections) -def format_infinifi_report( - contexts: list[InfinifiEscrowContext], - chain_id: int, - labels: dict[str, str], -) -> str: - """Render the deterministic Infinifi farm section for the gist report.""" - sections: list[str] = [] - for context in contexts: - asset = context.accounting_asset - total_assets = format_decimal_amount(normalize_token_amount(context.total_assets_raw, asset.decimals)) - farm_name = context.farm_name or context.farm_slug or "Unknown farm" - lines = [ - f"- **Farm:** {farm_name} — {address_link(context.farm_address, chain_id)}", - f"- **Escrow:** {address_link(context.escrow_address, chain_id, labels)}", - f"- **Accounting asset:** {asset.name} (`{asset.symbol}`, {asset.decimals} decimals) — " - f"{address_link(asset.address, chain_id)}", - f"- **Current `totalAssets`:** `{total_assets} {asset.symbol}` (`{context.total_assets_raw:,}` raw units)", - ] - if custody := context.custody_line(): - lines.append(f"- **Custody:** {custody}") - if context.configured_tokens or context.whitelisted_contracts: - lines.append("- **Whitelisted call targets** (`externalCall`):") - for token in context.configured_tokens: - lines.append( - f" - {token.name} (`{token.symbol}`, {token.decimals} decimals) — " - f"{address_link(token.address, chain_id)}" - ) - for address, label in context.whitelisted_contracts: - lines.append(f" - {address_link(address, chain_id, {address: label} if label else labels)}") - lines.extend(f"- {line}" for line in context.rate_lines()) - lines.extend(f"- {line}" for line in context.whitelist_lines()) - sections.append("\n".join(lines)) - return "\n\n".join(sections) - - def reset_cache() -> None: """Reset process caches for tests or long-running workers.""" _fetch_farm_records.cache_clear() diff --git a/utils/llm/infinifi_farm_context.py b/utils/llm/infinifi_farm_context.py index f805adcb..f360cf80 100644 --- a/utils/llm/infinifi_farm_context.py +++ b/utils/llm/infinifi_farm_context.py @@ -30,7 +30,6 @@ from utils.chains import Chain from utils.erc20_metadata import fetch_erc20_metadata from utils.llm.abi_exposure import exposes -from utils.llm.report import address_link from utils.logger import get_logger from utils.web3_wrapper import ChainManager @@ -493,32 +492,3 @@ def format_infinifi_farm_prompt(contexts: list[FarmContext]) -> str: lines = [PAIR_SEMANTICS] if any(isinstance(context, SwapPairContext) for context in contexts) else [] lines.extend(context.prompt_line() for context in contexts) return "\n".join(lines) - - -def format_infinifi_farm_report(contexts: list[FarmContext], chain_id: int, labels: dict[str, str]) -> str: - """Render the deterministic farm-configuration section for the gist report.""" - lines = [] - for context in contexts: - link = address_link(context.farm, chain_id, labels) - if isinstance(context, SwapPairContext): - lines.append( - f"- **Swap pair {context.token_in.symbol}/{context.token_out.symbol}** on {link}: minimum output " - f"`{_percent(context.ratio)}` of oracle value (max loss `{_percent(WAD - context.ratio)}`; farm " - f"floor `{_percent(context.floor)}`), cooldown `{context.cooldown:,}s` ({_duration(context.cooldown)}); " - f"before: {context.current_text()}" - ) - elif isinstance(context, AssetEnableContext): - for asset in context.assets: - state = "oracle set" if asset.can_be_priced else "**no Accounting oracle — reverts until one is set**" - lines.append(f"- **Enable {asset.symbol}** on {link}: {asset.status}; {state}") - else: - change = ( - "no immediate change (no PTs held)" - if context.total_pts == 0 - else f"assets() changes by `{_amount(context.assets_change, context.asset_decimals, context.asset_symbol)}`" - ) - lines.append( - f"- **Maturity PT discount** on {link}: `{_percent(context.current)}` → `{_percent(context.proposed)}`; " - f"holds `{_amount(context.total_pts, context.pt_decimals, 'PT')}`; {change}" - ) - return "\n".join(lines) diff --git a/utils/llm/infinifi_oracle_context.py b/utils/llm/infinifi_oracle_context.py index d49250ee..20eaa919 100644 --- a/utils/llm/infinifi_oracle_context.py +++ b/utils/llm/infinifi_oracle_context.py @@ -25,7 +25,6 @@ from utils.chains import Chain from utils.erc20_metadata import fetch_erc20_metadata from utils.llm.abi_exposure import exposes -from utils.llm.report import address_link from utils.logger import get_logger from utils.source_context import fetch_verified_contract from utils.web3_wrapper import ChainManager @@ -256,25 +255,3 @@ def format_infinifi_oracle_prompt(contexts: list[OracleAssignmentContext]) -> st line += " " + context.feed.describe() lines.append(line) return "\n".join(lines) - - -def format_infinifi_oracle_report( - contexts: list[OracleAssignmentContext], chain_id: int, labels: dict[str, str] -) -> str: - """Render the deterministic oracle section for the gist report.""" - lines = [] - for context in contexts: - line = ( - f"- **Oracle price:** {address_link(context.oracle, chain_id, labels)} reports `{context.price_raw}` " - f"→ one whole `{context.asset_symbol}` = `{context.unit_price_text}` reference units (USDC ≈ 1)" - ) - feed = context.feed - if feed: - freshness = f", updated {_hours(feed.age)} before this alert" if feed.age is not None else "" - line += ( - f"\n - **Feed:** {address_link(feed.feed, chain_id, labels)} — " - f'"{feed.description or "n/a"}"; heartbeat `{_hours(feed.heartbeat)}`{freshness}. ' - "A stale feed makes `price()` revert." - ) - lines.append(line) - return "\n".join(lines) diff --git a/utils/llm/infinifi_outland_context.py b/utils/llm/infinifi_outland_context.py index 3e4e0562..7b52d91f 100644 --- a/utils/llm/infinifi_outland_context.py +++ b/utils/llm/infinifi_outland_context.py @@ -28,7 +28,6 @@ from utils.calldata.decoder import DecodedCall from utils.chains import Chain from utils.llm.abi_exposure import exposes -from utils.llm.report import address_link from utils.logger import get_logger from utils.web3_wrapper import ChainManager @@ -585,44 +584,3 @@ def format_outland_prompt(contexts: list[OutlandContext]) -> str: else: lines.append(f"Connector {context.connector} route to chain {context.chain_id}: {_route_status(context)}.") return "\n".join(lines) - - -def format_outland_report(contexts: list[OutlandContext], chain_id: int, labels: dict[str, str]) -> str: - """Render the deterministic Outland section for the gist report.""" - lines: list[str] = [] - for context in contexts: - if isinstance(context, FarmTypeContext): - lines.append( - f"- **Farm type {context.farm_type}:** `FarmTypes.{context.type_name}` — {context.type_note} " - f"({address_link(context.registry, chain_id, labels)} `{context.function_name}`)" - ) - lines.extend(f" - {line}" for line in context.terms_lines()) - elif isinstance(context, OutlandMessageContext): - lines.append(f"- **Cross-chain message (`{context.function_name}`):** {context.describe()}") - elif isinstance(context, HubVaultContext): - registered = ", ".join(f"`{chain}`" for chain in context.registered_chain_ids) or "none" - effect = ( - f"replaces {address_link(context.replaced_vault, chain_id, labels)}" - if context.replaced_vault - else "new chain, no vault replaced" - ) - lines.append( - f"- **PortalHub vault for chain `{context.vault_chain_id}`:** {effect}; " - f"chains registered before: {registered}" - ) - else: - if context.proposed is not None: - change = "set" if context.proposed.is_configured else "**cleared**" - status = f"{change} by this batch — peer {address_link(context.proposed.peer, chain_id, labels)}" - status += f", gas limit `{context.proposed.gas_limit:,}`, selector `{context.proposed.chain_selector}`" - if context.current.is_configured: - status += f" (was peer {address_link(context.current.peer, chain_id, labels)})" - elif context.current.is_configured: - status = f"configured, unchanged — peer {address_link(context.current.peer, chain_id, labels)}" - else: - status = "**not configured** — sends revert until a later `setConfiguration` sets the peer" - lines.append( - f"- **Connector route to chain `{context.chain_id}`** " - f"({address_link(context.connector, chain_id, labels)}): {status}" - ) - return "\n".join(lines) diff --git a/utils/llm/pendle_context.py b/utils/llm/pendle_context.py index a43140a5..ef9fce11 100644 --- a/utils/llm/pendle_context.py +++ b/utils/llm/pendle_context.py @@ -7,7 +7,7 @@ from utils.calldata.decoder import DecodedCall from utils.chains import Chain -from utils.llm.report import address_link, checksum_or_none +from utils.llm.report import checksum_or_none from utils.logger import get_logger from utils.proxy import get_current_implementation from utils.solidity_text import contract_functions, declares_contract, strip_noise, struct_definitions @@ -215,16 +215,7 @@ def _upgrade_implementation(target: str, call: DecodedCall) -> str | None: def format_pendle_prompt(contexts: list[PendleSwapContext]) -> str: """Render integration references separately from live state and verified code.""" - return "\n\n".join(_format_contexts(group, None, {}) for group in _by_proxy(contexts)) - - -def format_pendle_report( - contexts: list[PendleSwapContext], - chain_id: int, - labels: dict[str, str], -) -> str: - """Render the same evidence with full explorer links for the gist report.""" - return "\n\n".join(_format_contexts(group, chain_id, labels) for group in _by_proxy(contexts)) + return "\n\n".join(_format_contexts(group) for group in _by_proxy(contexts)) def _by_proxy(contexts: list[PendleSwapContext]) -> list[list[PendleSwapContext]]: @@ -256,8 +247,8 @@ def _execution_note(context: PendleSwapContext) -> str: ) -def _format_contexts(contexts: list[PendleSwapContext], chain_id: int | None, labels: dict[str, str]) -> str: - """Keep prompt and report facts identical, varying only address rendering. +def _format_contexts(contexts: list[PendleSwapContext]) -> str: + """Render one proxy's upgrades. The integration reference, proxy state and each implementation's source are stated once for the batch; every upgrade call keeps its own line and payload. @@ -266,8 +257,8 @@ def _format_contexts(contexts: list[PendleSwapContext], chain_id: int | None, la return "" def link(address: str | None) -> str: - """Link known addresses and keep missing state explicit.""" - return address_link(address, chain_id, labels) if address and chain_id else address or "unavailable" + """Keep missing state explicit.""" + return address or "unavailable" first = contexts[0] lines = [ diff --git a/utils/llm/permission_grant_context.py b/utils/llm/permission_grant_context.py index d82348fa..e0dc4815 100644 --- a/utils/llm/permission_grant_context.py +++ b/utils/llm/permission_grant_context.py @@ -32,8 +32,7 @@ from utils.calldata.decoder import DecodedCall from utils.chains import Chain -from utils.llm.control_transfer_context import ControllerInfo, describe_controller, describe_controller_markdown -from utils.llm.report import address_link +from utils.llm.control_transfer_context import ControllerInfo, describe_controller from utils.logger import get_logger from utils.solidity_text import FunctionDef, contract_functions from utils.source_context import fetch_verified_contract, get_contract_label @@ -601,68 +600,3 @@ def format_permission_grant_prompt(contexts: list[PermissionGrantContext]) -> st # What the call does to the gated entry points, keyed by the holder's access afterwards. -_EFFECT: dict[bool | None, str] = {True: "opens", False: "closes", None: "controls"} - - -def format_permission_grant_report( - contexts: list[PermissionGrantContext], - chain_id: int, - labels: dict[str, str], -) -> str: - """Render the deterministic allowlist-scope section for the gist report. - - One block per allowlist: its entry points and members before the batch once, - then each change with its holder, then the members after. - """ - - def links(addresses: tuple[str, ...]) -> str: - return ", ".join(address_link(a, chain_id, labels) for a in addresses) or "none" - - sections: list[str] = [] - for group in _groups(contexts): - first, last = group[0], group[-1] - accesses = {context.access for context in group} - effect = _EFFECT[accesses.pop()] if len(accesses) == 1 else "gates" - if len(group) == 1: - lines = [ - f"**`{first.signature}`** on {address_link(first.target, chain_id, labels)} — " - f"{first.verb.lower()} an entry in `{first.allowlist}`", - f"- **Holder:** {describe_controller_markdown(first.holder, chain_id, labels)}", - ] - else: - lines = [ - f"**`{first.allowlist}`** on {address_link(first.target, chain_id, labels)} — {len(group)} changes" - ] - lines.append(f"- **Entry points it {effect}:**") - for fn in first.gated: - flag = f" — **arbitrary {fn.arbitrary_call}**" if fn.arbitrary_call else "" - if fn.restricted_by: - flag = f" — forwards calls, restricted by {fn.restricted_by}" - lines.append(f" - `{fn.signature}` ({fn.via}){flag}") - if any(fn.arbitrary_call for fn in first.gated): - lines.append( - "- **Scope:** an arbitrary-call entry point lets the holder use every permission the target holds." - ) - if len(group) > 1: - if first.holders_before is not None: - lines.append(f"- **Members before:** {links(first.holders_before)}") - lines.append("- **Changes, in batch order:**") - for context in group: - lines.append( - f" - `{context.signature}` {context.verb.lower()} " - f"{describe_controller_markdown(context.holder, chain_id, labels)}" - ) - if context.holder_forwarders: - forwarders = ", ".join(f"`{f}`" for f in context.holder_forwarders) - lines.append(f" - holder forwards arbitrary calls: {forwarders}") - elif first.holder_forwarders: - forwarders = ", ".join(f"`{f}`" for f in first.holder_forwarders) - lines.append(f"- **Holder forwards arbitrary calls:** {forwarders}") - after = last.holders_after() - if first.holders_before is not None and after is not None: - if len(group) > 1: - lines.append(f"- **Members after:** {links(after)}") - else: - lines.append(f"- **`{first.allowlist}` members:** {links(first.holders_before)} → {links(after)}") - sections.append("\n".join(lines)) - return "\n\n".join(sections) diff --git a/utils/llm/protocol_context.py b/utils/llm/protocol_context.py index 599b80f8..154730a1 100644 --- a/utils/llm/protocol_context.py +++ b/utils/llm/protocol_context.py @@ -30,62 +30,50 @@ from utils.calldata.wrappers import unwrap_executed_calls from utils.llm.control_transfer_context import ( format_control_transfer_prompt, - format_control_transfer_report, resolve_control_transfer_context, ) from utils.llm.infinifi_context import ( format_infinifi_prompt, - format_infinifi_report, resolve_infinifi_context, ) from utils.llm.infinifi_farm_context import ( format_infinifi_farm_prompt, - format_infinifi_farm_report, resolve_infinifi_farm_context, ) from utils.llm.infinifi_oracle_context import ( format_infinifi_oracle_prompt, - format_infinifi_oracle_report, resolve_infinifi_oracle_context, ) from utils.llm.infinifi_outland_context import ( format_outland_prompt, - format_outland_report, resolve_outland_context, ) from utils.llm.pendle_context import ( format_pendle_prompt, - format_pendle_report, resolve_pendle_context, ) from utils.llm.permission_grant_context import ( format_permission_grant_prompt, - format_permission_grant_report, resolve_permission_grant_context, ) from utils.llm.threejane_context import ( format_threejane_prompt, - format_threejane_report, resolve_threejane_context, ) from utils.llm.timelock_execution_context import ( format_timelock_execution_prompt, - format_timelock_execution_report, resolve_timelock_execution_context, ) from utils.llm.yearn_auction_context import ( format_yearn_auction_prompt, - format_yearn_auction_report, resolve_yearn_auction_context, ) from utils.llm.yearn_strategy_context import ( format_yearn_strategy_prompt, - format_yearn_strategy_report, resolve_yearn_strategy_context, ) from utils.llm.yearn_v3_context import ( format_yearn_v3_prompt, - format_yearn_v3_report, resolve_yearn_v3_context, ) from utils.logger import get_logger @@ -96,61 +84,53 @@ @dataclass(frozen=True) class _Adapter: - """One protocol's resolver plus its prompt and report renderers.""" + """One protocol's resolver plus its prompt renderer.""" name: str resolve: Callable[[str, int, list[tuple[str, DecodedCall]]], list[Any]] format_prompt: Callable[[list[Any]], str] - format_report: Callable[[list[Any], int, dict[str, str]], str] _ADAPTERS: tuple[_Adapter, ...] = ( - _Adapter("infinifi", resolve_infinifi_context, format_infinifi_prompt, format_infinifi_report), - _Adapter("infinifi-outland", resolve_outland_context, format_outland_prompt, format_outland_report), + _Adapter("infinifi", resolve_infinifi_context, format_infinifi_prompt), + _Adapter("infinifi-outland", resolve_outland_context, format_outland_prompt), _Adapter( "infinifi-oracle", resolve_infinifi_oracle_context, format_infinifi_oracle_prompt, - format_infinifi_oracle_report, ), _Adapter( "infinifi-farm", resolve_infinifi_farm_context, format_infinifi_farm_prompt, - format_infinifi_farm_report, ), - _Adapter("3jane", resolve_threejane_context, format_threejane_prompt, format_threejane_report), - _Adapter("pendle", resolve_pendle_context, format_pendle_prompt, format_pendle_report), - _Adapter("yearn-v3", resolve_yearn_v3_context, format_yearn_v3_prompt, format_yearn_v3_report), + _Adapter("3jane", resolve_threejane_context, format_threejane_prompt), + _Adapter("pendle", resolve_pendle_context, format_pendle_prompt), + _Adapter("yearn-v3", resolve_yearn_v3_context, format_yearn_v3_prompt), _Adapter( "yearn-strategy", resolve_yearn_strategy_context, format_yearn_strategy_prompt, - format_yearn_strategy_report, ), _Adapter( "yearn-auction", resolve_yearn_auction_context, format_yearn_auction_prompt, - format_yearn_auction_report, ), _Adapter( "control-transfer", resolve_control_transfer_context, format_control_transfer_prompt, - format_control_transfer_report, ), _Adapter( "permission-grant", resolve_permission_grant_context, format_permission_grant_prompt, - format_permission_grant_report, ), _Adapter( "timelock-execution", resolve_timelock_execution_context, format_timelock_execution_prompt, - format_timelock_execution_report, ), ) @@ -197,7 +177,6 @@ class ResolvedProtocolContext: """Rendered protocol context for one alert, empty when no adapter matched.""" prompt: str = "" - report: str = "" addresses: list[str] = field(default_factory=list) labels: dict[str, str] = field(default_factory=dict) @@ -206,7 +185,6 @@ def resolve_protocol_context( protocol: str, chain_id: int, targets_and_calls: list[tuple[str, DecodedCall]], - labels: dict[str, str] | None = None, ) -> ResolvedProtocolContext: """Resolve and render protocol-specific context from every matching adapter. @@ -214,15 +192,13 @@ def resolve_protocol_context( protocol: Alert protocol name, matched case-insensitively by each adapter. chain_id: Chain the transaction executes on. targets_and_calls: Decoded calls paired with the address each one targets. - labels: Address labels used when rendering the report section. Returns: - Rendered prompt and report text plus the addresses and labels the + Rendered prompt text plus the addresses and labels the adapters introduced. Adapter failures are logged and skipped — context is an enrichment and must never block a governance alert. """ prompts: list[str] = [] - reports: list[str] = [] addresses: list[str] = [] resolved_labels: dict[str, str] = {} @@ -234,25 +210,19 @@ def resolve_protocol_context( continue if not contexts: continue - merged = dict(resolved_labels) - for context in contexts: - for address, label in context.labels.items(): - merged.setdefault(address, label) try: prompt = adapter.format_prompt(contexts) - report = adapter.format_report(contexts, chain_id, {**(labels or {}), **merged}) except Exception as error: # noqa: BLE001 - rendering must not break the alert either logger.info("Protocol context adapter %s failed to render: %s", adapter.name, error) continue for context in contexts: addresses.extend(context.addresses) - resolved_labels = merged + for address, label in context.labels.items(): + resolved_labels.setdefault(address, label) prompts.append(prompt) - reports.append(report) return ResolvedProtocolContext( prompt="\n\n".join(part for part in prompts if part), - report="\n\n".join(part for part in reports if part), addresses=list(dict.fromkeys(addresses)), labels=resolved_labels, ) diff --git a/utils/llm/report.py b/utils/llm/report.py index 8e38acd3..617b0f60 100644 --- a/utils/llm/report.py +++ b/utils/llm/report.py @@ -162,10 +162,6 @@ class ReportContext: # Safe multisend batch labels the utility contract instead. Linked from the # report's Contract header line. label_address: str = "" - # Deterministic protocol-specific facts that belong in the full gist but - # are not part of the raw calldata flow (for example an Infinifi farm and - # the non-accounting ERC20 targets configured in its escrow). - protocol_context: str = "" # Addresses introduced by protocol-specific context. These may not occur in # calldata but still belong in the report's deterministic reference table. related_addresses: list[str] = field(default_factory=list) @@ -710,8 +706,6 @@ def build_report( if ctx.safety_notes: notes = "\n".join(f"- {note}" for note in ctx.safety_notes) sections.append(f"## Safety Checks\n\n{notes}") - if ctx.protocol_context: - sections.append(f"## Protocol Context\n\n{ctx.protocol_context}") reference = format_reference_table(ctx, shown="\n\n".join(sections)) if reference: sections.append(f"## Reference\n\n{reference}") diff --git a/utils/llm/threejane_account_context.py b/utils/llm/threejane_account_context.py index 626513f7..edffb305 100644 --- a/utils/llm/threejane_account_context.py +++ b/utils/llm/threejane_account_context.py @@ -24,10 +24,9 @@ from eth_utils import keccak, to_checksum_address from utils.calldata.decoder import DecodedCall -from utils.chains import EXPLORER_URLS, Chain +from utils.chains import Chain from utils.erc20_metadata import fetch_erc20_metadata from utils.formatting import format_decimal_amount, normalize_token_amount -from utils.llm.report import address_link from utils.llm.threejane_abi import exposes, threejane_abi from utils.logger import get_logger from utils.proxy import eip7702_delegate @@ -615,61 +614,3 @@ def format_account_prompt(context: AccountContext) -> str: *(f"- {context.account_line(account)}" for account in context.accounts), ] ) - - -def format_account_report(context: AccountContext, chain_id: int, labels: dict[str, str]) -> str: - """Render one account context for the gist report.""" - if isinstance(context, LCCBounceContext): - lines = [ - f"- **LCC bounce:** {address_link(context.vault_address, chain_id, labels)} → user " - f"{address_link(context.user_address, chain_id, labels)}", - f" - Commitment removed: `{context.funding.amount(context.commitment_raw)}` " - f"(funding asset {address_link(context.funding.address, chain_id)})", - f" - Account now: activeCommitment `{context.funding.amount(context.active_commitment_raw)}`, " - f"activeMargin `{context.margin.amount(context.active_margin_raw)}` " - f"(margin asset {address_link(context.margin.address, chain_id)})", - f" - Effect: {context.effect_line()}", - ] - if share := context.vault_share_line(): - lines.append(f" - {share}") - return "\n".join(lines) - - lines = [ - f"- **USD3 supply-cap exemptions** on {address_link(context.usd3_address, chain_id, labels)}: " - f"{context.overview_line()}", - f" - {context.semantics_line()}", - f" - {context.purpose_line()}", - f" - {context.pairing_line()}", - f" - {context.supply_line()}", - ] - if exempt_set := context.exempt_set_line(): - lines.append(f" - {exempt_set}") - history_header = " Last change |" if context.history_read else "" - lines += [ - "", - f"| # | Account | Type | supplyCapExempt | ringFenceConduit | USD3 balance |{history_header}", - "|---|---|---|---|---|---|" + ("---|" if context.history_read else ""), - ] - for number, account in enumerate(context.accounts, start=1): - kind = ( - f"EOA, EIP-7702 → {address_link(account.delegate, chain_id, labels)}" - if account.delegate - else account.kind() - ) - issue = f" ⚠️ {account.pairing_issue()}" if account.pairing_issue() else "" - history = f" {_history_cell(account.last_change, chain_id)} |" if context.history_read else "" - lines.append( - f"| {number} | {address_link(account.address, chain_id, labels)} | {kind} | {account.change()} | " - f"{str(account.ring_fence_conduit).lower()}{issue} | `{context.share.amount(account.usd3_balance_raw)}` |" - f"{history}" - ) - return "\n".join(lines) - - -def _history_cell(change: ExemptionChange | None, chain_id: int) -> str: - """``set false at [block N](tx link)`` for the report table, or "none on record".""" - if change is None: - return "none on record" - explorer = EXPLORER_URLS.get(chain_id) - block = f"[block {change.block}]({explorer}/tx/{change.tx_hash})" if explorer else f"block {change.block}" - return f"set {str(change.exempt).lower()} at {block}" diff --git a/utils/llm/threejane_context.py b/utils/llm/threejane_context.py index 3ca57522..784e439a 100644 --- a/utils/llm/threejane_context.py +++ b/utils/llm/threejane_context.py @@ -25,7 +25,6 @@ from utils.calldata.decoder import DecodedCall from utils.chains import Chain from utils.erc20_metadata import fetch_erc20_metadata -from utils.llm.report import address_link from utils.llm.threejane_abi import exposes, threejane_abi from utils.llm.threejane_abi import reset_cache as reset_abi_cache from utils.llm.threejane_account_context import ( @@ -34,7 +33,6 @@ TokenUnit, fetch_token_unit, format_account_prompt, - format_account_report, resolve_account_contexts, ) from utils.logger import get_logger @@ -720,77 +718,6 @@ def _hashed_label_prompt(context: HashedLabelContext) -> str: return "\n".join(lines) -def format_threejane_report( - contexts: list[ThreeJaneContext], - chain_id: int, - labels: dict[str, str], -) -> str: - """Render the deterministic 3Jane section for the gist report.""" - sections: list[str] = [] - for context in contexts: - if isinstance(context, RewardsDistributorContext): - lines = [ - f"- **Rewards distributor:** {address_link(context.distributor_address, chain_id, labels)}", - f"- **Owner** (`owner()`, sole caller of the `onlyOwner` setters): " - f"{address_link(context.owner_address, chain_id, labels)}", - f"- **Reward token:** `{context.token_symbol}` ({context.token_decimals} decimals) — " - f"{address_link(context.token_address, chain_id)}", - f"- **Distribution mode:** `useMint = {str(context.use_mint).lower()}` — " - + ( - f"claims mint new {context.token_symbol}" - + ( - " (distributor holds `MINTER_ROLE`)" - if context.distributor_is_minter - else " (distributor does NOT hold `MINTER_ROLE`)" - ) - if context.use_mint - else f"claims transfer from the distributor's balance of `{context.amount(context.distributor_balance_raw)}`" - ), - f"- **Token supply:** `{context.amount(context.token_total_supply_raw)}` total, " - f"transfers globally enabled: `{str(context.token_transferable).lower()}`", - f"- **Claim accounting:** `maxClaimable {context.amount(context.max_claimable_raw)}` | " - f"`totalClaimed {context.amount(context.total_claimed_raw)}` | " - f"`outstanding {context.amount(context.outstanding_raw)}`", - f"- **Current `merkleRoot`:** `{context.merkle_root}`", - f"- **Current epoch:** `{context.current_epoch}`", - "- **Epoch emissions on-chain now:**", - ] - lines.extend(f" - Epoch `{epoch}`: `{context.amount(value)}`" for epoch, value in context.epoch_emissions) - lines.extend(f"- **Proposed:** {line}" for line in context.cadence_lines()) - sections.append("\n".join(lines)) - elif isinstance(context, HashedLabelContext): - sections.append(_hashed_label_report(context, chain_id, labels)) - else: - sections.append(format_account_report(context, chain_id, labels)) - return "\n\n".join(sections) - - -def _report_value(context: HashedLabelContext, raw: int) -> str: - """A config value for the gist: in its unit when verified, else the grouped raw integer.""" - return f"`{context.amount_text(raw)}`" if context.unit else f"`{raw:,}`" - - -def _hashed_label_report(context: HashedLabelContext, chain_id: int, labels: dict[str, str]) -> str: - """The gist bullet for one hashed label.""" - lines = [ - f'- **`{context.argument_hex}`** = `keccak256("{context.name}")` — {context.note}', - f" - Target: {address_link(context.target, chain_id, labels)}", - ] - if context.is_config_key: - value = "not readable" if context.current_value is None else _report_value(context, context.current_value) - lines.append(f" - Value stored on-chain right now: {value}") - lines.extend( - f" - Set by this transaction to: {_report_value(context, raw)}" for raw in context.proposed_values - ) - if context.current_usage is not None: - suffix = "" if context.unit else " (same units as the key)" - lines.append(f" - {context.usage_label} right now: {_report_value(context, context.current_usage)}{suffix}") - lines.extend(f" - {line}" for line in context.usage_lines()[1:]) - if context.usage_enforcement: - lines.append(f" - {context.usage_enforcement}") - return "\n".join(lines) - - def reset_cache() -> None: """Reset process caches for tests or long-running workers.""" reset_abi_cache() diff --git a/utils/llm/timelock_execution_context.py b/utils/llm/timelock_execution_context.py index 282e2950..a1ede6ee 100644 --- a/utils/llm/timelock_execution_context.py +++ b/utils/llm/timelock_execution_context.py @@ -23,7 +23,6 @@ from utils.chains import Chain from utils.eth_view import call_view from utils.formatting import format_duration, format_utc -from utils.llm.report import address_link from utils.logger import get_logger from utils.source_context import get_contract_label from utils.web3_wrapper import ChainManager, Web3Client @@ -191,29 +190,3 @@ def resolve_timelock_execution_context( def format_timelock_execution_prompt(contexts: list[TimelockExecutionContext]) -> str: """Render verified timelock-operation facts for the LLM prompt.""" return "\n\n".join("\n".join(context.lines()) for context in contexts) - - -def format_timelock_execution_report( - contexts: list[TimelockExecutionContext], - chain_id: int, - labels: dict[str, str], -) -> str: - """Render the deterministic timelock-operation section for the gist report.""" - sections: list[str] = [] - for context in contexts: - route = ( - f"{address_link(context.target, chain_id, labels)} → {address_link(context.timelock, chain_id, labels)}" - if context.via_executor - else address_link(context.timelock, chain_id, labels) - ) - sections.append( - "\n".join( - [ - f"**Timelock execution:** `{context.signature.split('(')[0]}` via {route}", - f"- **Operation ID:** `{context.operation_id}` ({context.call_count} call(s))", - f"- **Min delay:** {format_duration(context.min_delay)}", - f"- **Status:** {context.status()}", - ] - ) - ) - return "\n\n".join(sections) diff --git a/utils/llm/yearn_auction_context.py b/utils/llm/yearn_auction_context.py index 22675f4d..7e1f676c 100644 --- a/utils/llm/yearn_auction_context.py +++ b/utils/llm/yearn_auction_context.py @@ -37,7 +37,6 @@ from utils.eth_view import call_view from utils.formatting import format_asset_amount, format_decimal_amount, format_duration, format_utc from utils.formatting import normalize_token_amount as normalize -from utils.llm.report import address_link from utils.logger import get_logger from utils.source_context import ( extract_short_function_body, @@ -972,23 +971,3 @@ def render(address: str, labels: dict[str, str] = context.labels) -> str: ] sections.append("\n".join(lines)) return "\n\n".join(sections) - - -def format_yearn_auction_report( - contexts: list[YearnAuctionContext], - chain_id: int, - labels: dict[str, str], -) -> str: - """Render the deterministic auction section for the gist report; mechanics are stated once.""" - sections: list[str] = [f"_{AUCTION_MECHANICS}_"] if contexts else [] - for context in contexts: - merged = {**context.labels, **labels} - - def render(address: str, merged: dict[str, str] = merged) -> str: - return address_link(address, chain_id, merged) - - header, *rest = context.header_lines(render) - lines = [f"**{header}**", *(f"- {line}" for line in rest), "- **Proposed:**"] - lines.extend(f" - {line}" for line in context.proposal_lines(render)) - sections.append("\n".join(lines)) - return "\n\n".join(sections) diff --git a/utils/llm/yearn_strategy_context.py b/utils/llm/yearn_strategy_context.py index e6612b76..0cba52d9 100644 --- a/utils/llm/yearn_strategy_context.py +++ b/utils/llm/yearn_strategy_context.py @@ -32,7 +32,6 @@ from utils.erc20_metadata import fetch_erc20_metadata from utils.eth_view import call_view from utils.formatting import format_asset_amount, format_duration, format_utc -from utils.llm.report import address_link from utils.logger import get_logger from utils.source_context import get_contract_label from utils.web3_wrapper import ChainManager, Web3Client @@ -429,24 +428,3 @@ def render(address: str, labels: dict[str, str] = context.labels) -> str: sections.append("\n".join(lines)) sections.extend(_shared_notes(contexts)) return "\n\n".join(sections) - - -def format_yearn_strategy_report( - contexts: list[YearnStrategyContext], - chain_id: int, - labels: dict[str, str], -) -> str: - """Render the deterministic tokenized-strategy section for the gist report.""" - sections: list[str] = [] - for context in contexts: - merged = {**context.labels, **labels} - - def render(address: str, merged: dict[str, str] = merged) -> str: - return address_link(address, chain_id, merged) - - header, settings = context.header_lines(render) - lines = [f"**{header}**", f"- {settings}", "- **Proposed:**"] - lines.extend(f" - {line}" for line in context.proposal_lines(render)) - sections.append("\n".join(lines)) - sections.extend(_shared_notes(contexts)) - return "\n\n".join(sections) diff --git a/utils/llm/yearn_v3_context.py b/utils/llm/yearn_v3_context.py index c80c8eb8..ee63509f 100644 --- a/utils/llm/yearn_v3_context.py +++ b/utils/llm/yearn_v3_context.py @@ -37,7 +37,6 @@ from utils.erc20_metadata import fetch_erc20_metadata from utils.eth_view import call_view from utils.formatting import format_asset_amount -from utils.llm.report import address_link from utils.llm.yearn_v3_batch import DebtMove, VaultBatchState from utils.logger import get_logger from utils.web3_wrapper import ChainManager @@ -1179,37 +1178,3 @@ def format_yearn_v3_prompt(contexts: list[YearnV3VaultContext]) -> str: ] sections.append("\n".join(lines)) return "\n\n".join(sections) - - -def format_yearn_v3_report( - contexts: list[YearnV3VaultContext], - chain_id: int, - labels: dict[str, str], -) -> str: - """Render the deterministic Yearn V3 section for the gist report.""" - sections: list[str] = [] - for context in contexts: - lines = [ - f"**Yearn V3 vault:** {address_link(context.vault_address, chain_id, labels)} — API " - f"`{context.api_version}`, asset `{context.asset_symbol}` ({context.asset_decimals} decimals)", - f"- **Size:** `{context.amount(context.total_assets)}` total assets " - f"(`{context.amount(context.total_debt)}` deployed, `{context.amount(context.total_idle)}` idle)", - f"- **Deposit limit:** `{context.optional_amount(context.deposit_limit)}` · " - f"**use_default_queue:** `{context.use_default_queue}` · **shutdown:** `{str(context.is_shutdown).lower()}`", - f"- **Default queue** ({len(context.default_queue)}/{MAX_QUEUE}, current_debt / max_debt):", - ] - lines.extend( - f" {i}. {address_link(s.address, chain_id, labels)} — `{context.amount(s.current_debt)}` / " - f"`{context.amount(s.max_debt)}`" - for i, s in enumerate(context.default_queue, start=1) - ) - lines.extend(f"- {line}" for line in _allocator_lines(context, lambda a: address_link(a, chain_id, labels))) - lines.append("- **Proposed:**") - lines.extend(f" - {line}" for line in context.proposal_lines()) - lines.extend(f"- {line}" for line in context.outcome_lines) - lines.append( - "- _Units: `max_debt`, `target_debt`, `deposit_limit` and `minimum_total_idle` are in the vault asset. " - "Funds move only via `update_debt`._" - ) - sections.append("\n".join(lines)) - return "\n\n".join(sections)