Skip to content

Commit 810d03f

Browse files
authored
Merge branch 'main' into DEVSVCS-5269/enforce-rate-limit
2 parents fea951a + 66f81fd commit 810d03f

24 files changed

Lines changed: 275 additions & 185 deletions

cmd/secrets/common/handler.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,13 @@ type Handler struct {
100100
func NewHandler(execCtx context.Context, ctx *runtime.Context, secretsFilePath, secretsAuth string) (*Handler, error) {
101101
var pk *ecdsa.PrivateKey
102102
var err error
103-
if ctx.Settings.User.EthPrivateKey.IsSet() {
104-
pk, err = crypto.HexToECDSA(ctx.Settings.User.EthPrivateKey.Hex())
103+
if ethKey := ctx.Settings.User.PrivateKey(settings.EVM); ethKey != "" {
104+
pk, err = crypto.HexToECDSA(ethKey)
105105
if err != nil {
106106
return nil, fmt.Errorf("failed to decode the provided private key: %w", err)
107107
}
108108
} else {
109-
ctx.Logger.Debug().Msg("No EthPrivateKey found in settings; assuming a multisig request.")
110-
109+
ctx.Logger.Debug().Msg("No EVM private key found in settings; assuming a multisig request.")
111110
}
112111

113112
h := &Handler{

cmd/secrets/common/handler_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ func TestNewHandler_WorkflowRegistryClient(t *testing.T) {
404404
Logger: &logger,
405405
ClientFactory: cf,
406406
Settings: &settings.Settings{
407-
User: settings.UserSettings{EthPrivateKey: ""},
407+
User: settings.UserSettings{PrivateKeys: map[string]string{settings.EVM.Name: ""}},
408408
Workflow: settings.WorkflowSettings{},
409409
},
410410
EnvironmentSet: &environments.EnvironmentSet{GatewayURL: "http://localhost"},
@@ -446,7 +446,7 @@ func TestNewHandler_GatewayURL(t *testing.T) {
446446
Logger: &logger,
447447
ClientFactory: cf,
448448
Settings: &settings.Settings{
449-
User: settings.UserSettings{EthPrivateKey: ""},
449+
User: settings.UserSettings{PrivateKeys: map[string]string{settings.EVM.Name: ""}},
450450
Workflow: settings.WorkflowSettings{},
451451
},
452452
EnvironmentSet: &environments.EnvironmentSet{GatewayURL: "https://embedded.example.com/"},

cmd/workflow/activate/activate_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func TestNonInteractive_WithoutYes_ReturnsError(t *testing.T) {
2121
ctx := simulatedEnvironment.NewRuntimeContext()
2222
ctx.Settings = &settings.Settings{
2323
User: settings.UserSettings{
24-
EthPrivateKey: chainsim.TestPrivateKey,
24+
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
2525
},
2626
}
2727
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
@@ -48,7 +48,7 @@ func TestNonInteractive_WithYes_PassesGuard(t *testing.T) {
4848
ctx := simulatedEnvironment.NewRuntimeContext()
4949
ctx.Settings = &settings.Settings{
5050
User: settings.UserSettings{
51-
EthPrivateKey: chainsim.TestPrivateKey,
51+
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
5252
},
5353
}
5454
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
@@ -163,7 +163,7 @@ func TestWorkflowActivateCommand(t *testing.T) {
163163
ctx := simulatedEnvironment.NewRuntimeContext()
164164
ctx.Settings = &settings.Settings{
165165
User: settings.UserSettings{
166-
EthPrivateKey: chainsim.TestPrivateKey,
166+
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
167167
},
168168
}
169169
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA

cmd/workflow/delete/delete_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func TestNonInteractive_WithoutYes_ReturnsError(t *testing.T) {
2121
ctx := simulatedEnvironment.NewRuntimeContext()
2222
ctx.Settings = &settings.Settings{
2323
User: settings.UserSettings{
24-
EthPrivateKey: chainsim.TestPrivateKey,
24+
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
2525
},
2626
}
2727
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
@@ -47,7 +47,7 @@ func TestNonInteractive_WithYes_Proceeds(t *testing.T) {
4747
ctx := simulatedEnvironment.NewRuntimeContext()
4848
ctx.Settings = &settings.Settings{
4949
User: settings.UserSettings{
50-
EthPrivateKey: chainsim.TestPrivateKey,
50+
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
5151
},
5252
}
5353
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
@@ -134,7 +134,7 @@ func TestWorkflowDeleteCommand(t *testing.T) {
134134
ctx := simulatedEnvironment.NewRuntimeContext()
135135
ctx.Settings = &settings.Settings{
136136
User: settings.UserSettings{
137-
EthPrivateKey: chainsim.TestPrivateKey,
137+
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
138138
},
139139
}
140140
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA

cmd/workflow/hash/hash.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command {
5656
WorkflowName: s.Workflow.UserWorkflowSettings.WorkflowName,
5757
WorkflowPath: s.Workflow.WorkflowArtifactSettings.WorkflowPath,
5858
OwnerFromSettings: s.Workflow.UserWorkflowSettings.WorkflowOwnerAddress,
59-
PrivateKey: s.User.EthPrivateKey.Hex(),
59+
PrivateKey: s.User.PrivateKey(settings.EVM),
6060
SkipTypeChecks: v.GetBool(cmdcommon.SkipTypeChecksCLIFlag),
6161
RegistryType: registryType,
6262
DerivedOwner: runtimeContext.DerivedWorkflowOwner,

cmd/workflow/pause/pause_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func TestNonInteractive_WithoutYes_ReturnsError(t *testing.T) {
2121
ctx := simulatedEnvironment.NewRuntimeContext()
2222
ctx.Settings = &settings.Settings{
2323
User: settings.UserSettings{
24-
EthPrivateKey: chainsim.TestPrivateKey,
24+
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
2525
},
2626
}
2727
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
@@ -47,7 +47,7 @@ func TestNonInteractive_WithYes_PassesGuard(t *testing.T) {
4747
ctx := simulatedEnvironment.NewRuntimeContext()
4848
ctx.Settings = &settings.Settings{
4949
User: settings.UserSettings{
50-
EthPrivateKey: chainsim.TestPrivateKey,
50+
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
5151
},
5252
}
5353
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA
@@ -141,7 +141,7 @@ func TestWorkflowPauseCommand(t *testing.T) {
141141
ctx := simulatedEnvironment.NewRuntimeContext()
142142
ctx.Settings = &settings.Settings{
143143
User: settings.UserSettings{
144-
EthPrivateKey: chainsim.TestPrivateKey,
144+
PrivateKeys: map[string]string{settings.EVM.Name: chainsim.TestPrivateKey},
145145
},
146146
}
147147
ctx.Settings.Workflow.UserWorkflowSettings.WorkflowOwnerType = constants.WorkflowOwnerTypeEOA

cmd/workflow/simulate/chain/evm/capabilities.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"github.com/smartcontractkit/chainlink-common/pkg/logger"
1212
"github.com/smartcontractkit/chainlink/v2/core/capabilities"
1313
"github.com/smartcontractkit/chainlink/v2/core/capabilities/fakes"
14+
15+
"github.com/smartcontractkit/cre-cli/cmd/workflow/simulate/chain"
1416
)
1517

1618
// EVMChainCapabilities holds the EVM chain capability servers created for simulation.
@@ -29,7 +31,7 @@ func NewEVMChainCapabilities(
2931
forwarders map[uint64]string,
3032
privateKey *ecdsa.PrivateKey,
3133
dryRunChainWrite bool,
32-
limits EVMChainLimits,
34+
limits chain.Limits,
3335
) (*EVMChainCapabilities, error) {
3436
evmChains := make(map[uint64]*ManualEVMChain)
3537
for sel, client := range clients {
@@ -48,11 +50,7 @@ func NewEVMChainCapabilities(
4850
dryRunChainWrite,
4951
)
5052

51-
// Wrap with limits enforcement if limits are provided
52-
var evmCap evmserver.ClientCapability = evm
53-
if limits != nil {
54-
evmCap = NewLimitedEVMChain(evm, limits)
55-
}
53+
evmCap := NewLimitedEVMChain(evm, limits)
5654

5755
manualEVM := NewManualEVMChain(evmCap)
5856
evmServer := evmserver.NewClientServer(manualEVM)

cmd/workflow/simulate/chain/evm/chaintype.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ func (ct *EVMChainType) ResolveClients(v *viper.Viper) (chain.ResolvedChains, er
9494
}
9595

9696
for _, ec := range expChains {
97+
// Empty chain-type falls back to this chain type
98+
if ec.ChainType != "" && !strings.EqualFold(ec.ChainType, ct.Name()) {
99+
continue
100+
}
97101
if ec.ChainSelector == 0 {
98102
return chain.ResolvedChains{}, fmt.Errorf("experimental chain missing chain-selector")
99103
}
@@ -161,13 +165,9 @@ func (ct *EVMChainType) RegisterCapabilities(ctx context.Context, cfg chain.Capa
161165
// cfg.Limits is the generic chain.Limits contract. The EVM chain type
162166
// needs the wider EVMChainLimits contract (adds ChainWriteGasLimit). A
163167
// nil cfg.Limits disables enforcement entirely.
164-
var evmLimits EVMChainLimits
168+
var evmLimits chain.Limits
165169
if cfg.Limits != nil {
166-
el, ok := cfg.Limits.(EVMChainLimits)
167-
if !ok {
168-
return nil, fmt.Errorf("EVM chain type: limits value does not implement evm.EVMChainLimits (got %T)", cfg.Limits)
169-
}
170-
evmLimits = el
170+
evmLimits = ExtractLimits(cfg.Limits)
171171
}
172172

173173
evmCaps, err := NewEVMChainCapabilities(
@@ -254,12 +254,13 @@ func (ct *EVMChainType) RunHealthCheck(resolved chain.ResolvedChains) error {
254254
// is true, an invalid or default-sentinel key is a hard error. Otherwise a
255255
// sentinel key is used with a warning so non-broadcast simulations can run.
256256
func (ct *EVMChainType) ResolveKey(creSettings *settings.Settings, broadcast bool) (interface{}, error) {
257-
pk, err := crypto.HexToECDSA(creSettings.User.EthPrivateKey.Hex())
257+
pk, err := crypto.HexToECDSA(creSettings.User.PrivateKey(settings.EVM))
258258
if err != nil {
259259
// If the user explicitly set a key that looks like a hex string but is
260260
// malformed (wrong length, invalid chars), always error with guidance.
261261
// Skip placeholder values like DefaultEthPrivateKeyEnvPlaceholder from the default .env template.
262-
if creSettings.User.EthPrivateKey.IsSet() && isHexString(creSettings.User.EthPrivateKey.Hex()) {
262+
evmKey := creSettings.User.PrivateKey(settings.EVM)
263+
if evmKey != "" && isHexString(evmKey) {
263264
return nil, fmt.Errorf(
264265
"invalid private key: expected 64 hex characters (256 bits), got %d characters.\n\n"+
265266
"The CLI reads CRE_ETH_PRIVATE_KEY from your .env file or system environment.\n"+
@@ -268,7 +269,7 @@ func (ct *EVMChainType) ResolveKey(creSettings *settings.Settings, broadcast boo
268269
" • Pasted an Ethereum address (40 chars) instead of a private key (64 chars)\n"+
269270
" • Value has extra quotes — use CRE_ETH_PRIVATE_KEY=abc123... without wrapping quotes\n"+
270271
" • Key was truncated during copy-paste",
271-
len(creSettings.User.EthPrivateKey.Hex()))
272+
len(creSettings.User.PrivateKey(settings.EVM)))
272273
}
273274
if broadcast {
274275
return nil, fmt.Errorf(

cmd/workflow/simulate/chain/evm/chaintype_test.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ func TestEVMChainType_ResolveKey(t *testing.T) {
163163
for _, tt := range tests {
164164
t.Run(tt.name, func(t *testing.T) {
165165
ct := newEVMChainType()
166-
s := &settings.Settings{User: settings.UserSettings{EthPrivateKey: settings.EthPrivateKeyHex(tt.pk)}}
166+
s := &settings.Settings{User: settings.UserSettings{PrivateKeys: map[string]string{settings.EVM.Name: tt.pk}}}
167167

168168
var got interface{}
169169
var err error
@@ -369,3 +369,16 @@ func TestEVMChainType_CollectCLIInputs_DefaultsOnly(t *testing.T) {
369369
result := ct.CollectCLIInputs(v)
370370
assert.Empty(t, result)
371371
}
372+
373+
func TestEVMChainType_RegisterCapabilities_WrongPrivateKeyType(t *testing.T) {
374+
t.Parallel()
375+
ct := newEVMChainType()
376+
cfg := chain.CapabilityConfig{
377+
Clients: map[uint64]chain.ChainClient{},
378+
Forwarders: map[uint64]string{},
379+
PrivateKey: "not-an-ecdsa-key",
380+
}
381+
_, err := ct.RegisterCapabilities(context.Background(), cfg)
382+
require.Error(t, err)
383+
assert.Contains(t, err.Error(), "private key is not *ecdsa.PrivateKey")
384+
}

cmd/workflow/simulate/chain/evm/limited_capabilities.go

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,42 +13,30 @@ import (
1313
"github.com/smartcontractkit/cre-cli/cmd/workflow/simulate/chain"
1414
)
1515

16-
// EVMChainLimits is the EVM-scoped limit contract LimitedEVMChain enforces.
17-
// It extends chain.Limits with EVM-specific accessors (e.g. gas limit) so
18-
// non-EVM chain types cannot accidentally depend on EVM semantics.
19-
type EVMChainLimits interface {
20-
chain.Limits
21-
ChainWriteGasLimit() uint64
22-
}
23-
2416
// LimitedEVMChain wraps an evmserver.ClientCapability and enforces chain write
2517
// report size and gas limits.
2618
type LimitedEVMChain struct {
2719
inner evmserver.ClientCapability
28-
limits EVMChainLimits
20+
limits chain.Limits
2921
}
3022

3123
var _ evmserver.ClientCapability = (*LimitedEVMChain)(nil)
3224

33-
func NewLimitedEVMChain(inner evmserver.ClientCapability, limits EVMChainLimits) *LimitedEVMChain {
25+
func NewLimitedEVMChain(inner evmserver.ClientCapability, limits chain.Limits) *LimitedEVMChain {
3426
return &LimitedEVMChain{inner: inner, limits: limits}
3527
}
3628

3729
func (l *LimitedEVMChain) WriteReport(ctx context.Context, metadata commonCap.RequestMetadata, input *evmcappb.WriteReportRequest) (*commonCap.ResponseAndMetadata[*evmcappb.WriteReportReply], caperrors.Error) {
38-
// Check report size
39-
reportLimit := l.limits.ChainWriteReportSizeLimit()
40-
if reportLimit > 0 && input.Report != nil && len(input.Report.RawReport) > reportLimit {
30+
if l.limits.ReportSize > 0 && input.Report != nil && len(input.Report.RawReport) > l.limits.ReportSize {
4131
return nil, caperrors.NewPublicUserError(
42-
fmt.Errorf("simulation limit exceeded: chain write report size %d bytes exceeds limit of %d bytes", len(input.Report.RawReport), reportLimit),
32+
fmt.Errorf("simulation limit exceeded: chain write report size %d bytes exceeds limit of %d bytes", len(input.Report.RawReport), l.limits.ReportSize),
4333
caperrors.ResourceExhausted,
4434
)
4535
}
4636

47-
// Check gas limit
48-
gasLimit := l.limits.ChainWriteGasLimit()
49-
if gasLimit > 0 && input.GasConfig != nil && input.GasConfig.GasLimit > gasLimit {
37+
if l.limits.GasLimit > 0 && input.GasConfig != nil && input.GasConfig.GasLimit > l.limits.GasLimit {
5038
return nil, caperrors.NewPublicUserError(
51-
fmt.Errorf("simulation limit exceeded: EVM gas limit %d exceeds maximum of %d", input.GasConfig.GasLimit, gasLimit),
39+
fmt.Errorf("simulation limit exceeded: EVM gas limit %d exceeds maximum of %d", input.GasConfig.GasLimit, l.limits.GasLimit),
5240
caperrors.ResourceExhausted,
5341
)
5442
}

0 commit comments

Comments
 (0)