diff --git a/protocols/timelock/README.md b/protocols/timelock/README.md index aa0d37bf..d40e3e9a 100644 --- a/protocols/timelock/README.md +++ b/protocols/timelock/README.md @@ -21,7 +21,7 @@ The script runs hourly via the [monitoring runner](../automation/jobs.yaml). Only `TimelockController` timelocks are checked. Compound-style queues expire after a grace period, and governor proposals have their own lifecycle. -Alerts show the timelock, schedule transaction link, readiness date, and call details. Operation IDs are omitted from the alert text. +Alerts show the timelock, full operation ID in inline code for copying into `getTimestamp(id)` or other state queries, schedule transaction link, readiness date, and call details. ## GraphQL Schema diff --git a/protocols/timelock/stale_operations.py b/protocols/timelock/stale_operations.py index 18686e4f..09bb68d2 100644 --- a/protocols/timelock/stale_operations.py +++ b/protocols/timelock/stale_operations.py @@ -205,6 +205,7 @@ def format_operation(operation: Operation, ready_at: int, now: int) -> str: ) prefix = ( f"*{operation.timelock.label}* (chain {chain_id}): {timelock}\n" + f"Operation ID: `{operation.operation_id}`\n" f"Scheduled {_date(operation.scheduled_at)} in {tx}\n" f"Ready since {_date(ready_at)} ({(now - ready_at) // DAY} days)\n" "Calls:\n" diff --git a/tests/test_timelock_stale_operations.py b/tests/test_timelock_stale_operations.py index 4bf1b1f4..92321286 100644 --- a/tests/test_timelock_stale_operations.py +++ b/tests/test_timelock_stale_operations.py @@ -94,6 +94,7 @@ def test_links_full_addresses_and_counts_days(self, decode: MagicMock) -> None: text, ) self.assertIn("(39 days)", text) + self.assertIn(f"Operation ID: `{_operation().operation_id}`\n", text) self.assertIn(f"[{RATE_MANAGER}](https://etherscan.io/address/{RATE_MANAGER}) `setRate(address,uint256)`", text) @patch("utils.calldata.decoder._resolve_signature_via_abi") @@ -109,7 +110,7 @@ def test_long_signatures_fit_with_an_explicit_omission_notice(self, resolve: Mag self.assertGreater(shown, 0) self.assertLess(shown, len(operation.calls)) self.assertIn(f"… and {len(operation.calls) - shown} more calls (see the schedule tx)", text) - self.assertNotIn(operation.operation_id, text) + self.assertIn(f"Operation ID: `{operation.operation_id}`\n", text) self.assertIn(operation.transaction_hash, text) @patch("utils.calldata.decoder._resolve_signature_via_abi") @@ -123,7 +124,7 @@ def test_a_single_oversized_call_is_omitted_without_breaking_markdown(self, reso self.assertLessEqual(len(message), stale_operations.MAX_MESSAGE_LENGTH) self.assertNotIn(signature, text) self.assertIn("… and 1 more calls (see the schedule tx)", text) - self.assertNotIn(operation.operation_id, text) + self.assertIn(f"Operation ID: `{operation.operation_id}`\n", text) self.assertIn(operation.transaction_hash, text) self.assertEqual(text.count("`") % 2, 0)