Skip to content

Commit

Permalink
feat: add view for new mcms evm state (#16290)
Browse files Browse the repository at this point in the history
  • Loading branch information
gustavogama-cll authored Feb 20, 2025
1 parent 8e1480c commit 2f30383
Show file tree
Hide file tree
Showing 6 changed files with 530 additions and 57 deletions.
3 changes: 2 additions & 1 deletion deployment/common/changeset/internal/solana/mcms.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package mcmsnew

import (
"errors"
"fmt"

"github.com/smartcontractkit/chainlink/deployment"
Expand All @@ -19,7 +20,7 @@ func DeployMCMSWithTimelockProgramsSolana(
config commontypes.MCMSWithTimelockConfigV2,
) (*state.MCMSWithTimelockStateSolana, error) {
addresses, err := e.ExistingAddresses.AddressesForChain(chain.Selector)
if err != nil {
if err != nil && !errors.Is(err, deployment.ErrChainNotFound) {
return nil, fmt.Errorf("failed to get addresses for chain %v from environment: %w", chain.Selector, err)
}

Expand Down
48 changes: 12 additions & 36 deletions deployment/common/changeset/state/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
"github.com/smartcontractkit/chainlink/deployment/common/types"
"github.com/smartcontractkit/chainlink/deployment/common/view/v1_0"
view "github.com/smartcontractkit/chainlink/deployment/common/view/v1_0"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token"
)
Expand All @@ -23,37 +23,13 @@ type MCMSWithTimelockState struct {
*proposalutils.MCMSWithTimelockContracts
}

func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWithTimelockView, error) {
func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (view.MCMSWithTimelockView, error) {
if err := state.Validate(); err != nil {
return v1_0.MCMSWithTimelockView{}, err
return view.MCMSWithTimelockView{}, fmt.Errorf("unable to validate McmsWithTimelock state: %w", err)
}
timelockView, err := v1_0.GenerateTimelockView(*state.Timelock)
if err != nil {
return v1_0.MCMSWithTimelockView{}, nil
}
callProxyView, err := v1_0.GenerateCallProxyView(*state.CallProxy)
if err != nil {
return v1_0.MCMSWithTimelockView{}, nil
}
bypasserView, err := v1_0.GenerateMCMSView(*state.BypasserMcm)
if err != nil {
return v1_0.MCMSWithTimelockView{}, nil
}
proposerView, err := v1_0.GenerateMCMSView(*state.ProposerMcm)
if err != nil {
return v1_0.MCMSWithTimelockView{}, nil
}
cancellerView, err := v1_0.GenerateMCMSView(*state.CancellerMcm)
if err != nil {
return v1_0.MCMSWithTimelockView{}, nil
}
return v1_0.MCMSWithTimelockView{
Timelock: timelockView,
Bypasser: bypasserView,
Proposer: proposerView,
Canceller: cancellerView,
CallProxy: callProxyView,
}, nil

return view.GenerateMCMSWithTimelockView(*state.BypasserMcm, *state.CancellerMcm, *state.ProposerMcm,
*state.Timelock, *state.CallProxy)
}

// MaybeLoadMCMSWithTimelockState loads the MCMSWithTimelockState state for each chain in the given environment.
Expand Down Expand Up @@ -163,11 +139,11 @@ type LinkTokenState struct {
LinkToken *link_token.LinkToken
}

func (s LinkTokenState) GenerateLinkView() (v1_0.LinkTokenView, error) {
func (s LinkTokenState) GenerateLinkView() (view.LinkTokenView, error) {
if s.LinkToken == nil {
return v1_0.LinkTokenView{}, errors.New("link token not found")
return view.LinkTokenView{}, errors.New("link token not found")
}
return v1_0.GenerateLinkTokenView(s.LinkToken)
return view.GenerateLinkTokenView(s.LinkToken)
}

// MaybeLoadLinkTokenState loads the LinkTokenState state for each chain in the given environment.
Expand Down Expand Up @@ -220,11 +196,11 @@ type StaticLinkTokenState struct {
StaticLinkToken *link_token_interface.LinkToken
}

func (s StaticLinkTokenState) GenerateStaticLinkView() (v1_0.StaticLinkTokenView, error) {
func (s StaticLinkTokenState) GenerateStaticLinkView() (view.StaticLinkTokenView, error) {
if s.StaticLinkToken == nil {
return v1_0.StaticLinkTokenView{}, errors.New("static link token not found")
return view.StaticLinkTokenView{}, errors.New("static link token not found")
}
return v1_0.GenerateStaticLinkTokenView(s.StaticLinkToken)
return view.GenerateStaticLinkTokenView(s.StaticLinkToken)
}

func MaybeLoadStaticLinkTokenState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*StaticLinkTokenState, error) {
Expand Down
186 changes: 186 additions & 0 deletions deployment/common/changeset/state/evm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package state

import (
"encoding/json"
"fmt"
"math/big"
"strings"
"testing"

"github.com/ethereum/go-ethereum/common"
bindings "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"
mcmsevmsdk "github.com/smartcontractkit/mcms/sdk/evm"
mcmstypes "github.com/smartcontractkit/mcms/types"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
"github.com/smartcontractkit/chainlink/deployment/environment/memory"
"github.com/smartcontractkit/chainlink/v2/core/logger"
)

func TestMCMSWithTimelockState_GenerateMCMSWithTimelockViewV2(t *testing.T) {
envConfig := memory.MemoryEnvironmentConfig{Chains: 1}
env := memory.NewMemoryEnvironment(t, logger.TestLogger(t), zapcore.InfoLevel, envConfig)
chain := env.Chains[env.AllChainSelectors()[0]]

proposerMcm := deployMCMEvm(t, chain, &mcmstypes.Config{Quorum: 1, Signers: []common.Address{
common.HexToAddress("0x0000000000000000000000000000000000000001"),
}})
cancellerMcm := deployMCMEvm(t, chain, &mcmstypes.Config{Quorum: 1, Signers: []common.Address{
common.HexToAddress("0x0000000000000000000000000000000000000002"),
}})
bypasserMcm := deployMCMEvm(t, chain, &mcmstypes.Config{Quorum: 1, Signers: []common.Address{
common.HexToAddress("0x0000000000000000000000000000000000000003"),
}})
timelock := deployTimelockEvm(t, chain, big.NewInt(1),
common.HexToAddress("0x0000000000000000000000000000000000000004"),
[]common.Address{common.HexToAddress("0x0000000000000000000000000000000000000005")},
[]common.Address{common.HexToAddress("0x0000000000000000000000000000000000000006")},
[]common.Address{common.HexToAddress("0x0000000000000000000000000000000000000007")},
[]common.Address{common.HexToAddress("0x0000000000000000000000000000000000000008")},
)
callProxy := deployCallProxyEvm(t, chain,
common.HexToAddress("0x0000000000000000000000000000000000000009"))

tests := []struct {
name string
contracts *proposalutils.MCMSWithTimelockContracts
want string
wantErr string
}{
{
name: "success",
contracts: &proposalutils.MCMSWithTimelockContracts{
ProposerMcm: proposerMcm,
CancellerMcm: cancellerMcm,
BypasserMcm: bypasserMcm,
Timelock: timelock,
CallProxy: callProxy,
},
want: fmt.Sprintf(`{
"proposer": {
"address": "%s",
"owner": "%s",
"config": {
"quorum": 1,
"signers": ["0x0000000000000000000000000000000000000001"],
"groupSigners": []
}
},
"canceller": {
"address": "%s",
"owner": "%s",
"config": {
"quorum": 1,
"signers": ["0x0000000000000000000000000000000000000002"],
"groupSigners": []
}
},
"bypasser": {
"address": "%s",
"owner": "%s",
"config": {
"quorum": 1,
"signers": ["0x0000000000000000000000000000000000000003"],
"groupSigners": []
}
},
"timelock": {
"address": "%s",
"owner": "0x0000000000000000000000000000000000000000",
"membersByRole": {
"ADMIN_ROLE": [ "0x0000000000000000000000000000000000000004" ],
"PROPOSER_ROLE": [ "0x0000000000000000000000000000000000000005" ],
"EXECUTOR_ROLE": [ "0x0000000000000000000000000000000000000006" ],
"CANCELLER_ROLE": [ "0x0000000000000000000000000000000000000007" ],
"BYPASSER_ROLE": [ "0x0000000000000000000000000000000000000008" ]
}
},
"callProxy": {
"address": "%s",
"owner": "0x0000000000000000000000000000000000000000"
}
}`, evmAddr(proposerMcm.Address()), evmAddr(chain.DeployerKey.From),
evmAddr(cancellerMcm.Address()), evmAddr(chain.DeployerKey.From),
evmAddr(bypasserMcm.Address()), evmAddr(chain.DeployerKey.From),
evmAddr(timelock.Address()), evmAddr(callProxy.Address())),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
state := MCMSWithTimelockState{tt.contracts}

got, err := state.GenerateMCMSWithTimelockView()

if tt.wantErr == "" {
require.NoError(t, err)
require.JSONEq(t, tt.want, toJSON(t, &got))
} else {
require.ErrorContains(t, err, tt.wantErr)
}
})
}
}

// ----- helpers -----

func toJSON[T any](t *testing.T, value T) string {
t.Helper()

bytes, err := json.Marshal(value)
require.NoError(t, err)

return string(bytes)
}

func deployMCMEvm(
t *testing.T, chain deployment.Chain, config *mcmstypes.Config,
) *bindings.ManyChainMultiSig {
t.Helper()

_, tx, contract, err := bindings.DeployManyChainMultiSig(chain.DeployerKey, chain.Client)
require.NoError(t, err)
_, err = chain.Confirm(tx)
require.NoError(t, err)

groupQuorums, groupParents, signerAddresses, signerGroups, err := mcmsevmsdk.ExtractSetConfigInputs(config)
require.NoError(t, err)
tx, err = contract.SetConfig(chain.DeployerKey, signerAddresses, signerGroups, groupQuorums, groupParents, false)
require.NoError(t, err)
_, err = chain.Confirm(tx)
require.NoError(t, err)

return contract
}

func deployTimelockEvm(
t *testing.T, chain deployment.Chain, minDelay *big.Int, admin common.Address,
proposers, executors, cancellers, bypassers []common.Address,
) *bindings.RBACTimelock {
t.Helper()
_, tx, contract, err := bindings.DeployRBACTimelock(
chain.DeployerKey, chain.Client, minDelay, admin, proposers, executors, cancellers, bypassers)
require.NoError(t, err)
_, err = chain.Confirm(tx)
require.NoError(t, err)

return contract
}

func deployCallProxyEvm(
t *testing.T, chain deployment.Chain, target common.Address,
) *bindings.CallProxy {
t.Helper()
_, tx, contract, err := bindings.DeployCallProxy(chain.DeployerKey, chain.Client, target)
require.NoError(t, err)
_, err = chain.Confirm(tx)
require.NoError(t, err)

return contract
}

func evmAddr(addr common.Address) string {
return strings.ToLower(addr.Hex())
}
30 changes: 23 additions & 7 deletions deployment/common/changeset/state/solana.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package state

import (
"context"
"errors"
"fmt"

Expand All @@ -10,6 +11,7 @@ import (
timelockBindings "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/timelock"
"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/common/types"
view "github.com/smartcontractkit/chainlink/deployment/common/view/v1_0"
)

type PDASeed [32]byte
Expand Down Expand Up @@ -98,25 +100,25 @@ func (s *MCMSWithTimelockProgramsSolana) SetState(contractType deployment.Contra
// for use generating views or interactions.
func (s *MCMSWithTimelockProgramsSolana) Validate() error {
if s.McmProgram.IsZero() {
return errors.New("canceller not found")
return errors.New("mcm program not found")
}
if s.TimelockProgram.IsZero() {
return errors.New("timelock not found")
return errors.New("timelock program not found")
}
if s.AccessControllerProgram.IsZero() {
return errors.New("timelock not found")
return errors.New("access controller program not found")
}
if s.ProposerAccessControllerAccount.IsZero() {
return errors.New("access controller not found")
return errors.New("proposer access controller account not found")
}
if s.ExecutorAccessControllerAccount.IsZero() {
return errors.New("access controller not found")
return errors.New("executor access controller account not found")
}
if s.CancellerAccessControllerAccount.IsZero() {
return errors.New("access controller not found")
return errors.New("canceller access controller account not found")
}
if s.BypasserAccessControllerAccount.IsZero() {
return errors.New("access controller not found")
return errors.New("bypasser access controller account not found")
}
return nil
}
Expand All @@ -136,6 +138,20 @@ func (s *MCMSWithTimelockProgramsSolana) RoleAccount(role timelockBindings.Role)
}
}

func (s *MCMSWithTimelockProgramsSolana) GenerateView(
ctx context.Context, chain deployment.SolChain,
) (view.MCMSWithTimelockViewSolana, error) {
if err := s.Validate(); err != nil {
return view.MCMSWithTimelockViewSolana{}, fmt.Errorf("unable to validate state: %w", err)
}

inspector := mcmssolanasdk.NewInspector(chain.Client)
timelockInspector := mcmssolanasdk.NewTimelockInspector(chain.Client)

return view.GenerateMCMSWithTimelockViewSolana(ctx, inspector, timelockInspector, s.McmProgram,
s.ProposerMcmSeed, s.CancellerMcmSeed, s.BypasserMcmSeed, s.TimelockProgram, s.TimelockSeed)
}

// MCMSWithTimelockStateStateSolana holds the Go bindings
// for a MCMSWithTimelock contract deployment.
// It is public for use in product specific packages.
Expand Down
Loading

0 comments on commit 2f30383

Please sign in to comment.