Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ go 1.25.7
require (
github.com/Masterminds/semver/v3 v3.4.0
github.com/aptos-labs/aptos-go-sdk v1.12.0
github.com/deckarep/golang-set/v2 v2.6.0
github.com/ethereum/go-ethereum v1.17.1
github.com/gagliardetto/solana-go v1.13.0
github.com/smartcontractkit/ccip-owner-contracts v0.1.0
github.com/smartcontractkit/chain-selectors v1.0.97
github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260121163256-85accaf3d28d
github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250912190424-fd2e35d7deb5
github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356cc7
github.com/smartcontractkit/chainlink-deployments-framework v0.98.0
github.com/smartcontractkit/chainlink-deployments-framework v0.99.0
github.com/smartcontractkit/chainlink-evm v0.3.3
github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260421142741-9c7fbaf7c828
github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0
Expand Down Expand Up @@ -73,7 +74,6 @@ require (
github.com/creachadair/mds v0.13.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dchest/siphash v1.2.3 // indirect
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/digital-asset/dazl-client/v8 v8.9.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -744,8 +744,8 @@ github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356c
github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356cc7/go.mod h1:HXgSKzmZ/bhSx8nHU7hHW6dR+BHSXkdcpFv2T8qJcS8=
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg=
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10/go.mod h1:oiDa54M0FwxevWwyAX773lwdWvFYYlYHHQV1LQ5HpWY=
github.com/smartcontractkit/chainlink-deployments-framework v0.98.0 h1:Ov/KOEtubOHXX8oa9UtARhHmkQNCOIjWNt+Zi0AuzHM=
github.com/smartcontractkit/chainlink-deployments-framework v0.98.0/go.mod h1:24dwRW1PYolrlxSth///ddG3auGqR+50xaJiXfUHhkg=
github.com/smartcontractkit/chainlink-deployments-framework v0.99.0 h1:UmFIN63m3+qXB5sP3ZtNzoMS8iIPDxeDVzYnhFB/U2k=
github.com/smartcontractkit/chainlink-deployments-framework v0.99.0/go.mod h1:h2R69nbkSMGUSYHrf1lbrchml1CdR1jP4t9HsBb0xdY=
github.com/smartcontractkit/chainlink-evm v0.3.3 h1:JqwyJEtnNEUaoQQPoOBTT4sn2lpdIZHtf0Hr0M60YDw=
github.com/smartcontractkit/chainlink-evm v0.3.3/go.mod h1:q0ZBvaoisNaqC8NcMYWNPTjee88nQktDEeJMQHq3hVI=
github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260421142741-9c7fbaf7c828 h1:BmsFk/TSHL6dPPR86GTqgSrUXLSINNFC6cfpFRrQX+4=
Expand Down
227 changes: 227 additions & 0 deletions pkg/contract/mcms/propose.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package mcms

import (
"encoding/json"
"errors"
"fmt"
"time"

mapset "github.com/deckarep/golang-set/v2"
chain_selectors "github.com/smartcontractkit/chain-selectors"
mcmslib "github.com/smartcontractkit/mcms"
mcmschainwrappers "github.com/smartcontractkit/mcms/chainwrappers"
mcmssdk "github.com/smartcontractkit/mcms/sdk"
mcmssolanasdk "github.com/smartcontractkit/mcms/sdk/solana"
"github.com/smartcontractkit/mcms/types"

"github.com/smartcontractkit/cld-changesets/pkg/family/solana"

cldf_adapters "github.com/smartcontractkit/chainlink-deployments-framework/chain/mcms/adapters"
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
cldfproposalutils "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils"
)

const (
DefaultValidUntil = 72 * time.Hour
)

type ChainMetadata map[uint64]map[string]any

func (c *ChainMetadata) Set(chainSelector uint64, key string, value any) *ChainMetadata {
_, exists := (*c)[chainSelector]
if !exists {
(*c)[chainSelector] = make(map[string]any)
}

(*c)[chainSelector][key] = value
Comment thread
graham-chainlink marked this conversation as resolved.

return c
}

type BuildProposalOption func(*buildProposalOptions)

type buildProposalOptions struct {
chainMetadata ChainMetadata
}

func WithChainMetadata(chainMetadata ChainMetadata) BuildProposalOption {
return func(opts *buildProposalOptions) {
opts.chainMetadata = chainMetadata
}
}

// BuildProposalFromBatchesV2 uses the new MCMS library which replaces the implementation in BuildProposalFromBatches.
func BuildProposalFromBatchesV2(
e cldf.Environment,
timelockAddressPerChain map[uint64]string,
mcmsAddressPerChain map[uint64]string,
inspectorPerChain map[uint64]mcmssdk.Inspector, // optional
batches []types.BatchOperation,
description string,
mcmsCfg cldfproposalutils.TimelockConfig,
opts ...BuildProposalOption,
) (*mcmslib.TimelockProposal, error) {
buildOptions := buildProposalOptions{}
for _, opt := range opts {
opt(&buildOptions)
}

// default to schedule if not set, this is to be consistent with the old implementation
// and to avoid breaking changes
if mcmsCfg.MCMSAction == "" {
mcmsCfg.MCMSAction = types.TimelockActionSchedule
}
if len(batches) == 0 {
return nil, errors.New("no operations in batch")
}

chains := mapset.NewSet[uint64]()
for _, op := range batches {
chains.Add(uint64(op.ChainSelector))
}
tlsPerChainID := make(map[types.ChainSelector]string)
for chainID, tl := range timelockAddressPerChain {
tlsPerChainID[types.ChainSelector(chainID)] = tl
}
mcmsMd, err := buildProposalMetadataV2(e, chains.ToSlice(), inspectorPerChain, mcmsAddressPerChain,
mcmsCfg.MCMSAction, buildOptions.chainMetadata)
if err != nil {
return nil, err
}

proposalDuration := DefaultValidUntil
if mcmsCfg.ValidDuration != nil {
proposalDuration = mcmsCfg.ValidDuration.Duration
}
validUntil := time.Now().Add(proposalDuration).Unix()

builder := mcmslib.NewTimelockProposalBuilder()
builder.
SetVersion("v1").
SetAction(mcmsCfg.MCMSAction).
//nolint:gosec // G115
SetValidUntil(uint32(validUntil)).
SetDescription(description).
Comment thread
graham-chainlink marked this conversation as resolved.
SetDelay(types.NewDuration(mcmsCfg.MinDelay)).
SetOverridePreviousRoot(mcmsCfg.OverrideRoot).
SetChainMetadata(mcmsMd).
SetTimelockAddresses(tlsPerChainID).
SetOperations(batches)

build, err := builder.Build()
if err != nil {
return nil, err
}

return build, nil
}

func buildProposalMetadataV2(
env cldf.Environment,
chainSelectors []uint64,
inspectorPerChain map[uint64]mcmssdk.Inspector, // optional
mcmAddresses map[uint64]string, // can be proposer, canceller or bypasser
mcmsAction types.TimelockAction,
additionalChainMetadata ChainMetadata,
) (map[types.ChainSelector]types.ChainMetadata, error) {
proposalChainMetadata := make(map[types.ChainSelector]types.ChainMetadata)

if len(additionalChainMetadata) == 0 {
additionalChainMetadata = make(ChainMetadata)
}

for _, selector := range chainSelectors {
mcmAddress, ok := mcmAddresses[selector]
if !ok {
return nil, fmt.Errorf("missing mcm address for chain %d", selector)
}

chainID := types.ChainSelector(selector)
family, err := chain_selectors.GetSelectorFamily(selector)
if err != nil {
return nil, fmt.Errorf("failed to get family for chain %d: %w", selector, err)
}

switch family {
case chain_selectors.FamilySolana:
solanaState, err := solana.GetState(env, selector)
if err != nil {
return nil, err
}

var instanceSeed mcmssolanasdk.PDASeed
switch mcmsAction {
case types.TimelockActionSchedule:
instanceSeed = mcmssolanasdk.PDASeed(solanaState.ProposerMcmSeed)
case types.TimelockActionCancel:
instanceSeed = mcmssolanasdk.PDASeed(solanaState.CancellerMcmSeed)
case types.TimelockActionBypass:
instanceSeed = mcmssolanasdk.PDASeed(solanaState.BypasserMcmSeed)
default:
return nil, fmt.Errorf("invalid MCMS action %s", mcmsAction)
}

proposalChainMetadata[chainID], err = mcmssolanasdk.NewChainMetadata(
0, // opCount is set later
solanaState.McmProgram,
instanceSeed,
solanaState.ProposerAccessControllerAccount,
solanaState.CancellerAccessControllerAccount,
solanaState.BypasserAccessControllerAccount)
if err != nil {
return nil, fmt.Errorf("failed to create chain metadata: %w", err)
}

case chain_selectors.FamilyAptos:
role, err := cldfproposalutils.GetAptosRoleFromAction(mcmsAction)
if err != nil {
return nil, fmt.Errorf("failed to get role from action: %w", err)
}
additionalChainMetadata.Set(selector, "role", role)

proposalChainMetadata[chainID] = types.ChainMetadata{MCMAddress: mcmAddress}
Comment thread
graham-chainlink marked this conversation as resolved.

default:
proposalChainMetadata[chainID] = types.ChainMetadata{MCMAddress: mcmAddress}
}
}

if len(inspectorPerChain) == 0 {
mcmsChains := cldf_adapters.Wrap(env.BlockChains)
inspectors, err := mcmschainwrappers.BuildInspectors(&mcmsChains, proposalChainMetadata, mcmsAction)
if err != nil {
return nil, fmt.Errorf("failed to build inspectors: %w", err)
}

inspectorPerChain = make(map[uint64]mcmssdk.Inspector)
for selector, inspector := range inspectors {
inspectorPerChain[uint64(selector)] = inspector
}
}

for selector, metadata := range proposalChainMetadata {
inspector, ok := inspectorPerChain[uint64(selector)]
if !ok {
return nil, fmt.Errorf("failed to get inspector for chain %d", selector)
}

opCount, err := inspector.GetOpCount(env.GetContext(), metadata.MCMAddress)
if err != nil {
return nil, fmt.Errorf("failed to get op count for chain %d: %w", selector, err)
}
metadata.StartingOpCount = opCount

additionalMetadata, exists := additionalChainMetadata[uint64(selector)]
if exists {
marshalledAdditionalMetadata, err := json.Marshal(additionalMetadata)
if err != nil {
return nil, fmt.Errorf("failed to marshal extra chain metadata for chain %d: %w", selector, err)
}
metadata.AdditionalFields = marshalledAdditionalMetadata
}

proposalChainMetadata[selector] = metadata
}

return proposalChainMetadata, nil
}
Loading