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

Commit 7cc0107

Browse files
author
Garry Sharp
committed
🐛 fee config bug fix and target added to tx fee validations
1 parent d80752a commit 7cc0107

2 files changed

Lines changed: 99 additions & 81 deletions

File tree

plugin/fees/config.go

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@ import (
1111

1212
// These are properties and parameters specific to the fee plugin config. They should be distinct from system/core config
1313
type FeeConfig struct {
14-
Type string `mapstructure:"type"`
15-
Version string `mapstructure:"version"`
16-
MaxFeeAmount uint64 `mapstructure:"max_fee_amount"` // Policies that are created/submitted which do not have this amount will be rejected.
17-
UsdcAddress string `mapstructure:"usdc_address"` // The address of the USDC token on the Ethereum blockchain.
18-
VerifierToken string `mapstructure:"verifier_token"` // The token to use for the verifier API.
19-
chainId uint64 `mapstructure:"chain_id"` // The chain ID of the Ethereum blockchain.
20-
ChainId *big.Int
21-
EthProvider string `mapstructure:"eth_provider"` // The Ethereum provider to use for the fee plugin.
14+
Type string `mapstructure:"type"`
15+
Version string `mapstructure:"version"`
16+
MaxFeeAmount uint64 `mapstructure:"max_fee_amount"` // Policies that are created/submitted which do not have this amount will be rejected.
17+
UsdcAddress string `mapstructure:"usdc_address"` // The address of the USDC token on the Ethereum blockchain.
18+
VerifierToken string `mapstructure:"verifier_token"` // The token to use for the verifier API.
19+
ChainId *big.Int // The chain ID as a big.Int (initialized from ChainIdRaw).
20+
EthProvider string `mapstructure:"eth_provider"` // The Ethereum provider to use for the fee plugin.
2221
Jobs struct {
2322
Load struct {
2423
MaxConcurrentJobs uint64 `mapstructure:"max_concurrent_jobs"` //How many consecutive tasks can take place
@@ -34,6 +33,12 @@ type FeeConfig struct {
3433
MaxConcurrentJobs uint64 `mapstructure:"max_concurrent_jobs"`
3534
} `mapstructure:"post"`
3635
}
36+
DryRun bool `mapstructure:"dry_run"`
37+
}
38+
39+
type FeeConfigFileWrapper struct {
40+
FeeConfig `mapstructure:",squash"`
41+
ChainIdRaw uint64 `mapstructure:"chain_id"`
3742
}
3843

3944
type ConfigOption func(*FeeConfig) error
@@ -52,6 +57,7 @@ func withDefaults(c *FeeConfig) {
5257
c.Jobs.Load.Cronexpr = "@every 2m"
5358
c.Jobs.Transact.Cronexpr = "0 12 * * 5"
5459
c.Jobs.Post.Cronexpr = "@every 5m"
60+
c.DryRun = false
5561
}
5662

5763
func WithMaxFeeAmount(maxFeeAmount uint64) ConfigOption {
@@ -100,9 +106,16 @@ func WithCronexpr(load, transact, post string) ConfigOption {
100106
}
101107
}
102108

103-
func WithFileConfig(basePath string) ConfigOption {
109+
func WithDryRun(dryRun bool) ConfigOption {
104110
return func(c *FeeConfig) error {
111+
c.DryRun = dryRun
112+
return nil
113+
}
114+
}
115+
116+
func WithFileConfig(basePath string) ConfigOption {
105117

118+
return func(c *FeeConfig) error {
106119
v := viper.New()
107120
v.SetConfigName("fee")
108121

@@ -122,11 +135,15 @@ func WithFileConfig(basePath string) ConfigOption {
122135
return fmt.Errorf("failed to read config: %w", err)
123136
}
124137

125-
if err := v.Unmarshal(c); err != nil {
138+
var wrappedConfig FeeConfigFileWrapper
139+
if err := v.Unmarshal(&wrappedConfig); err != nil {
126140
return fmt.Errorf("failed to unmarshal config: %w", err)
127141
}
128142

129-
c.ChainId = big.NewInt(0).SetUint64(c.chainId)
143+
// Copy all values from the wrapped config to the original config
144+
*c = wrappedConfig.FeeConfig
145+
146+
c.ChainId = big.NewInt(0).SetUint64(wrappedConfig.ChainIdRaw)
130147
return nil
131148
}
132149
}
@@ -149,8 +166,8 @@ func NewFeeConfig(fns ...ConfigOption) (*FeeConfig, error) {
149166
return c, errors.New("verifier_token is required")
150167
}
151168

152-
if c.ChainId == nil {
153-
return c, errors.New("chain_id is required")
169+
if c.ChainId == nil || c.ChainId.Uint64() == 0 {
170+
return c, errors.New("chain_id is required and must not be 0")
154171
}
155172

156173
if c.EthProvider == "" {

plugin/fees/transaction.go

Lines changed: 69 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -63,88 +63,89 @@ func (fp *FeePlugin) proposeTransactions(ctx context.Context, policy vtypes.Plug
6363

6464
txs := []vtypes.PluginKeysignRequest{}
6565
var magicConstantRecipientValue rtypes.MagicConstant = rtypes.MagicConstant_UNSPECIFIED
66-
var token string
66+
6767
// This should only return one rule, but in case there are more/fewer rules, we'll loop through them all and error if it's the case.
68-
for _, rule := range recipe.Rules {
69-
70-
// This section of code goes through the rules in the fee policy. It looks for the recipient of the fee collection policy and extracts it. If other data is found throws an error as they're unsupported rules.
71-
var recipient string // The address specified in the fee policy.
72-
switch rule.Resource {
73-
case "ethereum.erc20.transfer":
74-
for _, constraint := range rule.ParameterConstraints {
75-
if constraint.ParameterName == "recipient" {
76-
if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_MAGIC_CONSTANT {
77-
return nil, fmt.Errorf("recipient constraint is not a magic constant")
78-
}
79-
iv, err := strconv.ParseInt(constraint.Constraint.GetFixedValue(), 10, 64)
80-
if err != nil {
81-
return nil, fmt.Errorf("failed to parse fixed value: %v", err)
82-
}
83-
magicConstantRecipientValue = rtypes.MagicConstant(iv)
84-
}
68+
if len(recipe.Rules) != 1 {
69+
return nil, fmt.Errorf("expected 1 rule, got %d", len(recipe.Rules))
70+
}
71+
rule := recipe.Rules[0]
72+
73+
resourceName := "ethereum.erc20.transfer"
74+
if rule.Resource != resourceName {
75+
return nil, fmt.Errorf("rule resource expected to be ", resourceName)
76+
}
77+
78+
for _, constraint := range rule.ParameterConstraints {
79+
if constraint.ParameterName == "recipient" {
80+
if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_MAGIC_CONSTANT {
81+
return nil, fmt.Errorf("recipient constraint is not a magic constant")
82+
}
83+
iv, err := strconv.ParseInt(constraint.Constraint.GetFixedValue(), 10, 64)
84+
if err != nil {
85+
return nil, fmt.Errorf("failed to parse fixed value: %v", err)
8586
}
86-
default:
87-
return nil, fmt.Errorf("unsupported rule: %v", rule.Id)
87+
magicConstantRecipientValue = rtypes.MagicConstant(iv)
8888
}
89+
}
8990

90-
if magicConstantRecipientValue != rtypes.MagicConstant_VULTISIG_TREASURY {
91-
return nil, fmt.Errorf("recipient constraint is not a treasury magic constant")
92-
}
91+
if magicConstantRecipientValue != rtypes.MagicConstant_VULTISIG_TREASURY {
92+
return nil, fmt.Errorf("recipient constraint is not a treasury magic constant")
93+
}
9394

94-
treasuryResolver := resolver.NewDefaultTreasuryResolver()
95-
recipient, _, err := treasuryResolver.Resolve(magicConstantRecipientValue, "ethereum", "usdc")
96-
if err != nil {
97-
return nil, fmt.Errorf("failed to resolve treasury address: %v", err)
98-
}
95+
treasuryResolver := resolver.NewDefaultTreasuryResolver()
96+
recipient, _, err := treasuryResolver.Resolve(magicConstantRecipientValue, "ethereum", "usdc")
97+
if err != nil {
98+
return nil, fmt.Errorf("failed to resolve treasury address: %v", err)
99+
}
99100

100-
if gcommon.HexToAddress(token) != gcommon.HexToAddress(usdc.Address) {
101-
return nil, fmt.Errorf("token address does not match usdc address")
102-
}
101+
token := rule.Target.GetAddress()
103102

104-
amount := run.TotalAmount
103+
if gcommon.HexToAddress(token) != gcommon.HexToAddress(usdc.Address) {
104+
return nil, fmt.Errorf("token address does not match usdc address")
105+
}
105106

106-
tx, err := fp.eth.MakeAnyTransfer(ctx,
107-
gcommon.HexToAddress(ethAddress),
108-
gcommon.HexToAddress(recipient),
109-
gcommon.HexToAddress(usdc.Address),
110-
big.NewInt(int64(amount)))
111-
if err != nil {
112-
return nil, fmt.Errorf("failed to generate unsigned transaction: %w", err)
113-
}
107+
amount := run.TotalAmount
114108

115-
txHex := hexutil.Encode(tx)
109+
tx, err := fp.eth.MakeAnyTransfer(ctx,
110+
gcommon.HexToAddress(ethAddress),
111+
gcommon.HexToAddress(recipient),
112+
gcommon.HexToAddress(usdc.Address),
113+
big.NewInt(int64(amount)))
114+
if err != nil {
115+
return nil, fmt.Errorf("failed to generate unsigned transaction: %w", err)
116+
}
116117

117-
txData, e := reth.DecodeUnsignedPayload(tx)
118-
if e != nil {
119-
return nil, fmt.Errorf("ethereum.DecodeUnsignedPayload: %w", e)
120-
}
121-
txHashToSign := etypes.LatestSignerForChainID(fp.config.ChainId).Hash(etypes.NewTx(txData))
122-
123-
msgHash := sha256.Sum256(txHashToSign.Bytes())
124-
125-
signRequest := vtypes.PluginKeysignRequest{
126-
KeysignRequest: vtypes.KeysignRequest{
127-
PublicKey: policy.PublicKey,
128-
Messages: []vtypes.KeysignMessage{
129-
{
130-
Message: base64.StdEncoding.EncodeToString(txHashToSign.Bytes()),
131-
RawMessage: txHex,
132-
Chain: vcommon.Ethereum,
133-
Hash: base64.StdEncoding.EncodeToString(msgHash[:]),
134-
HashFunction: vtypes.HashFunction_SHA256,
135-
},
118+
txHex := hexutil.Encode(tx)
119+
120+
txData, e := reth.DecodeUnsignedPayload(tx)
121+
if e != nil {
122+
return nil, fmt.Errorf("ethereum.DecodeUnsignedPayload: %w", e)
123+
}
124+
125+
txHashToSign := etypes.LatestSignerForChainID(fp.config.ChainId).Hash(etypes.NewTx(txData))
126+
msgHash := sha256.Sum256(txHashToSign.Bytes())
127+
signRequest := vtypes.PluginKeysignRequest{
128+
KeysignRequest: vtypes.KeysignRequest{
129+
PublicKey: policy.PublicKey,
130+
Messages: []vtypes.KeysignMessage{
131+
{
132+
Message: base64.StdEncoding.EncodeToString(txHashToSign.Bytes()),
133+
RawMessage: txHex,
134+
Chain: vcommon.Ethereum,
135+
Hash: base64.StdEncoding.EncodeToString(msgHash[:]),
136+
HashFunction: vtypes.HashFunction_SHA256,
136137
},
137-
SessionID: "",
138-
HexEncryptionKey: "",
139-
PolicyID: policy.ID,
140-
PluginID: policy.PluginID.String(),
141138
},
142-
Transaction: txHex,
143-
}
144-
145-
txs = append(txs, signRequest)
139+
SessionID: "",
140+
HexEncryptionKey: "",
141+
PolicyID: policy.ID,
142+
PluginID: policy.PluginID.String(),
143+
},
144+
Transaction: txHex,
146145
}
147146

147+
txs = append(txs, signRequest)
148+
148149
return txs, nil
149150
}
150151

0 commit comments

Comments
 (0)