Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
19d82f9
feat: add EVM grant role changeset
ecPablo Jun 26, 2026
a1a5a65
fix: lint errors
ecPablo Jun 26, 2026
40f7ea7
fix: use generic string type instead of addresses to keep inputs chai…
ecPablo Jun 27, 2026
fd8efb4
fix: use generic string type instead of addresses to keep inputs chai…
ecPablo Jun 27, 2026
a340a0a
Merge branch 'ecpablo/grant-role-timelock' into ecpablo/grant-role-ti…
ecPablo Jun 27, 2026
196404f
feat: add solana grant role implementation sequence and operation
ecPablo Jun 27, 2026
e59d091
fix: address review comments
ecPablo Jun 29, 2026
8140ed3
fix: cleanup redundant funcs and us t.Context
ecPablo Jun 29, 2026
908e726
fix: add t.helper
ecPablo Jun 29, 2026
44030ea
Merge branch 'main' into ecpablo/grant-role-timelock
ecPablo Jun 29, 2026
a657cb5
Merge branch 'ecpablo/grant-role-timelock' into ecpablo/grant-role-ti…
ecPablo Jun 29, 2026
079d3e7
fix: test flakyness for solana program loading
ecPablo Jun 29, 2026
514e353
Merge branch 'ecpablo/grant-role-timelock' into ecpablo/grant-role-ti…
ecPablo Jun 29, 2026
3afd45e
fix: refactor roles helpers
ecPablo Jun 29, 2026
a3e9f1c
fix: refactor roles helpers unit tests
ecPablo Jun 29, 2026
9a17a52
fix: remove unused func
ecPablo Jun 29, 2026
05f961a
fix: cleanup tests code
ecPablo Jun 29, 2026
1fc1c45
fix: use map in validation funcs fdor role types
ecPablo Jun 29, 2026
8c69cb9
fix: restore gitignore
ecPablo Jun 29, 2026
b0c2d02
chore: bump mcms
ecPablo Jun 29, 2026
53d97d3
Merge branch 'main' into ecpablo/grant-role-timelock-solana
ecPablo Jun 30, 2026
9ad9c88
chore: bump mcms
ecPablo Jun 30, 2026
ca26c92
fix: merge conflicts
ecPablo Jun 30, 2026
97fb1f2
fix: linting errors
ecPablo Jun 30, 2026
9c67a8b
fix: address review comments
ecPablo Jun 30, 2026
633a7c4
Merge branch 'main' into ecpablo/grant-role-timelock-solana
ecPablo Jun 30, 2026
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
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ go 1.26.2

replace github.com/fbsobreira/gotron-sdk => github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.5-0.20251014120029-d73d15cc23f7

// TODO: remove once mcms lib is released
replace github.com/smartcontractkit/mcms => /Users/pablo/mcms

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will remove this once mcms lib is released with new solana implementation for set role


require (
github.com/Masterminds/semver/v3 v3.5.0
github.com/aptos-labs/aptos-go-sdk v1.13.0
Expand All @@ -16,13 +19,13 @@ require (
github.com/samber/lo v1.53.0
github.com/segmentio/ksuid v1.0.4
github.com/smartcontractkit/ccip-owner-contracts v0.1.0
github.com/smartcontractkit/chain-selectors v1.0.102
github.com/smartcontractkit/chain-selectors v1.0.103
github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc
github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260415165642-49f23e4d76cc
github.com/smartcontractkit/chainlink-deployments-framework v0.114.1
github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260421142741-9c7fbaf7c828
github.com/smartcontractkit/chainlink-protos/job-distributor v0.19.0
github.com/smartcontractkit/mcms v0.48.1-0.20260616002102-085d81f76b05
github.com/smartcontractkit/mcms v0.49.0
github.com/smartcontractkit/quarantine v0.0.0-20251203215908-fd0551c6adf9
github.com/smartcontractkit/wsrpc v0.8.5-0.20250502134807-c57d3d995945
github.com/spf13/cast v1.10.0
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -847,8 +847,8 @@ github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/smartcontractkit/ccip-owner-contracts v0.1.0 h1:GiBDtlx7539o7AKlDV+9LsA7vTMPv+0n7ClhSFnZFAk=
github.com/smartcontractkit/ccip-owner-contracts v0.1.0/go.mod h1:NnT6w4Kj42OFFXhSx99LvJZWPpMjmo4+CpDEWfw61xY=
github.com/smartcontractkit/chain-selectors v1.0.102 h1:qYP4+72HfvogCHR5ymwRFee36WH77514ZBj299SVCBA=
github.com/smartcontractkit/chain-selectors v1.0.102/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w=
github.com/smartcontractkit/chain-selectors v1.0.103 h1:PpvIinn1TIDT7nh/P5KLQunRk0Kp1IR6moP2IGvlP58=
github.com/smartcontractkit/chain-selectors v1.0.103/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w=
github.com/smartcontractkit/chainlink-aptos v0.0.0-20260430175646-295a7f9a1500 h1:045jrHCLI+MpeAyByJkyHbEjq0+aTPt04C7+sbsNNtw=
github.com/smartcontractkit/chainlink-aptos v0.0.0-20260430175646-295a7f9a1500/go.mod h1:zfE2R7887kiwXkGTHKPe5NBgwhFwIC3pnA2uAxrbvig=
github.com/smartcontractkit/chainlink-canton v0.0.0-20260615233851-4e78e7c23a58 h1:QT9lFZBf3bFsp7oJWLTQuUXW4FU5QXyJx2a2qZ40G6Q=
Expand Down Expand Up @@ -897,8 +897,6 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i
github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA=
github.com/smartcontractkit/libocr v0.0.0-20260403184524-b6409238958d h1:PvXor5Fjer7FIONSqYXbpd1LkA14hWrlAyxXzOrC9t8=
github.com/smartcontractkit/libocr v0.0.0-20260403184524-b6409238958d/go.mod h1:PLdNK6GlqfxIWXzziPkU7dCAVlVFeYkyyW7AQY0R+4Q=
github.com/smartcontractkit/mcms v0.48.1-0.20260616002102-085d81f76b05 h1:PVRKr9ra3ma9I+e1hWNqWnOwnYAzUMzZwPIzRDhAih4=
github.com/smartcontractkit/mcms v0.48.1-0.20260616002102-085d81f76b05/go.mod h1:O5OnKQjuY/4VIOVBTRfBECBuWBM/eKvDF5UDDae8Eyc=
github.com/smartcontractkit/quarantine v0.0.0-20251203215908-fd0551c6adf9 h1:MOEuXYogv+RStASb8dWsyescu/xkigSi/Sv45NEjV7A=
github.com/smartcontractkit/quarantine v0.0.0-20251203215908-fd0551c6adf9/go.mod h1:iwy4yWFuK+1JeoIRTaSOA9pl+8Kf//26zezxEXrAQEQ=
github.com/smartcontractkit/wsrpc v0.8.5-0.20250502134807-c57d3d995945 h1:zxcODLrFytOKmAd8ty8S/XK6WcIEJEgRBaL7sY/7l4Y=
Expand Down
9 changes: 9 additions & 0 deletions mcms/changesets/grant-role/all/wire.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Package all blank-imports built-in MCMS grant-role families and readers.
package all

import (
_ "github.com/smartcontractkit/cld-changesets/mcms/evm/grant-role"
_ "github.com/smartcontractkit/cld-changesets/mcms/evm/readers"
_ "github.com/smartcontractkit/cld-changesets/mcms/solana/grant-role"
_ "github.com/smartcontractkit/cld-changesets/mcms/solana/readers"
)
154 changes: 154 additions & 0 deletions mcms/changesets/grant-role/changeset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package grantrole

import (
"errors"
"fmt"
"slices"

"github.com/smartcontractkit/chainlink-deployments-framework/changeset/sequenceutils"
cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore"
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"

"github.com/smartcontractkit/cld-changesets/internal/maputil"
)

var _ cldf.ChangeSetV2[Input] = Changeset{}

// Changeset grants RBACTimelock roles across configured chains.
type Changeset struct{}

func (Changeset) VerifyPreconditions(env cldf.Environment, input Input) error {
if env.DataStore == nil {
return errors.New("datastore is required for grant-role")
}
if input.MCMS != nil {
if err := input.MCMS.Validate(); err != nil {
return fmt.Errorf("invalid MCMS timelock proposal input: %w", err)
}
}
if len(input.Cfg.GrantsByChain) == 0 {
return errors.New("no role grants provided")
}
if err := validateGrants(input.Cfg.GrantsByChain); err != nil {
return err
}

byFamily, err := groupByFamily(input)
if err != nil {
return err
}

families := make([]string, 0, len(byFamily))
for family := range byFamily {
families = append(families, family)
}
slices.Sort(families)

for _, family := range families {
if err := Registry.VerifyForFamily(family, env, byFamily[family]); err != nil {
return err
}
}

return nil
}

func (Changeset) Apply(env cldf.Environment, input Input) (cldf.ChangesetOutput, error) {
deps := Deps{
BlockChains: env.BlockChains,
DataStore: env.DataStore,
}

var agg sequenceutils.OnChainOutput
for _, chainSelector := range maputil.SortedMapKeys(input.Cfg.GrantsByChain) {
grants := input.Cfg.GrantsByChain[chainSelector]

seq, seqErr := Registry.SequenceForChainSelector(chainSelector)
if seqErr != nil {
return buildOutput(env, input.MCMS, agg, fmt.Errorf("chain selector %d: %w", chainSelector, seqErr))
}

var mergeErr error
agg, mergeErr = sequenceutils.ExecuteOnChainSequenceAndMerge(
env.OperationsBundle,
deps,
seq,
SeqInput{
ChainSelector: chainSelector,
Grants: grants,
MCMS: input.MCMS,
GasBoostConfig: input.Cfg.GasBoostConfig,
},
agg,
)
if mergeErr != nil {
return buildOutput(env, input.MCMS, agg, mergeErr)
}
}

return buildOutput(env, input.MCMS, agg, nil)
}

func buildOutput(
env cldf.Environment,
mcmsInput *cldf.MCMSTimelockProposalInput,
agg sequenceutils.OnChainOutput,
err error,
) (cldf.ChangesetOutput, error) {
ds := cldfdatastore.NewMemoryDataStore()
if metaErr := ds.WriteMetadata(agg.Metadata); metaErr != nil {
return cldf.ChangesetOutput{DataStore: ds},
fmt.Errorf("write metadata to datastore: %w", metaErr)
}

partialOutput := cldf.ChangesetOutput{DataStore: ds}
if err != nil {
return partialOutput, err
}

builder := cldf.NewOutputBuilder(env, ds)
if mcmsInput != nil {
builder = builder.WithTimelockProposal(*mcmsInput, agg.BatchOps)
}

out, buildErr := builder.Build()
if buildErr != nil {
return out, fmt.Errorf("build changeset output: %w", buildErr)
}

if mcmsInput != nil && len(out.MCMSTimelockProposals) > 0 {
env.Logger.Infow("GrantRole proposal created", "proposalCount", len(out.MCMSTimelockProposals))
}

return out, nil
}

func validateGrants(grantsByChain map[uint64][]RoleGrant) error {
for chainSelector, grants := range grantsByChain {
if len(grants) == 0 {
return fmt.Errorf("chain %d: no role grants provided", chainSelector)
}
seen := make(map[string]struct{})
for grantIdx, grant := range grants {
if !grant.Role.Valid() {
return fmt.Errorf("chain %d grants[%d]: unsupported timelock role %s", chainSelector, grantIdx, grant.Role.String())
}
if len(grant.Addresses) == 0 {
return fmt.Errorf("chain %d grants[%d]: no addresses provided", chainSelector, grantIdx)
}
for addrIdx, addr := range grant.Addresses {
if addr == "" {
return fmt.Errorf("chain %d grants[%d].addresses[%d]: address must not be empty", chainSelector, grantIdx, addrIdx)
}
key := grant.Role.String() + ":" + addr
if _, ok := seen[key]; ok {
return fmt.Errorf("chain %d grants[%d].addresses[%d]: duplicate grant for role %s and address %s",
chainSelector, grantIdx, addrIdx, grant.Role.String(), addr)
}
seen[key] = struct{}{}
}
}
}

return nil
}
Loading