Skip to content

Commit

Permalink
feat(deployment/mcms): update test helpers (#16248)
Browse files Browse the repository at this point in the history
Add equivalent test helpers which use the new MCMS library

Also updated the changeset link transfer to use the new helpers as a way to test the new changes.

JIRA: https://smartcontract-it.atlassian.net/browse/DPA-1367

Co-authored-by: ajaskolski <[email protected]>
  • Loading branch information
graham-chainlink and ajaskolski authored Feb 7, 2025
1 parent 111fe84 commit e6dfb80
Show file tree
Hide file tree
Showing 4 changed files with 320 additions and 7 deletions.
88 changes: 82 additions & 6 deletions deployment/common/changeset/example/link_transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import (
"github.com/ethereum/go-ethereum/common"
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"
chain_selectors "github.com/smartcontractkit/chain-selectors"
mcmslib "github.com/smartcontractkit/mcms"
"github.com/smartcontractkit/mcms/sdk"
"github.com/smartcontractkit/mcms/sdk/evm"
mcmstypes "github.com/smartcontractkit/mcms/types"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/common/changeset"
Expand Down Expand Up @@ -99,6 +102,9 @@ func (cfg LinkTransferConfig) Validate(e deployment.Environment) error {
}
// check that from address has enough funds for the transfers
balance, err := linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, cfg.From)
if err != nil {
return fmt.Errorf("error getting balance of sender: %w", err)
}
if balance.Cmp(totalAmount) < 0 {
return fmt.Errorf("sender does not have enough funds for transfers for chain selector %d, required: %s, available: %s", chainSel, totalAmount.String(), balance.String())
}
Expand Down Expand Up @@ -167,15 +173,15 @@ func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("invalid LinkTransferConfig: %w", err)
}
chainSelectors := []uint64{}
for chainSelector := range cfg.Transfers {
chainSelectors = append(chainSelectors, chainSelector)
}

mcmsPerChain := map[uint64]*owner_helpers.ManyChainMultiSig{}

timelockAddresses := map[uint64]common.Address{}
// Initialize state for each chain
linkStatePerChain, mcmsStatePerChain, err := initStatePerChain(cfg, e)
if err != nil {
return deployment.ChangesetOutput{}, err
}

allBatches := []timelock.BatchChainOperation{}
for chainSelector := range cfg.Transfers {
Expand Down Expand Up @@ -234,3 +240,73 @@ func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment

return deployment.ChangesetOutput{}, nil
}

// LinkTransferV2 is an reimplementation of LinkTransfer that uses the new MCMS SDK.
func LinkTransferV2(e deployment.Environment, cfg *LinkTransferConfig) (deployment.ChangesetOutput, error) {
err := cfg.Validate(e)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("invalid LinkTransferConfig: %w", err)
}

proposerAddressPerChain := map[uint64]string{}
inspectorPerChain := map[uint64]sdk.Inspector{}
timelockAddressesPerChain := map[uint64]string{}
linkStatePerChain, mcmsStatePerChain, err := initStatePerChain(cfg, e)
if err != nil {
return deployment.ChangesetOutput{}, err
}

allBatches := []mcmstypes.BatchOperation{}
for chainSelector := range cfg.Transfers {
chain := e.Chains[chainSelector]
linkAddress := linkStatePerChain[chainSelector].LinkToken.Address()
mcmsState := mcmsStatePerChain[chainSelector]
linkState := linkStatePerChain[chainSelector]

proposerAddressPerChain[chainSelector] = mcmsState.ProposerMcm.Address().Hex()
inspectorPerChain[chainSelector] = evm.NewInspector(chain.Client)

timelockAddress := mcmsState.Timelock.Address().Hex()
timelockAddressesPerChain[chainSelector] = timelockAddress

batch := mcmstypes.BatchOperation{
ChainSelector: mcmstypes.ChainSelector(chainSelector),
Transactions: []mcmstypes.Transaction{},
}

opts := getDeployer(e, chainSelector, cfg.McmsConfig)
totalAmount := big.NewInt(0)
for _, transfer := range cfg.Transfers[chainSelector] {
tx, err := transferOrBuildTx(e, linkState, transfer, opts, chain, cfg.McmsConfig)
if err != nil {
return deployment.ChangesetOutput{}, err
}
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.BuildProposalFromBatchesV2(
e.GetContext(),
timelockAddressesPerChain,
proposerAddressPerChain,
inspectorPerChain,
allBatches,
"LINK Value transfer proposal",
cfg.McmsConfig.MinDelay,
)
if err != nil {
return deployment.ChangesetOutput{}, err
}

return deployment.ChangesetOutput{
MCMSTimelockProposals: []mcmslib.TimelockProposal{*proposal},
}, nil
}

return deployment.ChangesetOutput{}, nil
}
73 changes: 73 additions & 0 deletions deployment/common/changeset/example/link_transfer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,3 +370,76 @@ func TestValidate(t *testing.T) {
})
}
}

func TestLinkTransferMCMSV2(t *testing.T) {
t.Parallel()
ctx := context.Background()

env := setupLinkTransferTestEnv(t)
chainSelector := env.AllChainSelectors()[0]
chain := env.Chains[chainSelector]
addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector)
require.NoError(t, err)
require.Len(t, addrs, 6)

mcmsState, err := changeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs)
require.NoError(t, err)
linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addrs)
require.NoError(t, err)
timelockAddress := mcmsState.Timelock.Address()

// Mint some funds
// grant minter permissions
tx, err := linkState.LinkToken.GrantMintRole(chain.DeployerKey, chain.DeployerKey.From)
require.NoError(t, err)
_, err = deployment.ConfirmIfNoError(chain, tx, err)
require.NoError(t, err)

tx, err = linkState.LinkToken.Mint(chain.DeployerKey, timelockAddress, big.NewInt(750))
require.NoError(t, err)
_, err = deployment.ConfirmIfNoError(chain, tx, err)
require.NoError(t, err)

timelocks := map[uint64]*proposalutils.TimelockExecutionContracts{
chainSelector: {
Timelock: mcmsState.Timelock,
CallProxy: mcmsState.CallProxy,
},
}
// Apply the changeset
_, err = changeset.ApplyChangesets(t, env, timelocks, []changeset.ChangesetApplication{
// the changeset produces proposals, ApplyChangesets will sign & execute them.
// in practice, signing and executing are separated processes.
{
Changeset: changeset.WrapChangeSet(example.LinkTransferV2),
Config: &example.LinkTransferConfig{
From: timelockAddress,
Transfers: map[uint64][]example.TransferConfig{
chainSelector: {
{
To: chain.DeployerKey.From,
Value: big.NewInt(500),
},
},
},
McmsConfig: &example.MCMSConfig{
MinDelay: 0,
OverrideRoot: true,
},
},
},
})
require.NoError(t, err)

// Check new balances
endBalance, err := linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, chain.DeployerKey.From)
require.NoError(t, err)
expectedBalance := big.NewInt(500)
require.Equal(t, expectedBalance, endBalance)

// check timelock balance
endBalance, err = linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, timelockAddress)
require.NoError(t, err)
expectedBalance = big.NewInt(250)
require.Equal(t, expectedBalance, endBalance)
}
32 changes: 32 additions & 0 deletions deployment/common/changeset/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,38 @@ 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)
for _, sel := range chains.ToSlice() {
proposalutils.ExecuteMCMSProposalV2(t, e, p, sel)
}
}
}
currentEnv = deployment.Environment{
Name: e.Name,
Logger: e.Logger,
Expand Down
Loading

0 comments on commit e6dfb80

Please sign in to comment.