Skip to content

Commit 2a4a443

Browse files
fix: make predecessors parameter optional (#297)
Predecessors can be optional and user should not be forced to provide an empty value if not needed on the API for `NewProposal` and `NewTimelockProposal`. This commit introduces a functional option for `predecessors` and making that config optional, so the api is cleaner. This change was created after a brief discussion with James. This is a breaking change, so open to recommendations if we want to do it or not ( do we care enough?) but better do it now than later before we have more users. This is our official first breaking change, this will be a major version bump.
1 parent b00e009 commit 2a4a443

8 files changed

+49
-27
lines changed

.changeset/hip-adults-sniff.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smartcontractkit/mcms": major
3+
---
4+
5+
fix: make predecessors parameter optional on NewProposal and NewTimelockProposal

docs/usage/building-proposals.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func main() {
3636
defer file.Close()
3737

3838
// Create the proposal from the JSON data
39-
proposal, err := mcms.NewProposal(file, []io.Reader{})
39+
proposal, err := mcms.NewProposal(file)
4040
if err != nil {
4141
log.Fatalf("Error creating proposal: %v", err)
4242
}
@@ -49,7 +49,9 @@ For the JSON structure of the proposal please check the [MCMS Proposal Format Do
4949

5050
### Build Proposal Given Staged but Non-Executed Predecessor Proposals
5151

52-
In scenarios where a proposal is generated with the assumption that multiple proposals are executed beforehand, you can enable proposals to be signed in parallel with a pre-determined execution order. This can be achieved by passing a list of files as the second argument in the Proposal constructor, as shown below:
52+
In scenarios where a proposal is generated with the assumption that multiple proposals are executed beforehand,
53+
you can enable proposals to be signed in parallel with a pre-determined execution order. This can be achieved
54+
by passing a list of files using the `WithPredecessors` functional option, as shown below:
5355

5456
```go
5557
package main
@@ -78,7 +80,7 @@ func main() {
7880
defer preFile.Close()
7981

8082
// Create the proposal from the JSON data
81-
proposal, err := mcms.NewProposal(file, []io.Reader{preFile})
83+
proposal, err := mcms.NewProposal(file, mcms.WithPredecessors([]io.Reader{preFile}))
8284
if err != nil {
8385
log.Fatalf("Error creating proposal: %v", err)
8486
}

e2e/ledger/ledger_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package ledger
55

66
import (
77
"context"
8-
"io"
98
"log"
109
"math/big"
1110
"os"
@@ -204,7 +203,7 @@ func (s *ManualLedgerSigningTestSuite) TestManualLedgerSigning() {
204203
}(file)
205204
s.Require().NoError(err)
206205

207-
proposal, err := mcms.NewProposal(file, []io.Reader{})
206+
proposal, err := mcms.NewProposal(file)
208207
s.Require().NoError(err, "Failed to parse proposal")
209208
s.T().Log("Proposal loaded successfully.")
210209
proposal.ChainMetadata[s.chainSelectorEVM] = types.ChainMetadata{

e2e/tests/evm/signing.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func (s *SigningTestSuite) TestReadAndSign() {
4949
}
5050
}(file)
5151
s.Require().NoError(err)
52-
proposal, err := mcms.NewProposal(file, []io.Reader{})
52+
proposal, err := mcms.NewProposal(file)
5353
s.Require().NoError(err)
5454
s.Require().NotNil(proposal)
5555
inspectors := map[mcmtypes.ChainSelector]sdk.Inspector{
@@ -81,7 +81,7 @@ func (s *SigningTestSuite) TestReadAndSign() {
8181
_, err = tmpFile.Seek(0, io.SeekStart)
8282
s.Require().NoError(err, "Failed to reset file pointer to the start")
8383

84-
writtenProposal, err := mcms.NewProposal(tmpFile, []io.Reader{})
84+
writtenProposal, err := mcms.NewProposal(tmpFile)
8585
s.Require().NoError(err)
8686

8787
// Validate the appended signature

proposal.go

+27-7
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func LoadProposal(proposalType types.ProposalKind, filePath string) (ProposalInt
4444
// Ensure the file is closed when done
4545
defer file.Close()
4646

47-
return NewProposal(file, []io.Reader{}) // TODO: inject predecessors
47+
return NewProposal(file)
4848
case types.KindTimelockProposal:
4949
// Open the file
5050
file, err := os.Open(filePath)
@@ -55,7 +55,7 @@ func LoadProposal(proposalType types.ProposalKind, filePath string) (ProposalInt
5555
// Ensure the file is closed when done
5656
defer file.Close()
5757

58-
return NewTimelockProposal(file, []io.Reader{}) // TODO: inject predecessors
58+
return NewTimelockProposal(file)
5959
default:
6060
return nil, errors.New("unknown proposal type")
6161
}
@@ -107,18 +107,38 @@ type Proposal struct {
107107

108108
var _ ProposalInterface = (*Proposal)(nil)
109109

110-
// NewProposal unmarshals data from the reader to JSON and returns a new Proposal.
111-
// The predecessors parameter is a list of readers that contain the predecessors
112-
// for the proposal for configuring operations counts, which makes the following
110+
type ProposalOption func(*proposalOptions)
111+
112+
type proposalOptions struct {
113+
predecessors []io.Reader
114+
}
115+
116+
// WithPredecessors is an option that allows the user to specify a list of
117+
// that contain the predecessors for the proposal for configuring operations counts, which makes the following
113118
// assumptions:
114119
// - The order of the predecessors array is the order in which the proposals are
115120
// intended to be executed.
116121
// - The op counts for the first proposal are meant to be the starting op for the
117122
// full set of proposals.
118123
// - The op counts for all other proposals except the first are ignored
119124
// - all proposals are configured correctly and need no additional modifications
120-
func NewProposal(reader io.Reader, predecessors []io.Reader) (*Proposal, error) {
121-
return newProposal[*Proposal](reader, predecessors)
125+
func WithPredecessors(predecessors []io.Reader) ProposalOption {
126+
return func(opts *proposalOptions) {
127+
opts.predecessors = predecessors
128+
if opts.predecessors == nil {
129+
opts.predecessors = []io.Reader{}
130+
}
131+
}
132+
}
133+
134+
// NewProposal unmarshals data from the reader to JSON and returns a new Proposal.
135+
func NewProposal(reader io.Reader, opts ...ProposalOption) (*Proposal, error) {
136+
options := &proposalOptions{}
137+
for _, opt := range opts {
138+
opt(options)
139+
}
140+
141+
return newProposal[*Proposal](reader, options.predecessors)
122142
}
123143

124144
// WriteProposal marshals the proposal to JSON and writes it to the provided writer.

proposal_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ func Test_NewProposal(t *testing.T) {
214214
givePredecessors = append(givePredecessors, strings.NewReader(p))
215215
}
216216

217-
fileProposal, err := NewProposal(give, givePredecessors)
217+
fileProposal, err := NewProposal(give, WithPredecessors(givePredecessors))
218218

219219
if tt.wantErr != "" {
220220
require.EqualError(t, err, tt.wantErr)

timelock_proposal.go

+7-11
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,13 @@ type TimelockProposal struct {
3131
var _ ProposalInterface = (*TimelockProposal)(nil)
3232

3333
// NewTimelockProposal unmarshal data from the reader to JSON and returns a new TimelockProposal.
34-
// The predecessors parameter is a list of readers that contain the predecessors
35-
// for the proposal for configuring operations counts, which makes the following
36-
// assumptions:
37-
// - The order of the predecessors array is the order in which the proposals are
38-
// intended to be executed.
39-
// - The op counts for the first proposal are meant to be the starting op for the
40-
// full set of proposals.
41-
// - The op counts for all other proposals except the first are ignored
42-
// - all proposals are configured correctly and need no additional modifications
43-
func NewTimelockProposal(r io.Reader, predecessors []io.Reader) (*TimelockProposal, error) {
44-
return newProposal[*TimelockProposal](r, predecessors)
34+
func NewTimelockProposal(r io.Reader, opts ...ProposalOption) (*TimelockProposal, error) {
35+
options := &proposalOptions{}
36+
for _, opt := range opts {
37+
opt(options)
38+
}
39+
40+
return newProposal[*TimelockProposal](r, options.predecessors)
4541
}
4642

4743
func WriteTimelockProposal(w io.Writer, p *TimelockProposal) error {

timelock_proposal_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ func Test_NewTimelockProposal(t *testing.T) {
285285
givePredecessors = append(givePredecessors, strings.NewReader(p))
286286
}
287287

288-
got, err := NewTimelockProposal(give, givePredecessors)
288+
got, err := NewTimelockProposal(give, WithPredecessors(givePredecessors))
289289

290290
if tt.wantErr != "" {
291291
require.EqualError(t, err, tt.wantErr)

0 commit comments

Comments
 (0)