Skip to content
This repository was archived by the owner on Feb 8, 2026. It is now read-only.

payroll: propose and validated transactions based on recipes#100

Merged
RaghavSood merged 3 commits into
mainfrom
payroll/tx-proposal
Jun 24, 2025
Merged

payroll: propose and validated transactions based on recipes#100
RaghavSood merged 3 commits into
mainfrom
payroll/tx-proposal

Conversation

@RaghavSood

@RaghavSood RaghavSood commented Jun 22, 2025

Copy link
Copy Markdown
Contributor

This PR gets us to an MVP stage for recipe-based payroll transaction proposals.

It removes the legacy PayrollPolicy and transitions to solely using recipes for transaction construction and validation

It still has a few rough edges, and is missing many edge cases, but it is able to derive the recipient and amounts from the recipe, and use them to propose a valid transaction based on the vault address.

Summary by CodeRabbit

  • New Features
    • Added support for configuring an encryption secret in payroll server and worker settings.
    • Enhanced plugin initialization to support vault storage and encryption secret handling.
  • Improvements
    • Payroll transaction proposals now use a recipe-driven approach, enabling more flexible and extensible transaction creation and validation.
    • Transaction validation shifted to a recipe-based evaluation engine for improved policy enforcement.
  • Dependency Updates
    • Updated a core dependency to a newer version.
  • Chores
    • Removed the development script for creating payroll policies.

Copilot AI review requested due to automatic review settings June 22, 2025 22:41
@coderabbitai

coderabbitai Bot commented Jun 22, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This change introduces support for vault storage and encryption secrets in the payroll plugin, updating configuration structures, plugin initialization, and transaction proposal logic. The payroll policy and transaction handling are refactored to use a recipe-driven approach, replacing manual transaction decoding and recipient management with generalized rule-based evaluation and proposal.

Changes

File(s) Change Summary
cmd/payroll/server/config.go,
cmd/payroll/worker/config.go
Added EncryptionSecret field to PayrollServerConfig and PayrollWorkerConfig structs.
cmd/payroll/server/main.go,
cmd/payroll/worker/main.go
Updated payroll.NewPayrollPlugin invocation to include vault storage and encryption secret parameters.
plugin/payroll/payroll.go Added vaultStorage and encryptionSecret fields to PayrollPlugin; updated constructor to accept these parameters.
common/vault.go Added GetVaultFromPolicy function to retrieve and decrypt vaults using an encryption secret.
plugin/payroll/policy.go Removed custom payroll policy types and manual transaction decoding; refactored validation to use recipe-based evaluation engine.
plugin/payroll/transaction.go Refactored transaction proposal to use vault extraction, recipe rules, and chain abstractions; updated transaction generation signatures.
go.mod Updated github.com/vultisig/verifier dependency version.
scripts/dev/create_payroll_policy/main.go Deleted the development script for interactive payroll policy creation.

Sequence Diagram(s)

sequenceDiagram
    participant Server/Worker
    participant PayrollPlugin
    participant VaultStorage
    participant ChainRegistry
    participant Engine

    Server/Worker->>PayrollPlugin: NewPayrollPlugin(db, vaultStorage, ..., encryptionSecret)
    PayrollPlugin->>VaultStorage: Store reference, use encryptionSecret

    PayrollPlugin->>VaultStorage: GetVaultFromPolicy(policy, encryptionSecret)
    VaultStorage-->>PayrollPlugin: Decrypted vault

    PayrollPlugin->>ChainRegistry: Get chain by ID
    PayrollPlugin->>Engine: Evaluate transaction recipe rules
    Engine-->>PayrollPlugin: Evaluation result

    PayrollPlugin-->>Server/Worker: Proposed transactions or validation result
Loading

Possibly related PRs

Suggested reviewers

  • RaghavSood
  • johnnyluo

Poem

