Skip to content

fix: include oz_governor proposer in transitive sender resolution#62

Open
nvtaveras wants to merge 2 commits into
mainfrom
fix/missingGovernorSender
Open

fix: include oz_governor proposer in transitive sender resolution#62
nvtaveras wants to merge 2 commits into
mainfrom
fix/missingGovernorSender

Conversation

@nvtaveras
Copy link
Copy Markdown
Contributor

@nvtaveras nvtaveras commented Mar 20, 2026

Summary

  • Bug: When a script uses an oz_governor sender (e.g. @custom:senders deployer, governor), the governor's proposer account (e.g. dev-pk) was not included in the encoded SENDER_CONFIGS passed to Solidity. This caused OZGovernorSender.initialize() to revert with InvalidOZGovernorConfig because it couldn't find the proposer in the sender registry.
  • Root cause: BuildSenderScriptConfig resolved transitive sender dependencies for safe senders (including their signer), but did not do the same for oz_governor senders and their proposer field.
  • Fix: Extended the transitive dependency resolution to also handle oz_governor senders by including their proposer, using the same pattern already in place for safe signers.

Details

The error manifested as:

execution reverted: custom error 0x8fede2fc: governor

This is InvalidOZGovernorConfig("governor") from OZGovernorSender.sol:112.

The flow:

  1. CLI reads @custom:senders deployer, governor from the script's natspec
  2. CLI resolves each sender through the namespace config and builds SenderInitConfig structs
  3. CLI ABI-encodes these into the SENDER_CONFIGS env var
  4. Solidity deserializes SENDER_CONFIGS and registers each sender by keccak256(name) in the Senders registry
  5. OZGovernorSender.initialize() decodes the governor's config to find proposerName = "dev-pk", then calls Senders.get(keccak256("dev-pk")) to look up the proposer
  6. dev-pk was never registered — only deployer was (which uses the same underlying private key but is registered under a different name)
  7. The lookup returns an uninitialized slot with senderType = 0x0, which fails the type validation → revert

The fix adds oz_governor to the same switch block that already handles safe signers, so dev-pk (or whatever the proposer is) gets included as a transitive dependency.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed sender dependency resolution to collect all transitive dependencies instead of only direct Safe signers, improving sender configuration completeness
    • Enhanced hardware wallet setup to properly incorporate governor proposer references in addition to signer references
    • Improved sender initialization to ensure all resolved transitive dependencies are correctly included and sorted in the final script configuration

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 20, 2026

📝 Walkthrough

Walkthrough

Updated BuildSenderScriptConfig to collect transitive sender dependencies from configured @custom:senders, gathering proposer references for oz_governor senders alongside safe signer references. These dependencies are now used when computing hardware-wallet configuration and prepended to declared script senders during initialization config encoding.

Changes

Cohort / File(s) Summary
Sender Configuration
internal/config/senders.go
Modified BuildSenderScriptConfig to collect transitive sender dependencies (both safe signers and oz_governor proposers) and apply them to hardware-wallet configuration computation and sender init config encoding, replacing previous safe-signers-only behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hoppy times in config land,
Where dependencies now expand,
Safe signers, governors in tow,
Transitive paths help configs flow,
Senders dance in harmony grand!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: extending transitive sender dependency resolution to include oz_governor proposer references, which directly addresses the root cause of the bug described in the PR objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/missingGovernorSender
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

When an oz_governor sender references a proposer account (e.g. "dev-pk"),
that account must be included in SENDER_CONFIGS so the Solidity-side
registry can look it up during OZGovernorSender.initialize().

Previously, only safe senders had their transitive dependencies (signers)
resolved. The oz_governor proposer was missing, causing a revert with
InvalidOZGovernorConfig when the Solidity code tried to validate the
proposer sender type.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/config/senders.go (1)

91-92: ⚠️ Potential issue | 🟡 Minor

Pre-existing bug: Error message mentions "ledger" but validates Trezor.

The error message on line 92 says "can not use ledger" but the condition checks signersHWConfig.UseTrezor && executionHWConfig.UseTrezor. This is a copy-paste error from line 88.

Not introduced by this PR, but worth fixing while you're here:

