Skip to content
Draft
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
3 changes: 3 additions & 0 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 Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
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.49.0 h1:4Bav/bNsIc6pNlPhiNqYpvMyxDF9OpRgrWVtCa3BW+A=
github.com/smartcontractkit/mcms v0.49.0/go.mod h1:tzyPA51qtN5us/2DS3kBIY7OVWZXSVKPOrOcYcKuvZI=
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
2 changes: 2 additions & 0 deletions mcms/changesets/grant-role/all/wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ 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"
)
70 changes: 70 additions & 0 deletions mcms/changesets/grant-role/roles_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package grantrole

import (
"context"
"fmt"
"slices"

mcmssdk "github.com/smartcontractkit/mcms/sdk"
)

// AddressesForRole returns accounts that currently hold role on the timelock.
// Admin returns nil without querying the chain.
func AddressesForRole(
ctx context.Context,
inspector mcmssdk.TimelockInspector,
timelockAddress string,
role mcmssdk.TimelockRole,
) ([]string, error) {
switch role {
case mcmssdk.TimelockRoleProposer:
return inspector.GetProposers(ctx, timelockAddress)
case mcmssdk.TimelockRoleCanceller:
return inspector.GetCancellers(ctx, timelockAddress)
case mcmssdk.TimelockRoleBypasser:
return inspector.GetBypassers(ctx, timelockAddress)
case mcmssdk.TimelockRoleExecutor:
return inspector.GetExecutors(ctx, timelockAddress)
case mcmssdk.TimelockRoleAdmin:
return nil, nil
default:
return nil, fmt.Errorf("unsupported timelock role %s", role.String())
}
}

// AddressesNeedingGrant returns grant addresses that do not yet hold the role.
// normalize canonicalizes addresses for comparison; pass nil to compare raw strings.
func AddressesNeedingGrant(
ctx context.Context,
inspector mcmssdk.TimelockInspector,
timelockAddress string,
grant RoleGrant,
normalize func(string) string,
) ([]string, error) {
addressesWithRole, err := AddressesForRole(ctx, inspector, timelockAddress, grant.Role)
if err != nil {
return nil, err
}
if len(addressesWithRole) == 0 {
return grant.Addresses, nil
}

if normalize == nil {
normalize = func(address string) string { return address }
}

normalizedExisting := make([]string, len(addressesWithRole))
for i, address := range addressesWithRole {
normalizedExisting[i] = normalize(address)
}

out := make([]string, 0, len(grant.Addresses))
for _, address := range grant.Addresses {
if slices.Contains(normalizedExisting, normalize(address)) {
continue
}
out = append(out, address)
}

return out, nil
}
68 changes: 68 additions & 0 deletions mcms/changesets/grant-role/roles_helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package grantrole

import (
"testing"

"github.com/stretchr/testify/require"

mcmssdk "github.com/smartcontractkit/mcms/sdk"
mcmssdkmocks "github.com/smartcontractkit/mcms/sdk/mocks"
)

func TestAddressesForRole_unsupported(t *testing.T) {
t.Parallel()

inspector := mcmssdkmocks.NewTimelockInspector(t)

_, err := AddressesForRole(t.Context(), inspector, "timelock", mcmssdk.TimelockRole(99))
require.EqualError(t, err, "unsupported timelock role Unknown")
}

func TestAddressesForRole_admin(t *testing.T) {
t.Parallel()

inspector := mcmssdkmocks.NewTimelockInspector(t)

got, err := AddressesForRole(t.Context(), inspector, "timelock", mcmssdk.TimelockRoleAdmin)
require.NoError(t, err)
require.Nil(t, got)
}

func TestAddressesNeedingGrant(t *testing.T) {
t.Parallel()

ctx := t.Context()
timelock := "timelock"

inspector := mcmssdkmocks.NewTimelockInspector(t)
inspector.EXPECT().
GetCancellers(ctx, timelock).
Return([]string{"alice", "bob"}, nil)

got, err := AddressesNeedingGrant(
ctx,
inspector,
timelock,
RoleGrant{
Role: mcmssdk.TimelockRoleCanceller,
Addresses: []string{"alice", "carol"},
},
nil,
)
require.NoError(t, err)
require.Equal(t, []string{"carol"}, got)

adminInspector := mcmssdkmocks.NewTimelockInspector(t)
all, err := AddressesNeedingGrant(
ctx,
adminInspector,
timelock,
RoleGrant{
Role: mcmssdk.TimelockRoleAdmin,
Addresses: []string{"alice"},
},
nil,
)
require.NoError(t, err)
require.Equal(t, []string{"alice"}, all)
}
59 changes: 7 additions & 52 deletions mcms/evm/grant-role/sequence.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package evmgrantrole

import (
"fmt"
"slices"
"strconv"

"github.com/Masterminds/semver/v3"
Expand All @@ -13,7 +12,6 @@ import (
"github.com/smartcontractkit/chainlink-deployments-framework/changeset/sequenceutils"
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
"github.com/smartcontractkit/chainlink-deployments-framework/operations"
mcmssdk "github.com/smartcontractkit/mcms/sdk"
mcmsevm "github.com/smartcontractkit/mcms/sdk/evm"
mcmstypes "github.com/smartcontractkit/mcms/types"

Expand Down Expand Up @@ -47,7 +45,13 @@ func runEVMGrantRole(
var addresses []string

for _, grant := range in.Grants {
addresses, err = addressesNeedingGrant(b, chain, timelock, grant)
addresses, err = grantrole.AddressesNeedingGrant(
b.GetContext(),
mcmsevm.NewTimelockInspector(chain.Client),
timelock.Hex(),
grant,
func(address string) string { return common.HexToAddress(address).Hex() },
)
if err != nil {
return sequenceutils.OnChainOutput{}, err
}
Expand Down Expand Up @@ -117,55 +121,6 @@ func runEVMGrantRole(
return sequenceutils.OnChainOutput{BatchOps: []mcmstypes.BatchOperation{batch}}, nil
}

// addressesNeedingGrant returns the set of addresses that don't yet have the provided role.
func addressesNeedingGrant(
b operations.Bundle,
chain cldf_evm.Chain,
timelock common.Address,
grant grantrole.RoleGrant,
) ([]string, error) {
addressesWithRole, err := addressesForRole(b, chain, timelock, grant.Role)
if err != nil {
return nil, err
}
if len(addressesWithRole) == 0 {
return grant.Addresses, nil
}

out := make([]string, 0, len(grant.Addresses))
for _, address := range grant.Addresses {
if slices.Contains(addressesWithRole, common.HexToAddress(address).Hex()) {
continue
}
out = append(out, address)
}

return out, nil
}

func addressesForRole(
b operations.Bundle,
chain cldf_evm.Chain,
timelock common.Address,
role mcmssdk.TimelockRole,
) ([]string, error) {
inspector := mcmsevm.NewTimelockInspector(chain.Client)
switch role {
case mcmssdk.TimelockRoleProposer:
return inspector.GetProposers(b.GetContext(), timelock.Hex())
case mcmssdk.TimelockRoleCanceller:
return inspector.GetCancellers(b.GetContext(), timelock.Hex())
case mcmssdk.TimelockRoleBypasser:
return inspector.GetBypassers(b.GetContext(), timelock.Hex())
case mcmssdk.TimelockRoleExecutor:
return inspector.GetExecutors(b.GetContext(), timelock.Hex())
case mcmssdk.TimelockRoleAdmin:
return nil, nil
default:
return nil, fmt.Errorf("unsupported timelock role %s", role.String())
}
}

func timelockAddress(env cldf.Environment, in grantrole.SeqInput) (common.Address, error) {
reader, ok := cldf.GetMCMSReaderRegistry().Get(chainselectors.FamilyEVM)
if !ok {
Expand Down
37 changes: 23 additions & 14 deletions mcms/evm/grant-role/sequence_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func TestRunEVMGrantRole_idempotent(t *testing.T) {
rt := newEVMGrantRoleRuntime(t, selector)
refs := grantRoleRefsFromEnv(t, rt.Environment(), selector)
chain := rt.Environment().BlockChains.EVMChains()[selector]
grantee := common.HexToAddress("0x00000000000000000000000000000000000000bb").Hex()
grantee := "0x00000000000000000000000000000000000000bb"
deps := grantrole.Deps{
BlockChains: rt.Environment().BlockChains,
DataStore: rt.Environment().DataStore,
Expand Down Expand Up @@ -138,8 +138,8 @@ func TestAddressesNeedingGrant(t *testing.T) {
DataStore: rt.Environment().DataStore,
}

grantee := common.HexToAddress("0x00000000000000000000000000000000000000dd").Hex()
pending := common.HexToAddress("0x00000000000000000000000000000000000000ee").Hex()
grantee := "0x00000000000000000000000000000000000000dd"
pending := "0x00000000000000000000000000000000000000ee"
_, err := runEVMGrantRole(bundle, deps, grantrole.SeqInput{
ChainSelector: selector,
Grants: []grantrole.RoleGrant{{
Expand All @@ -149,22 +149,31 @@ func TestAddressesNeedingGrant(t *testing.T) {
})
require.NoError(t, err)

needed, err := addressesNeedingGrant(bundle, chain, refs.Timelock, grantrole.RoleGrant{
Role: mcmssdk.TimelockRoleCanceller,
Addresses: []string{grantee, pending},
})
needed, err := grantrole.AddressesNeedingGrant(
t.Context(),
mcmsevm.NewTimelockInspector(chain.Client),
refs.Timelock.Hex(),
grantrole.RoleGrant{
Role: mcmssdk.TimelockRoleCanceller,
Addresses: []string{grantee, pending},
},
func(address string) string { return common.HexToAddress(address).Hex() },
)
require.NoError(t, err)
require.Equal(t, []string{pending}, needed)

adminNeeded, err := addressesNeedingGrant(bundle, chain, refs.Timelock, grantrole.RoleGrant{
Role: mcmssdk.TimelockRoleAdmin,
Addresses: []string{grantee},
})
adminNeeded, err := grantrole.AddressesNeedingGrant(
t.Context(),
mcmsevm.NewTimelockInspector(chain.Client),
refs.Timelock.Hex(),
grantrole.RoleGrant{
Role: mcmssdk.TimelockRoleAdmin,
Addresses: []string{grantee},
},
func(address string) string { return common.HexToAddress(address).Hex() },
)
require.NoError(t, err)
require.Equal(t, []string{grantee}, adminNeeded)

_, err = addressesForRole(bundle, chain, refs.Timelock, mcmssdk.TimelockRole(99))
require.EqualError(t, err, "unsupported timelock role Unknown")
}

func newEVMGrantRoleRuntime(t *testing.T, selector uint64) *runtime.Runtime {
Expand Down
Loading