Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Testing link token transfer changeset with new helpers and builder #16224

Closed
wants to merge 3 commits into from
Closed
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
29 changes: 12 additions & 17 deletions deployment/common/changeset/example/link_transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import (
ethTypes "github.com/ethereum/go-ethereum/core/types"
owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"
chain_selectors "github.com/smartcontractkit/chain-selectors"

"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"
mcms2 "github.com/smartcontractkit/mcms"
"github.com/smartcontractkit/mcms/sdk/evm"
types2 "github.com/smartcontractkit/mcms/types"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/common/changeset"
Expand Down Expand Up @@ -177,9 +177,9 @@ func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment
// Initialize state for each chain
linkStatePerChain, mcmsStatePerChain, err := initStatePerChain(cfg, e)

allBatches := []timelock.BatchChainOperation{}
allBatches := []types2.BatchOperation{}
for chainSelector := range cfg.Transfers {
chainID := mcms.ChainIdentifier(chainSelector)
chainID := types2.ChainSelector(chainSelector)
chain := e.Chains[chainSelector]
linkAddress := linkStatePerChain[chainSelector].LinkToken.Address()
mcmsState := mcmsStatePerChain[chainSelector]
Expand All @@ -190,9 +190,9 @@ func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment
mcmsPerChain[uint64(chainID)] = mcmsState.ProposerMcm

timelockAddresses[chainSelector] = timelockAddress
batch := timelock.BatchChainOperation{
ChainIdentifier: chainID,
Batch: []mcms.Operation{},
batch := types2.BatchOperation{
ChainSelector: chainID,
Transactions: []types2.Transaction{},
}

opts := getDeployer(e, chainSelector, cfg.McmsConfig)
Expand All @@ -202,21 +202,16 @@ func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment
if err != nil {
return deployment.ChangesetOutput{}, err
}
op := mcms.Operation{
To: linkAddress,
Data: tx.Data(),
Value: big.NewInt(0),
ContractType: string(types.LinkToken),
}
batch.Batch = append(batch.Batch, op)
op := evm.NewTransaction(linkAddress, tx.Data(), big.NewInt(0), string(types.LinkToken), []string{})
batch.Transactions = append(batch.Transactions, op)
totalAmount.Add(totalAmount, transfer.Value)
}

allBatches = append(allBatches, batch)
}

if cfg.McmsConfig != nil {
proposal, err := proposalutils.BuildProposalFromBatches(
proposal, err := proposalutils.BuildProposalFromBatchesV2(
timelockAddresses,
mcmsPerChain,
allBatches,
Expand All @@ -228,7 +223,7 @@ func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment
}

return deployment.ChangesetOutput{
Proposals: []timelock.MCMSWithTimelockProposal{*proposal},
MCMSTimelockProposals: []mcms2.TimelockProposal{*proposal},
}, nil
}

Expand Down
33 changes: 33 additions & 0 deletions deployment/common/changeset/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,39 @@ func ApplyChangesets(t *testing.T, e deployment.Environment, timelockContractsPe
}
}
}
if out.MCMSTimelockProposals != nil {
for _, prop := range out.MCMSTimelockProposals {
chains := mapset.NewSet[uint64]()
for _, op := range prop.Operations {
chains.Add(uint64(op.ChainSelector))
}

p := proposalutils.SignMCMSTimelockProposal(t, e, &prop)
for _, sel := range chains.ToSlice() {
timelockContracts, ok := timelockContractsPerChain[sel]
if !ok || timelockContracts == nil {
return deployment.Environment{}, fmt.Errorf("timelock contracts not found for chain %d", sel)
}

proposalutils.ExecuteMCMSProposalV2(t, e, p, sel)
proposalutils.ExecuteMCMSTimelockProposalV2(t, e, &prop, sel)
}
}
}
if out.MCMSProposals != nil {
for _, prop := range out.MCMSProposals {
chains := mapset.NewSet[uint64]()
for _, op := range prop.Operations {
chains.Add(uint64(op.ChainSelector))
}

p := proposalutils.SignMCMSProposal(t, e, &prop)
p.UseSimulatedBackend(true)
for _, sel := range chains.ToSlice() {
proposalutils.ExecuteMCMSProposalV2(t, e, p, sel)
}
}
}
currentEnv = deployment.Environment{
Name: e.Name,
Logger: e.Logger,
Expand Down
125 changes: 125 additions & 0 deletions deployment/common/proposalutils/mcms_test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ import (
"testing"

"github.com/ethereum/go-ethereum/common"
types2 "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/config"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"
chainsel "github.com/smartcontractkit/chain-selectors"
mcms2 "github.com/smartcontractkit/mcms"
"github.com/smartcontractkit/mcms/sdk"
"github.com/smartcontractkit/mcms/sdk/evm"
"github.com/smartcontractkit/mcms/types"
"github.com/stretchr/testify/require"

"github.com/smartcontractkit/chainlink/deployment"
Expand Down Expand Up @@ -82,6 +87,126 @@ func ExecuteProposal(t *testing.T, env deployment.Environment, executor *mcms.Ex
require.NoError(t, RunTimelockExecutor(env, cfg))
}

func SignMCMSTimelockProposal(t *testing.T, env deployment.Environment, proposal *mcms2.TimelockProposal) *mcms2.Proposal {
converters := make(map[types.ChainSelector]sdk.TimelockConverter)
inspectorsMap := make(map[types.ChainSelector]sdk.Inspector)
for _, chain := range env.Chains {
chainselc, exists := chainsel.ChainBySelector(chain.Selector)
require.True(t, exists)
chainSel := types.ChainSelector(chainselc.Selector)
converters[chainSel] = &evm.TimelockConverter{}
inspectorsMap[chainSel] = evm.NewInspector(chain.Client)
}

p, _, err := proposal.Convert(env.GetContext(), converters)
require.NoError(t, err)
p.UseSimulatedBackend(true)

signable, err := mcms2.NewSignable(&p, inspectorsMap)
require.NoError(t, err)

err = signable.ValidateConfigs(env.GetContext())
require.NoError(t, err)

signer := mcms2.NewPrivateKeySigner(TestXXXMCMSSigner)
_, err = signable.SignAndAppend(signer)
require.NoError(t, err)

quorumMet, err := signable.ValidateSignatures(env.GetContext())
require.NoError(t, err)
require.True(t, quorumMet)

return &p
}

func SignMCMSProposal(t *testing.T, env deployment.Environment, p *mcms2.Proposal) *mcms2.Proposal {
converters := make(map[types.ChainSelector]sdk.TimelockConverter)
inspectorsMap := make(map[types.ChainSelector]sdk.Inspector)
for _, chain := range env.Chains {
chainselc, exists := chainsel.ChainBySelector(chain.Selector)
require.True(t, exists)
chainSel := types.ChainSelector(chainselc.Selector)
converters[chainSel] = &evm.TimelockConverter{}
inspectorsMap[chainSel] = evm.NewInspector(chain.Client)
}

signable, err := mcms2.NewSignable(p, inspectorsMap)
require.NoError(t, err)

err = signable.ValidateConfigs(env.GetContext())
require.NoError(t, err)

signer := mcms2.NewPrivateKeySigner(TestXXXMCMSSigner)
_, err = signable.SignAndAppend(signer)
require.NoError(t, err)

quorumMet, err := signable.ValidateSignatures(env.GetContext())
require.NoError(t, err)
require.True(t, quorumMet)

return p
}

func ExecuteMCMSProposalV2(t *testing.T, env deployment.Environment, proposal *mcms2.Proposal, sel uint64) {
t.Log("Executing proposal on chain", sel)

encoders, err := proposal.GetEncoders()
require.NoError(t, err)

selector := types.ChainSelector(sel)
encoder := encoders[selector].(*evm.Encoder)
evmExecutor := evm.NewExecutor(encoder, env.Chains[sel].Client, env.Chains[sel].DeployerKey)
executorsMap := map[types.ChainSelector]sdk.Executor{
selector: evmExecutor,
}
executable, err := mcms2.NewExecutable(proposal, executorsMap)
require.NoError(t, err)

chain := env.Chains[sel]
root, err := executable.SetRoot(env.GetContext(), selector)
require.NoError(t, deployment.MaybeDataErr(err))

evmTransaction := root.RawTransaction.(*types2.Transaction)
_, err = chain.Confirm(evmTransaction)
require.NoError(t, err)

for i := 0; i < len(proposal.Operations); i++ {
result, err := executable.Execute(env.GetContext(), i)
require.NoError(t, err)

evmTransaction = result.RawTransaction.(*types2.Transaction)
_, err = chain.Confirm(evmTransaction)
require.NoError(t, err)
}
}

// ExecuteMCMSTimelockProposalV2 - Includes an option to set callProxy to execute the calls through a proxy.
// If the callProxy is not set, the calls will be executed directly to the timelock.
func ExecuteMCMSTimelockProposalV2(t *testing.T, env deployment.Environment, timelockProposal *mcms2.TimelockProposal, sel uint64, opts ...mcms2.Option) {
tExecutors := map[types.ChainSelector]sdk.TimelockExecutor{}
chain := env.Chains[sel]

chainSel := types.ChainSelector(sel)
tExecutors[chainSel] = evm.NewTimelockExecutor(
env.Chains[sel].Client,
env.Chains[sel].DeployerKey)

timelockExecutable, err := mcms2.NewTimelockExecutable(timelockProposal, tExecutors)
require.NoError(t, err)

err = timelockExecutable.IsReady(env.GetContext())
require.NoError(t, err)

var tx = types.TransactionResult{}
for i := range timelockProposal.Operations {
tx, err = timelockExecutable.Execute(env.GetContext(), i, opts...)
require.NoError(t, err, "Failed to mine execution transaction on Chain A")
evmTransaction := tx.RawTransaction.(*types2.Transaction)
_, err = chain.Confirm(evmTransaction)
require.NoError(t, err)
}
}

func SingleGroupTimelockConfig(t *testing.T) commontypes.MCMSWithTimelockConfig {
return commontypes.MCMSWithTimelockConfig{
Canceller: SingleGroupMCMS(t),
Expand Down
74 changes: 74 additions & 0 deletions deployment/common/proposalutils/propose.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"
mcmslib "github.com/smartcontractkit/mcms"
"github.com/smartcontractkit/mcms/types"
)

const (
Expand Down Expand Up @@ -47,6 +49,7 @@ func BuildProposalMetadata(
// The batches are specified separately because we eventually intend
// to support user-specified cross chain ordering of batch execution by the tooling itself.
// TODO: Can/should merge timelocks and proposers into a single map for the chain.
// Deprecated: Use BuildProposalFromBatchesV2 instead.
func BuildProposalFromBatches(
timelocksPerChain map[uint64]common.Address,
proposerMcmsesPerChain map[uint64]*gethwrappers.ManyChainMultiSig,
Expand Down Expand Up @@ -86,3 +89,74 @@ func BuildProposalFromBatches(
minDelay.String(),
)
}

// BuildProposalFromBatchesV2 uses the new MCMS library which replaces the implementation in BuildProposalFromBatches.
func BuildProposalFromBatchesV2(
timelocksPerChain map[uint64]common.Address,
proposerMcmsesPerChain map[uint64]*gethwrappers.ManyChainMultiSig,
batches []types.BatchOperation,
description string,
minDelay time.Duration,
) (*mcmslib.TimelockProposal, error) {
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))
}

mcmsMd, err := buildProposalMetadataV2(chains.ToSlice(), proposerMcmsesPerChain)
if err != nil {
return nil, err
}

tlsPerChainID := make(map[types.ChainSelector]string)
for chainID, tl := range timelocksPerChain {
tlsPerChainID[types.ChainSelector(chainID)] = tl.Hex()
}
validUntil := time.Now().Unix() + int64(DefaultValidUntil.Seconds())

builder := mcmslib.NewTimelockProposalBuilder()
builder.
SetVersion("v1").
SetAction(types.TimelockActionSchedule).
//nolint:gosec // G115
SetValidUntil(uint32(validUntil)).
SetDescription(description).
SetDelay(types.NewDuration(minDelay)).
SetOverridePreviousRoot(false).
SetChainMetadata(mcmsMd).
SetTimelockAddresses(tlsPerChainID).
SetOperations(batches)

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

func buildProposalMetadataV2(
chainSelectors []uint64,
proposerMcmsesPerChain map[uint64]*gethwrappers.ManyChainMultiSig,
) (map[types.ChainSelector]types.ChainMetadata, error) {
metaDataPerChain := make(map[types.ChainSelector]types.ChainMetadata)
for _, selector := range chainSelectors {
proposerMcms, ok := proposerMcmsesPerChain[selector]
if !ok {
return nil, fmt.Errorf("missing proposer mcm for chain %d", selector)
}
chainID := types.ChainSelector(selector)
opCount, err := proposerMcms.GetOpCount(nil)
if err != nil {
return nil, fmt.Errorf("failed to get op count for chain %d: %w", selector, err)
}
metaDataPerChain[chainID] = types.ChainMetadata{
StartingOpCount: opCount.Uint64(),
MCMAddress: proposerMcms.Address().Hex(),
}
}
return metaDataPerChain, nil
}
Loading
Loading