Proposed fix
 	if signersHWConfig.UseTrezor && executionHWConfig.UseTrezor {
-		return nil, fmt.Errorf("can not use ledger in both main sender and safe signer, configure `@custom`:senders")
+		return nil, fmt.Errorf("can not use trezor in both main sender and safe signer, configure `@custom`:senders")
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/config/senders.go` around lines 91 - 92, The error message is
incorrect for the Trezor check: when signersHWConfig.UseTrezor &&
executionHWConfig.UseTrezor is true, update the fmt.Errorf message to mention
Trezor (not ledger) so it matches the condition; locate the check in the
function that uses signersHWConfig.UseTrezor and executionHWConfig.UseTrezor and
replace the string passed to fmt.Errorf with a descriptive message like "can not
use trezor in both main sender and safe signer, configure `@custom`:senders".
🧹 Nitpick comments (3)
internal/config/senders.go (3)

85-85: Consider renaming signersHWConfig for clarity.

Now that this variable covers both safe signers and governor proposers, a name like transitiveHWConfig or dependenciesHWConfig would better reflect its purpose.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/config/senders.go` at line 85, Rename the variable signersHWConfig
to a clearer name (e.g., transitiveHWConfig or dependenciesHWConfig) to reflect
that it covers both safe signers and governor proposers; update the declaration
that assigns m.getSendersHWConfig(transitiveSenders) and then rename every usage
of signersHWConfig in this file (and any nearby functions referencing it) to the
new identifier so references remain consistent (ensure getSendersHWConfig and
transitiveSenders identifiers remain unchanged).

95-97: Potential duplicate entries when a sender is both declared and a transitive dependency.

If a user declares @custom:senders safe0, signer0 where safe0.signer = "signer0", then signer0 appears in both senders and transitiveSenders, resulting in duplicates in allSenders. This causes duplicate entries in SenderInitConfigs.

Proposed fix to deduplicate
 	sort.Strings(transitiveSenders)
 	sort.Strings(senders)
-	allSenders := append(slices.Clone(transitiveSenders), senders...)
+	// Deduplicate: transitive senders that are already declared don't need to be prepended
+	seen := make(map[string]bool, len(senders))
+	for _, s := range senders {
+		seen[s] = true
+	}
+	allSenders := make([]string, 0, len(transitiveSenders)+len(senders))
+	for _, s := range transitiveSenders {
+		if !seen[s] {
+			allSenders = append(allSenders, s)
+		}
+	}
+	allSenders = append(allSenders, senders...)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/config/senders.go` around lines 95 - 97, The concatenation of
transitiveSenders and senders into allSenders can produce duplicates (e.g., when
an entry appears in both transitiveSenders and senders), which leads to
duplicate SenderInitConfigs; change the construction of allSenders (referencing
transitiveSenders, senders, allSenders, and resulting SenderInitConfigs) to
deduplicate entries—for example, iterate over slices.Clone(transitiveSenders)
then append only those senders from senders that are not already present (use a
temporary map[string]struct{} set to track seen names) so allSenders contains
each sender exactly once before downstream use.

77-82: Guard against empty transitive references before appending.

If a safe or oz_governor sender is misconfigured with an empty Signer or Proposer, this code appends an empty string to transitiveSenders. Later, buildSenderInitConfigs would fail with "sender '' not found in configuration", which is a confusing error message.

Consider validating non-empty before appending:

Proposed fix
 		switch sender.Type {
 		case "safe":
-			transitiveSenders = append(transitiveSenders, sender.Signer)
+			if sender.Signer != "" {
+				transitiveSenders = append(transitiveSenders, sender.Signer)
+			}
 		case "oz_governor":
-			transitiveSenders = append(transitiveSenders, sender.Proposer)
+			if sender.Proposer != "" {
+				transitiveSenders = append(transitiveSenders, sender.Proposer)
+			}
 		}

This allows the existing validation in buildSenderInitConfig (lines 252 and 341) to produce the appropriate error message when processing the parent sender.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/config/senders.go` around lines 77 - 82, The switch in senders.go is
appending potentially-empty transitive references which leads to confusing
errors later; update the switch handling in the function that builds
transitiveSenders so that before appending sender.Signer (for "safe") or
sender.Proposer (for "oz_governor") you check for non-empty (e.g., != "" or
len(...)>0) and only append when non-empty, otherwise skip (or collect a clearer
validation error earlier); ensure you touch the transitiveSenders variable and
keep buildSenderInitConfigs unchanged so later validation on the parent sender
produces the proper error message.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@internal/config/senders.go`:
- Around line 91-92: The error message is incorrect for the Trezor check: when
signersHWConfig.UseTrezor && executionHWConfig.UseTrezor is true, update the
fmt.Errorf message to mention Trezor (not ledger) so it matches the condition;
locate the check in the function that uses signersHWConfig.UseTrezor and
executionHWConfig.UseTrezor and replace the string passed to fmt.Errorf with a
descriptive message like "can not use trezor in both main sender and safe
signer, configure `@custom`:senders".

---

Nitpick comments:
In `@internal/config/senders.go`:
- Line 85: Rename the variable signersHWConfig to a clearer name (e.g.,
transitiveHWConfig or dependenciesHWConfig) to reflect that it covers both safe
signers and governor proposers; update the declaration that assigns
m.getSendersHWConfig(transitiveSenders) and then rename every usage of
signersHWConfig in this file (and any nearby functions referencing it) to the
new identifier so references remain consistent (ensure getSendersHWConfig and
transitiveSenders identifiers remain unchanged).
- Around line 95-97: The concatenation of transitiveSenders and senders into
allSenders can produce duplicates (e.g., when an entry appears in both
transitiveSenders and senders), which leads to duplicate SenderInitConfigs;
change the construction of allSenders (referencing transitiveSenders, senders,
allSenders, and resulting SenderInitConfigs) to deduplicate entries—for example,
iterate over slices.Clone(transitiveSenders) then append only those senders from
senders that are not already present (use a temporary map[string]struct{} set to
track seen names) so allSenders contains each sender exactly once before
downstream use.
- Around line 77-82: The switch in senders.go is appending potentially-empty
transitive references which leads to confusing errors later; update the switch
handling in the function that builds transitiveSenders so that before appending
sender.Signer (for "safe") or sender.Proposer (for "oz_governor") you check for
non-empty (e.g., != "" or len(...)>0) and only append when non-empty, otherwise
skip (or collect a clearer validation error earlier); ensure you touch the
transitiveSenders variable and keep buildSenderInitConfigs unchanged so later
validation on the parent sender produces the proper error message.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 29d3569c-c958-4d9a-b015-780333d83d61

📥 Commits

Reviewing files that changed from the base of the PR and between 8e396cb and 124c629.

📒 Files selected for processing (1)
  • internal/config/senders.go

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