Skip to content

Commit 28d53d7

Browse files
1 parent 90a68d8 commit 28d53d7

3 files changed

Lines changed: 231 additions & 4 deletions

File tree

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ go 1.25.7
55
require (
66
github.com/Masterminds/semver/v3 v3.4.0
77
github.com/aptos-labs/aptos-go-sdk v1.12.0
8+
github.com/deckarep/golang-set/v2 v2.6.0
89
github.com/ethereum/go-ethereum v1.17.1
910
github.com/gagliardetto/solana-go v1.13.0
1011
github.com/smartcontractkit/ccip-owner-contracts v0.1.0
1112
github.com/smartcontractkit/chain-selectors v1.0.97
1213
github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260121163256-85accaf3d28d
1314
github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250912190424-fd2e35d7deb5
1415
github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356cc7
15-
github.com/smartcontractkit/chainlink-deployments-framework v0.98.0
16+
github.com/smartcontractkit/chainlink-deployments-framework v0.99.0
1617
github.com/smartcontractkit/chainlink-evm v0.3.3
1718
github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260421142741-9c7fbaf7c828
1819
github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0
@@ -73,7 +74,6 @@ require (
7374
github.com/creachadair/mds v0.13.4 // indirect
7475
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
7576
github.com/dchest/siphash v1.2.3 // indirect
76-
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
7777
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
7878
github.com/digital-asset/dazl-client/v8 v8.9.0 // indirect
7979
github.com/distribution/reference v0.6.0 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -744,8 +744,8 @@ github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356c
744744
github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356cc7/go.mod h1:HXgSKzmZ/bhSx8nHU7hHW6dR+BHSXkdcpFv2T8qJcS8=
745745
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg=
746746
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10/go.mod h1:oiDa54M0FwxevWwyAX773lwdWvFYYlYHHQV1LQ5HpWY=
747-
github.com/smartcontractkit/chainlink-deployments-framework v0.98.0 h1:Ov/KOEtubOHXX8oa9UtARhHmkQNCOIjWNt+Zi0AuzHM=
748-
github.com/smartcontractkit/chainlink-deployments-framework v0.98.0/go.mod h1:24dwRW1PYolrlxSth///ddG3auGqR+50xaJiXfUHhkg=
747+
github.com/smartcontractkit/chainlink-deployments-framework v0.99.0 h1:UmFIN63m3+qXB5sP3ZtNzoMS8iIPDxeDVzYnhFB/U2k=
748+
github.com/smartcontractkit/chainlink-deployments-framework v0.99.0/go.mod h1:h2R69nbkSMGUSYHrf1lbrchml1CdR1jP4t9HsBb0xdY=
749749
github.com/smartcontractkit/chainlink-evm v0.3.3 h1:JqwyJEtnNEUaoQQPoOBTT4sn2lpdIZHtf0Hr0M60YDw=
750750
github.com/smartcontractkit/chainlink-evm v0.3.3/go.mod h1:q0ZBvaoisNaqC8NcMYWNPTjee88nQktDEeJMQHq3hVI=
751751
github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260421142741-9c7fbaf7c828 h1:BmsFk/TSHL6dPPR86GTqgSrUXLSINNFC6cfpFRrQX+4=

pkg/contract/mcms/propose.go

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
package mcms
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"time"
8+
9+
mapset "github.com/deckarep/golang-set/v2"
10+
chain_selectors "github.com/smartcontractkit/chain-selectors"
11+
mcmslib "github.com/smartcontractkit/mcms"
12+
mcmschainwrappers "github.com/smartcontractkit/mcms/chainwrappers"
13+
mcmssdk "github.com/smartcontractkit/mcms/sdk"
14+
mcmssolanasdk "github.com/smartcontractkit/mcms/sdk/solana"
15+
"github.com/smartcontractkit/mcms/types"
16+
17+
"github.com/smartcontractkit/cld-changesets/pkg/family/solana"
18+
19+
cldf_adapters "github.com/smartcontractkit/chainlink-deployments-framework/chain/mcms/adapters"
20+
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
21+
cldfproposalutils "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils"
22+
)
23+
24+
const (
25+
DefaultValidUntil = 72 * time.Hour
26+
)
27+
28+
type ChainMetadata map[uint64]map[string]any
29+
30+
func (c *ChainMetadata) Set(chainSelector uint64, key string, value any) *ChainMetadata {
31+
_, exists := (*c)[chainSelector]
32+
if !exists {
33+
(*c)[chainSelector] = make(map[string]any)
34+
}
35+
36+
(*c)[chainSelector][key] = value
37+
38+
return c
39+
}
40+
41+
type BuildProposalOption func(*buildProposalOptions)
42+
43+
type buildProposalOptions struct {
44+
chainMetadata ChainMetadata
45+
}
46+
47+
func WithChainMetadata(chainMetadata ChainMetadata) BuildProposalOption {
48+
return func(opts *buildProposalOptions) {
49+
opts.chainMetadata = chainMetadata
50+
}
51+
}
52+
53+
// BuildProposalFromBatchesV2 uses the new MCMS library which replaces the implementation in BuildProposalFromBatches.
54+
func BuildProposalFromBatchesV2(
55+
e cldf.Environment,
56+
timelockAddressPerChain map[uint64]string,
57+
mcmsAddressPerChain map[uint64]string,
58+
inspectorPerChain map[uint64]mcmssdk.Inspector, // optional
59+
batches []types.BatchOperation,
60+
description string,
61+
mcmsCfg cldfproposalutils.TimelockConfig,
62+
opts ...BuildProposalOption,
63+
) (*mcmslib.TimelockProposal, error) {
64+
buildOptions := buildProposalOptions{}
65+
for _, opt := range opts {
66+
opt(&buildOptions)
67+
}
68+
69+
// default to schedule if not set, this is to be consistent with the old implementation
70+
// and to avoid breaking changes
71+
if mcmsCfg.MCMSAction == "" {
72+
mcmsCfg.MCMSAction = types.TimelockActionSchedule
73+
}
74+
if len(batches) == 0 {
75+
return nil, errors.New("no operations in batch")
76+
}
77+
78+
chains := mapset.NewSet[uint64]()
79+
for _, op := range batches {
80+
chains.Add(uint64(op.ChainSelector))
81+
}
82+
tlsPerChainID := make(map[types.ChainSelector]string)
83+
for chainID, tl := range timelockAddressPerChain {
84+
tlsPerChainID[types.ChainSelector(chainID)] = tl
85+
}
86+
mcmsMd, err := buildProposalMetadataV2(e, chains.ToSlice(), inspectorPerChain, mcmsAddressPerChain,
87+
mcmsCfg.MCMSAction, buildOptions.chainMetadata)
88+
if err != nil {
89+
return nil, err
90+
}
91+
92+
proposalDuration := DefaultValidUntil
93+
if mcmsCfg.ValidDuration != nil {
94+
proposalDuration = mcmsCfg.ValidDuration.Duration
95+
}
96+
validUntil := time.Now().Add(proposalDuration).Unix()
97+
98+
builder := mcmslib.NewTimelockProposalBuilder()
99+
builder.
100+
SetVersion("v1").
101+
SetAction(mcmsCfg.MCMSAction).
102+
//nolint:gosec // G115
103+
SetValidUntil(uint32(validUntil)).
104+
SetDescription(description).
105+
SetDelay(types.NewDuration(mcmsCfg.MinDelay)).
106+
SetOverridePreviousRoot(mcmsCfg.OverrideRoot).
107+
SetChainMetadata(mcmsMd).
108+
SetTimelockAddresses(tlsPerChainID).
109+
SetOperations(batches)
110+
111+
build, err := builder.Build()
112+
if err != nil {
113+
return nil, err
114+
}
115+
116+
return build, nil
117+
}
118+
119+
func buildProposalMetadataV2(
120+
env cldf.Environment,
121+
chainSelectors []uint64,
122+
inspectorPerChain map[uint64]mcmssdk.Inspector, // optional
123+
mcmAddresses map[uint64]string, // can be proposer, canceller or bypasser
124+
mcmsAction types.TimelockAction,
125+
additionalChainMetadata ChainMetadata,
126+
) (map[types.ChainSelector]types.ChainMetadata, error) {
127+
proposalChainMetadata := make(map[types.ChainSelector]types.ChainMetadata)
128+
129+
if len(additionalChainMetadata) == 0 {
130+
additionalChainMetadata = make(ChainMetadata)
131+
}
132+
133+
for _, selector := range chainSelectors {
134+
mcmAddress, ok := mcmAddresses[selector]
135+
if !ok {
136+
return nil, fmt.Errorf("missing mcm address for chain %d", selector)
137+
}
138+
139+
chainID := types.ChainSelector(selector)
140+
family, err := chain_selectors.GetSelectorFamily(selector)
141+
if err != nil {
142+
return nil, fmt.Errorf("failed to get family for chain %d: %w", selector, err)
143+
}
144+
145+
switch family {
146+
case chain_selectors.FamilySolana:
147+
solanaState, err := solana.GetState(env, selector)
148+
if err != nil {
149+
return nil, err
150+
}
151+
152+
var instanceSeed mcmssolanasdk.PDASeed
153+
switch mcmsAction {
154+
case types.TimelockActionSchedule:
155+
instanceSeed = mcmssolanasdk.PDASeed(solanaState.ProposerMcmSeed)
156+
case types.TimelockActionCancel:
157+
instanceSeed = mcmssolanasdk.PDASeed(solanaState.CancellerMcmSeed)
158+
case types.TimelockActionBypass:
159+
instanceSeed = mcmssolanasdk.PDASeed(solanaState.BypasserMcmSeed)
160+
default:
161+
return nil, fmt.Errorf("invalid MCMS action %s", mcmsAction)
162+
}
163+
164+
proposalChainMetadata[chainID], err = mcmssolanasdk.NewChainMetadata(
165+
0, // opCount is set later
166+
solanaState.McmProgram,
167+
instanceSeed,
168+
solanaState.ProposerAccessControllerAccount,
169+
solanaState.CancellerAccessControllerAccount,
170+
solanaState.BypasserAccessControllerAccount)
171+
if err != nil {
172+
return nil, fmt.Errorf("failed to create chain metadata: %w", err)
173+
}
174+
175+
case chain_selectors.FamilyAptos:
176+
role, err := cldfproposalutils.GetAptosRoleFromAction(mcmsAction)
177+
if err != nil {
178+
return nil, fmt.Errorf("failed to get role from action: %w", err)
179+
}
180+
additionalChainMetadata.Set(selector, "role", role)
181+
182+
proposalChainMetadata[chainID] = types.ChainMetadata{MCMAddress: mcmAddress}
183+
184+
default:
185+
proposalChainMetadata[chainID] = types.ChainMetadata{MCMAddress: mcmAddress}
186+
}
187+
}
188+
189+
if len(inspectorPerChain) == 0 {
190+
mcmsChains := cldf_adapters.Wrap(env.BlockChains)
191+
inspectors, err := mcmschainwrappers.BuildInspectors(&mcmsChains, proposalChainMetadata, mcmsAction)
192+
if err != nil {
193+
return nil, fmt.Errorf("failed to build inspectors: %w", err)
194+
}
195+
196+
inspectorPerChain = make(map[uint64]mcmssdk.Inspector)
197+
for selector, inspector := range inspectors {
198+
inspectorPerChain[uint64(selector)] = inspector
199+
}
200+
}
201+
202+
for selector, metadata := range proposalChainMetadata {
203+
inspector, ok := inspectorPerChain[uint64(selector)]
204+
if !ok {
205+
return nil, fmt.Errorf("failed to get inspector for chain %d", selector)
206+
}
207+
208+
opCount, err := inspector.GetOpCount(env.GetContext(), metadata.MCMAddress)
209+
if err != nil {
210+
return nil, fmt.Errorf("failed to get op count for chain %d: %w", selector, err)
211+
}
212+
metadata.StartingOpCount = opCount
213+
214+
additionalMetadata, exists := additionalChainMetadata[uint64(selector)]
215+
if exists {
216+
marshalledAdditionalMetadata, err := json.Marshal(additionalMetadata)
217+
if err != nil {
218+
return nil, fmt.Errorf("failed to marshal extra chain metadata for chain %d: %w", selector, err)
219+
}
220+
metadata.AdditionalFields = marshalledAdditionalMetadata
221+
}
222+
223+
proposalChainMetadata[selector] = metadata
224+
}
225+
226+
return proposalChainMetadata, nil
227+
}

0 commit comments

Comments
 (0)