A vault now guards our payroll's core,
With secrets locked behind its door.
Recipes guide each payment's flight,
Chains and engines check what's right.
Old scripts retire, new flows begin—
Secure and clever, let the bunnies grin!
🐰🔒✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (1.64.8)

level=warning msg="[runner] Can't run linter goanalysis_metalinter: buildir: failed to load package session: could not load export data: no export data for "github.com/vultisig/go-wrappers/go-dkls/sessions""
level=error msg="Running error: can't run linter goanalysis_metalinter\nbuildir: failed to load package session: could not load export data: no export data for "github.com/vultisig/go-wrappers/go-dkls/sessions""

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR transitions the payroll processing functionality from using the legacy PayrollPolicy to a recipe‐based approach for constructing and validating transactions. Key changes include:

  • Removing the legacy create_payroll_policy script.
  • Refactoring transaction proposal logic to extract data from recipes.
  • Updating plugin policy validation and configuration to incorporate vault storage and encryption secret handling.

Reviewed Changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
scripts/dev/create_payroll_policy/main.go Removal of the legacy payroll policy script.
plugin/payroll/transaction.go Refactored transaction proposal logic to use recipe rules.
plugin/payroll/policy.go Updated plugin policy validation to derive and evaluate recipe data.
plugin/payroll/payroll.go Updated plugin initialization to include vault storage and secret.
go.mod Updated dependency version for the verifier module.
common/vault.go Added helper to retrieve and decrypt vault data from storage.
cmd/payroll/worker/* & cmd/payroll/server/* Updated worker and server initialization to pass vault and secret.
Comments suppressed due to low confidence (1)

plugin/payroll/transaction.go:250

  • [nitpick] Adding inline comments here to explain the conversion from a recipe rule to recipient and amount data would improve clarity and long-term maintainability.
		recipient, amountStr, err := RuleToRecipientAndAmount(rule)

i := _i
recipient := _recipient

for _, rule := range recipe.Rules {

Copilot AI Jun 22, 2025

Copy link

Choose a reason for hiding this comment

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

Consider whether failing the entire transaction proposal on a single rule parsing error is intended. If robustness is desired, you might aggregate errors or skip invalid rules instead of returning an error immediately.

Copilot uses AI. Check for mistakes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
plugin/payroll/payroll.go (1)

30-38: Consider adding validation for vault storage parameter.

The constructor validates the database parameter but not the vault storage. Consider adding similar validation for consistency.

 func NewPayrollPlugin(
 	db storage.DatabaseStorage,
 	vaultStorage vault.Storage,
 	baseConfigPath string,
 	txIndexerService *tx_indexer.Service,
 	client *asynq.Client,
 	inspector *asynq.Inspector,
 	encryptionSecret string,
 ) (*PayrollPlugin, error) {
 	if db == nil {
 		return nil, fmt.Errorf("database storage cannot be nil")
 	}
+	if vaultStorage == nil {
+		return nil, fmt.Errorf("vault storage cannot be nil")
+	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 938405f and 190a039.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (10)
  • cmd/payroll/server/config.go (1 hunks)
  • cmd/payroll/server/main.go (1 hunks)
  • cmd/payroll/worker/config.go (1 hunks)
  • cmd/payroll/worker/main.go (1 hunks)
  • common/vault.go (1 hunks)
  • go.mod (1 hunks)
  • plugin/payroll/payroll.go (3 hunks)
  • plugin/payroll/policy.go (4 hunks)
  • plugin/payroll/transaction.go (7 hunks)
  • scripts/dev/create_payroll_policy/main.go (0 hunks)
💤 Files with no reviewable changes (1)
  • scripts/dev/create_payroll_policy/main.go
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (10)
go.mod (1)

23-23: LGTM: Dependency update aligns with new vault functionality.

The verifier dependency update is consistent with the vault storage and encryption features being added to the payroll plugin.

cmd/payroll/worker/config.go (1)

25-25: LGTM: Configuration field added correctly.

The EncryptionSecret field is properly tagged and follows the established configuration pattern. Ensure this secret is handled securely in deployment (environment variables, secret management systems).

cmd/payroll/server/config.go (1)

26-26: LGTM: Consistent configuration field addition.

The EncryptionSecret field matches the worker configuration structure. Same security handling recommendations apply.

plugin/payroll/payroll.go (3)

12-12: LGTM: Import added for vault storage functionality.

The vault import is correctly added to support the new functionality.


26-27: LGTM: Plugin fields added appropriately.

The new fields follow Go naming conventions and are properly typed. The encryptionSecret will be stored in memory during plugin lifecycle, which is appropriate for this use case.


61-62: LGTM: Constructor properly assigns new fields.

The new fields are correctly assigned in the constructor.

cmd/payroll/worker/main.go (1)

84-84: LGTM: Constructor call updated correctly.

The plugin constructor call now properly passes the vault storage and encryption secret parameters, matching the updated signature in plugin/payroll/payroll.go.

cmd/payroll/server/main.go (1)

74-74: LGTM!

The plugin initialization correctly passes the vault storage and encryption secret parameters, aligning with the new vault integration requirements.

plugin/payroll/policy.go (1)

32-35: ```shell
#!/bin/bash

Locate all 'chain' package directories and inspect for registry and GetChain

echo "=== Chain package directories ==="
find . -type d -name chain

for DIR in $(find . -type d -name chain); do
echo
echo "=== Inspecting $DIR ==="
echo "-- Go files --"
find "$DIR" -maxdepth 1 -type f -name '*.go'

echo "-- Registry map definitions (map[string]Chain) --"
rg -C2 "map\[string\]Chain" "$DIR" || echo " No map[string]Chain found in $DIR"

echo "-- GetChain implementation --"
rg -C2 "^func GetChain" "$DIR" || echo " No 'func GetChain' declaration in $DIR"

echo "-- GetChain lookup logic --"
rg -C2 "GetChain\(" "$DIR" || echo " No 'GetChain(' pattern in $DIR"
done


</details>
<details>
<summary>plugin/payroll/transaction.go (1)</summary>

`373-432`: **Well-structured recipe specification!**

The `GetRecipeSpecification` method provides a comprehensive schema that clearly defines the plugin's capabilities and constraints. This recipe-driven approach significantly improves maintainability and flexibility compared to the previous hardcoded policy structure.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread common/vault.go
Comment on lines +12 to +24
func GetVaultFromPolicy(s vault.Storage, policy vtypes.PluginPolicy, encryptionSecret string) (*v1.Vault, error) {
vaultFileName := vcommon.GetVaultBackupFilename(policy.PublicKey, policy.PluginID.String())
vaultContent, err := s.GetVault(vaultFileName)
if err != nil {
return nil, fmt.Errorf("failed to get vault")
}

if vaultContent == nil {
return nil, fmt.Errorf("vault not found")
}

return vcommon.DecryptVaultFromBackup(encryptionSecret, vaultContent)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error messages to include more context.

The error messages should wrap the original errors and provide more context for better debugging.

Apply this diff to improve error handling:

 func GetVaultFromPolicy(s vault.Storage, policy vtypes.PluginPolicy, encryptionSecret string) (*v1.Vault, error) {
 	vaultFileName := vcommon.GetVaultBackupFilename(policy.PublicKey, policy.PluginID.String())
 	vaultContent, err := s.GetVault(vaultFileName)
 	if err != nil {
-		return nil, fmt.Errorf("failed to get vault")
+		return nil, fmt.Errorf("failed to get vault %s: %w", vaultFileName, err)
 	}
 
 	if vaultContent == nil {
-		return nil, fmt.Errorf("vault not found")
+		return nil, fmt.Errorf("vault not found for file %s", vaultFileName)
 	}
 
 	return vcommon.DecryptVaultFromBackup(encryptionSecret, vaultContent)
 }
🤖 Prompt for AI Agents
In common/vault.go around lines 12 to 24, the error messages returned when
failing to get the vault or when the vault is not found lack context and do not
include the original error details. Modify the error handling to wrap the
original error using fmt.Errorf with the %w verb and add descriptive context
messages, so the returned errors provide more information for debugging.

Comment thread plugin/payroll/policy.go
Comment on lines +23 to 26
recipe, err := policy.GetRecipe()
if err != nil {
return fmt.Errorf("failed to parse ABI: %v", err)
return fmt.Errorf("failed to get recipe from policy: %v", err)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Wrap the original error for better debugging.

The error message should include the original error from GetRecipe.

 recipe, err := policy.GetRecipe()
 if err != nil {
-	return fmt.Errorf("failed to get recipe from policy: %v", err)
+	return fmt.Errorf("failed to get recipe from policy: %w", err)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
recipe, err := policy.GetRecipe()
if err != nil {
return fmt.Errorf("failed to parse ABI: %v", err)
return fmt.Errorf("failed to get recipe from policy: %v", err)
}
recipe, err := policy.GetRecipe()
if err != nil {
return fmt.Errorf("failed to get recipe from policy: %w", err)
}
🤖 Prompt for AI Agents
In plugin/payroll/policy.go around lines 23 to 26, the error returned from
GetRecipe is formatted as a new error string without wrapping the original
error. Update the return statement to wrap the original error using fmt.Errorf
with the %w verb instead of %v, so the original error is preserved and can be
unwrapped for better debugging.

Comment on lines +222 to +228
echain, err := chain.GetChain("ethereum")
if err != nil {
return nil, fmt.Errorf("failed to get ethereum chain: %v", err)
}

ethchain := echain.(*reth.Ethereum)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add type assertion check to prevent panic.

The unchecked type assertion could cause a panic if the chain is not of the expected type.

Apply this diff to add proper type checking:

 echain, err := chain.GetChain("ethereum")
 if err != nil {
 	return nil, fmt.Errorf("failed to get ethereum chain: %v", err)
 }
 
-ethchain := echain.(*reth.Ethereum)
+ethchain, ok := echain.(*reth.Ethereum)
+if !ok {
+	return nil, fmt.Errorf("chain is not of type *reth.Ethereum")
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
echain, err := chain.GetChain("ethereum")
if err != nil {
return nil, fmt.Errorf("failed to get ethereum chain: %v", err)
}
ethchain := echain.(*reth.Ethereum)
echain, err := chain.GetChain("ethereum")
if err != nil {
return nil, fmt.Errorf("failed to get ethereum chain: %v", err)
}
ethchain, ok := echain.(*reth.Ethereum)
if !ok {
return nil, fmt.Errorf("chain is not of type *reth.Ethereum")
}
🤖 Prompt for AI Agents
In plugin/payroll/transaction.go around lines 222 to 228, the code performs an
unchecked type assertion on echain to *reth.Ethereum, which can cause a panic if
the type is incorrect. Modify the code to use the two-value form of type
assertion to check if echain is indeed of type *reth.Ethereum. If the assertion
fails, return an appropriate error instead of proceeding, preventing a runtime
panic.

Comment on lines +672 to +705
func RuleToRecipientAndAmount(rule *rtypes.Rule) (string, string, error) {
var recipient string
var amountStr string

if len(rule.ParameterConstraints) == 0 {
return "", "", fmt.Errorf("no parameter constraints found")
}

if len(rule.ParameterConstraints) > 2 {
return "", "", fmt.Errorf("too many parameter constraints found")
}

for _, constraint := range rule.ParameterConstraints {
if constraint.ParameterName == "recipient" {
if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
return "", "", fmt.Errorf("recipient constraint is not a fixed value")
}

fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
recipient = fixedValue.FixedValue
}

if constraint.ParameterName == "amount" {
if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
return "", "", fmt.Errorf("amount constraint is not a fixed value")
}

fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
amountStr = fixedValue.FixedValue
}
}

return recipient, amountStr, nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add safe type assertions to prevent runtime panics.

The function has multiple unchecked type assertions that could cause panics at runtime.

Apply this diff to add proper type checking:

 func RuleToRecipientAndAmount(rule *rtypes.Rule) (string, string, error) {
 	var recipient string
 	var amountStr string
 
 	if len(rule.ParameterConstraints) == 0 {
 		return "", "", fmt.Errorf("no parameter constraints found")
 	}
 
 	if len(rule.ParameterConstraints) > 2 {
 		return "", "", fmt.Errorf("too many parameter constraints found")
 	}
 
 	for _, constraint := range rule.ParameterConstraints {
 		if constraint.ParameterName == "recipient" {
 			if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
 				return "", "", fmt.Errorf("recipient constraint is not a fixed value")
 			}
 
-			fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
+			fixedValue, ok := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
+			if !ok {
+				return "", "", fmt.Errorf("failed to get fixed value for recipient")
+			}
 			recipient = fixedValue.FixedValue
 		}
 
 		if constraint.ParameterName == "amount" {
 			if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
 				return "", "", fmt.Errorf("amount constraint is not a fixed value")
 			}
 
-			fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
+			fixedValue, ok := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
+			if !ok {
+				return "", "", fmt.Errorf("failed to get fixed value for amount")
+			}
 			amountStr = fixedValue.FixedValue
 		}
 	}
 
 	return recipient, amountStr, nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func RuleToRecipientAndAmount(rule *rtypes.Rule) (string, string, error) {
var recipient string
var amountStr string
if len(rule.ParameterConstraints) == 0 {
return "", "", fmt.Errorf("no parameter constraints found")
}
if len(rule.ParameterConstraints) > 2 {
return "", "", fmt.Errorf("too many parameter constraints found")
}
for _, constraint := range rule.ParameterConstraints {
if constraint.ParameterName == "recipient" {
if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
return "", "", fmt.Errorf("recipient constraint is not a fixed value")
}
fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
recipient = fixedValue.FixedValue
}
if constraint.ParameterName == "amount" {
if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
return "", "", fmt.Errorf("amount constraint is not a fixed value")
}
fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
amountStr = fixedValue.FixedValue
}
}
return recipient, amountStr, nil
}
func RuleToRecipientAndAmount(rule *rtypes.Rule) (string, string, error) {
var recipient string
var amountStr string
if len(rule.ParameterConstraints) == 0 {
return "", "", fmt.Errorf("no parameter constraints found")
}
if len(rule.ParameterConstraints) > 2 {
return "", "", fmt.Errorf("too many parameter constraints found")
}
for _, constraint := range rule.ParameterConstraints {
if constraint.ParameterName == "recipient" {
if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
return "", "", fmt.Errorf("recipient constraint is not a fixed value")
}
fixedValue, ok := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
if !ok {
return "", "", fmt.Errorf("failed to get fixed value for recipient")
}
recipient = fixedValue.FixedValue
}
if constraint.ParameterName == "amount" {
if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
return "", "", fmt.Errorf("amount constraint is not a fixed value")
}
fixedValue, ok := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
if !ok {
return "", "", fmt.Errorf("failed to get fixed value for amount")
}
amountStr = fixedValue.FixedValue
}
}
return recipient, amountStr, nil
}
🤖 Prompt for AI Agents
In plugin/payroll/transaction.go lines 672 to 705, the function uses unchecked
type assertions on constraint.Constraint.GetValue() which can cause runtime
panics if the assertion fails. To fix this, replace the direct type assertions
with safe type assertions using the "comma ok" idiom to check if the assertion
succeeded. If the assertion fails, return an appropriate error instead of
proceeding. This ensures the function handles unexpected types gracefully
without panicking.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
plugin/payroll/transaction.go (2)

222-228: The unchecked type assertion issue remains unaddressed.

This is the same critical issue flagged in the past review comment. The code still performs an unchecked type assertion that could cause a runtime panic.

Apply this diff to add proper type checking:

 echain, err := chain.GetChain("ethereum")
 if err != nil {
 	return nil, fmt.Errorf("failed to get ethereum chain: %v", err)
 }
 
-ethchain := echain.(*reth.Ethereum)
+ethchain, ok := echain.(*reth.Ethereum)
+if !ok {
+	return nil, fmt.Errorf("chain is not of type *reth.Ethereum")
+}

669-702: The unchecked type assertion issue remains unaddressed.

This is the same critical issue flagged in the past review comment. The function still uses unchecked type assertions on lines 687 and 696 that could cause runtime panics.

Apply this diff to add proper type checking:

 func RuleToRecipientAndAmount(rule *rtypes.Rule) (string, string, error) {
 	var recipient string
 	var amountStr string
 
 	if len(rule.ParameterConstraints) == 0 {
 		return "", "", fmt.Errorf("no parameter constraints found")
 	}
 
 	if len(rule.ParameterConstraints) > 2 {
 		return "", "", fmt.Errorf("too many parameter constraints found")
 	}
 
 	for _, constraint := range rule.ParameterConstraints {
 		if constraint.ParameterName == "recipient" {
 			if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
 				return "", "", fmt.Errorf("recipient constraint is not a fixed value")
 			}
 
-			fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
+			fixedValue, ok := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
+			if !ok {
+				return "", "", fmt.Errorf("failed to get fixed value for recipient")
+			}
 			recipient = fixedValue.FixedValue
 		}
 
 		if constraint.ParameterName == "amount" {
 			if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
 				return "", "", fmt.Errorf("amount constraint is not a fixed value")
 			}
 
-			fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
+			fixedValue, ok := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
+			if !ok {
+				return "", "", fmt.Errorf("failed to get fixed value for amount")
+			}
 			amountStr = fixedValue.FixedValue
 		}
 	}
 
 	return recipient, amountStr, nil
 }
🧹 Nitpick comments (2)
plugin/payroll/transaction.go (2)

239-243: Consider aggregating resource parsing errors instead of failing immediately.

Similar to the past review comment on line 239, failing the entire transaction proposal on a single resource parsing error might not be ideal for robustness. Consider whether you want to aggregate errors or skip invalid rules.


677-679: Validate parameter constraint count more precisely.

The current validation allows 0-2 parameter constraints, but both recipient and amount are required according to the recipe specification. Consider enforcing exactly 2 constraints.

-	if len(rule.ParameterConstraints) > 2 {
-		return "", "", fmt.Errorf("too many parameter constraints found")
-	}
+	if len(rule.ParameterConstraints) != 2 {
+		return "", "", fmt.Errorf("exactly 2 parameter constraints required (recipient and amount), found %d", len(rule.ParameterConstraints))
+	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 190a039 and 1150df3.

📒 Files selected for processing (1)
  • plugin/payroll/transaction.go (7 hunks)
🔇 Additional comments (9)
plugin/payroll/transaction.go (9)

16-16: LGTM! Import changes support the recipe-driven refactor.

The new imports for common, chain, and rutil packages align with the transition to recipe-based transaction proposals.

Also applies to: 19-19, 21-21


207-210: LGTM! Good integration with vault storage.

The code correctly integrates with the vault storage system using the new common.GetVaultFromPolicy function.


212-215: LGTM! Proper address derivation from vault.

The Ethereum address derivation from the vault's public key and chain code is implemented correctly.


217-220: LGTM! Recipe extraction from policy.

The recipe retrieval from the plugin policy is straightforward and includes proper error handling.


245-248: LGTM! Token retrieval from chain.

The token lookup by protocol ID is correctly implemented with proper error handling.


250-253: LGTM! Rule processing integration.

The integration with the new RuleToRecipientAndAmount helper function is clean and includes proper error handling.


259-261: LGTM! Schedule time handling.

The schedule time conversion using AsTime() and interval casting is correctly implemented.


264-266: LGTM! Transition from public keys to addresses.

The changes from using public keys to direct addresses (recipient, token.Address) in the transaction proposal flow are consistent and correctly implemented throughout.

Also applies to: 272-276, 284-287, 298-301


434-434: LGTM! Function signature updates for address-based approach.

The function signature changes in genUnsignedTx and evmMakeUnsignedTransfer to use senderAddress instead of public keys are consistent with the new approach.

Also applies to: 441-441, 608-608, 639-639, 643-643

Comment on lines +681 to +702
for _, constraint := range rule.ParameterConstraints {
if constraint.ParameterName == "recipient" {
if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
return "", "", fmt.Errorf("recipient constraint is not a fixed value")
}

fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
recipient = fixedValue.FixedValue
}

if constraint.ParameterName == "amount" {
if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
return "", "", fmt.Errorf("amount constraint is not a fixed value")
}

fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
amountStr = fixedValue.FixedValue
}
}

return recipient, amountStr, nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation for missing required parameters.

The function should validate that both recipient and amount were found in the constraints to ensure completeness.

 	for _, constraint := range rule.ParameterConstraints {
 		// ... existing constraint processing ...
 	}
 
+	if recipient == "" {
+		return "", "", fmt.Errorf("recipient parameter constraint not found")
+	}
+	if amountStr == "" {
+		return "", "", fmt.Errorf("amount parameter constraint not found")
+	}
+
 	return recipient, amountStr, nil
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for _, constraint := range rule.ParameterConstraints {
if constraint.ParameterName == "recipient" {
if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
return "", "", fmt.Errorf("recipient constraint is not a fixed value")
}
fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
recipient = fixedValue.FixedValue
}
if constraint.ParameterName == "amount" {
if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
return "", "", fmt.Errorf("amount constraint is not a fixed value")
}
fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
amountStr = fixedValue.FixedValue
}
}
return recipient, amountStr, nil
}
for _, constraint := range rule.ParameterConstraints {
if constraint.ParameterName == "recipient" {
if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
return "", "", fmt.Errorf("recipient constraint is not a fixed value")
}
fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
recipient = fixedValue.FixedValue
}
if constraint.ParameterName == "amount" {
if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED {
return "", "", fmt.Errorf("amount constraint is not a fixed value")
}
fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue)
amountStr = fixedValue.FixedValue
}
}
if recipient == "" {
return "", "", fmt.Errorf("recipient parameter constraint not found")
}
if amountStr == "" {
return "", "", fmt.Errorf("amount parameter constraint not found")
}
return recipient, amountStr, nil
}
🤖 Prompt for AI Agents
In plugin/payroll/transaction.go around lines 681 to 702, the code extracts
recipient and amount from constraints but does not validate if both were found.
Add checks after the loop to verify that recipient and amountStr are not empty
or unset, and if either is missing, return an error indicating the missing
required parameter to ensure completeness of the data.

@webpiratt

webpiratt commented Jun 23, 2025

Copy link
Copy Markdown
Contributor

@RaghavSood Thanks for PR, looks great, please review my commit, I think that change fixes recipe rules list 1150df3

amount — I assume max or max_per_period is mistake, payroll is always fixed amount per period, likely that were copy-pasted from DCA;

token — I assume it must be fixed, not array, just some tokenID(contract address) for Payroll;

Comment thread plugin/payroll/transaction.go
Comment thread plugin/payroll/transaction.go

@webpiratt webpiratt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@RaghavSood
RaghavSood merged commit 99230a3 into main Jun 24, 2025
3 checks passed
@RaghavSood
RaghavSood deleted the payroll/tx-proposal branch June 24, 2025 08:47
@coderabbitai coderabbitai Bot mentioned this pull request Jul 2, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants