From a6910aecb756da792c80621e80df2db5804c44dc Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 3 Dec 2024 15:14:48 -0600 Subject: [PATCH 01/41] feat: link transfer with timelock changeset --- deployment/common/changeset/link_transfer.go | 85 ++++++++++++ .../common/changeset/link_transfer_test.go | 122 ++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 deployment/common/changeset/link_transfer.go create mode 100644 deployment/common/changeset/link_transfer_test.go diff --git a/deployment/common/changeset/link_transfer.go b/deployment/common/changeset/link_transfer.go new file mode 100644 index 00000000000..f9e675e7287 --- /dev/null +++ b/deployment/common/changeset/link_transfer.go @@ -0,0 +1,85 @@ +package changeset + +import ( + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token" +) + +type LinkTransfer struct { + From common.Address + To common.Address + Value big.Int +} +type LinkTransferTimelockRequest struct { + Transfers []LinkTransfer + ChainSelector uint64 + LinkTokenAddress common.Address + TimelockAddress common.Address + MCMSAddress common.Address + ValidUntil uint32 // unix time until the proposal will be valid + MinDelay time.Duration // delay for timelock worker to execute the transfers. + OverrideRoot bool +} + +var _ deployment.ChangeSet[*LinkTransferTimelockRequest] = LinkTransferTimelock + +// LinkTransferTimelock takes the given link transfers and creates an MCMS proposal for them. +func LinkTransferTimelock(env deployment.Environment, req *LinkTransferTimelockRequest) (deployment.ChangesetOutput, error) { + chain := env.Chains[req.ChainSelector] + link, err := link_token.NewLinkToken(req.LinkTokenAddress, chain.Client) + if err != nil { + return deployment.ChangesetOutput{}, err + } + chainID := mcms.ChainIdentifier(req.ChainSelector) + chainMetadata := map[mcms.ChainIdentifier]mcms.ChainMetadata{ + chainID: {MCMAddress: req.MCMSAddress, StartingOpCount: 0}, + } + timelockAddresses := map[mcms.ChainIdentifier]common.Address{ + chainID: req.TimelockAddress, + } + batch := timelock.BatchChainOperation{ + ChainIdentifier: chainID, + Batch: []mcms.Operation{}, + } + for _, transfer := range req.Transfers { + transferTx, err := link.TransferFrom(chain.DeployerKey, transfer.From, transfer.To, &transfer.Value) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("error constructing transfer tx: %w", err) + } + op := mcms.Operation{ + To: req.LinkTokenAddress, + Data: transferTx.Data(), + Value: big.NewInt(0), + ContractType: "LinkToken", + } + batch.Batch = append(batch.Batch, op) + + } + proposal, err := timelock.NewMCMSWithTimelockProposal( + "1", + req.ValidUntil, + []mcms.Signature{}, + req.OverrideRoot, + chainMetadata, + timelockAddresses, + "Value transfer proposal", + []timelock.BatchChainOperation{batch}, // 1 batch with all the transfers on the same batch + timelock.Schedule, + req.MinDelay.String(), + ) + if err != nil { + return deployment.ChangesetOutput{}, nil + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*proposal}, + }, nil + +} diff --git a/deployment/common/changeset/link_transfer_test.go b/deployment/common/changeset/link_transfer_test.go new file mode 100644 index 00000000000..978e482ce34 --- /dev/null +++ b/deployment/common/changeset/link_transfer_test.go @@ -0,0 +1,122 @@ +package changeset_test + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token" +) + +func TestLinkTransferTimelock(t *testing.T) { + t.Parallel() + ctx := context.Background() + lggr := logger.Test(t) + cfg := memory.MemoryEnvironmentConfig{ + Nodes: 1, + Chains: 2, + } + env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) + chainSelector := env.AllChainSelectors()[0] + chain := env.Chains[chainSelector] + // Deploy Value Token + resp, err := changeset.DeployLinkToken(env, chainSelector) + require.NoError(t, err) + require.NotNil(t, resp) + + // Deploy MCMS and Timelock + config := changeset.SingleGroupMCMS(t) + respTimelock, err := changeset.DeployMCMSWithTimelock(env, map[uint64]types.MCMSWithTimelockConfig{ + chainSelector: { + Canceller: config, + Bypasser: config, + Proposer: config, + TimelockExecutors: []common.Address{chain.DeployerKey.From}, + TimelockMinDelay: big.NewInt(0), + }, + }) + + require.NoError(t, err) + + addrs, err := respTimelock.AddressBook.AddressesForChain(chainSelector) + require.NoError(t, err) + require.Len(t, addrs, 4) + + timelockAddress := "" + linkAddress := "" + mcmsAddress := "" + for key, typeAndVer := range addrs { + if typeAndVer.Type == types.LinkToken { + linkAddress = key + } + if typeAndVer.Type == types.RBACTimelock { + timelockAddress = key + } + if typeAndVer.Type == types.ProposerManyChainMultisig { + mcmsAddress = key + } + } + + linkContract, err := link_token.NewLinkToken(common.HexToAddress(linkAddress), chain.Client) + require.NoError(t, err) + + // Transfer some funds + tx, err := linkContract.Transfer(chain.DeployerKey, common.HexToAddress(timelockAddress), big.NewInt(15000)) + require.NoError(t, err) + err = chain.Client.SendTransaction(ctx, tx) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + tx, err = linkContract.Transfer(chain.DeployerKey, common.HexToAddress(mcmsAddress), big.NewInt(15000)) + require.NoError(t, err) + err = chain.Client.SendTransaction(context.Background(), tx) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + + timelockContract, err := gethwrappers.NewRBACTimelock(common.HexToAddress(timelockAddress), chain.Client) + require.NoError(t, err) + timelocks := map[uint64]*gethwrappers.RBACTimelock{ + chainSelector: timelockContract, + } + // Apply the changest + _, err = changeset.ApplyChangesets(t, env, timelocks, []changeset.ChangesetApplication{ + // this produces proposals, ApplyChangesets will sign & execute them. + // in practice, signing and executing are separated processes. + { + Changeset: changeset.WrapChangeSet(changeset.LinkTransferTimelock), + Config: changeset.LinkTransferTimelockRequest{ + Transfers: []changeset.LinkTransfer{ + { + From: common.HexToAddress(mcmsAddress), + To: chain.DeployerKey.From, + Value: *big.NewInt(10000), + }, + { + From: common.HexToAddress(timelockAddress), + To: chain.DeployerKey.From, + Value: *big.NewInt(10000), + }, + }, + }, + }, + }) + require.NoError(t, err) + + // Check new balances + endBalance, err := linkContract.BalanceOf(&bind.CallOpts{Context: ctx}, chain.DeployerKey.From) + require.NoError(t, err) + expectedBalance := big.NewInt(20000) + require.Equal(t, expectedBalance, endBalance) +} From c46c5d4d28b141e0e67c0a40b47b07246d0b08e6 Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 4 Dec 2024 13:47:10 -0600 Subject: [PATCH 02/41] feat: link transfer and approval integration tests and changesets. --- deployment/common/changeset/link_approve.go | 96 +++++++++++++ .../common/changeset/link_approve_test.go | 126 ++++++++++++++++++ .../common/changeset/link_transfer_test.go | 73 +++++++--- deployment/environment.go | 2 +- 4 files changed, 275 insertions(+), 22 deletions(-) create mode 100644 deployment/common/changeset/link_approve.go create mode 100644 deployment/common/changeset/link_approve_test.go diff --git a/deployment/common/changeset/link_approve.go b/deployment/common/changeset/link_approve.go new file mode 100644 index 00000000000..c7c39c36c47 --- /dev/null +++ b/deployment/common/changeset/link_approve.go @@ -0,0 +1,96 @@ +package changeset + +import ( + "fmt" + "math/big" + "strings" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token" +) + +type LinkAllowances struct { + Spender common.Address + Allowance big.Int +} +type LinkApproveTimelockRequest struct { + Allowances []LinkAllowances + ChainSelector uint64 + LinkTokenAddress common.Address + TimelockAddress common.Address + MCMSAddress common.Address + ValidUntil uint32 // unix time until the proposal will be valid + MinDelay time.Duration // delay for timelock worker to execute the transfers. + OverrideRoot bool + StartingOpCount uint64 +} + +var _ deployment.ChangeSet[*LinkApproveTimelockRequest] = LinkApproveTimelock + +// packApprove packs the transferFrom method call data +func packApprove(parsedABI abi.ABI, spender common.Address, amount *big.Int) ([]byte, error) { + data, err := parsedABI.Pack("approve", spender, amount) + if err != nil { + return nil, fmt.Errorf("failed to pack transferFrom: %w", err) + } + return data, nil +} + +// LinkApproveTimelock takes the given approvals for token transfers and creates an mcms proposal for them +func LinkApproveTimelock(_ deployment.Environment, req *LinkApproveTimelockRequest) (deployment.ChangesetOutput, error) { + chainID := mcms.ChainIdentifier(req.ChainSelector) + chainMetadata := map[mcms.ChainIdentifier]mcms.ChainMetadata{ + chainID: {MCMAddress: req.MCMSAddress, StartingOpCount: req.StartingOpCount}, + } + timelockAddresses := map[mcms.ChainIdentifier]common.Address{ + chainID: req.TimelockAddress, + } + batch := timelock.BatchChainOperation{ + ChainIdentifier: chainID, + Batch: []mcms.Operation{}, + } + // Parse the ABI + parsedABI, err := abi.JSON(strings.NewReader(link_token.LinkTokenMetaData.ABI)) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to parse ABI: %w", err) + } + for _, transfer := range req.Allowances { + data, err := packApprove(parsedABI, transfer.Spender, &transfer.Allowance) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("error packing transferFrom data: %w", err) + } + op := mcms.Operation{ + To: req.LinkTokenAddress, + Data: data, + Value: big.NewInt(0), + ContractType: "LinkToken", + } + batch.Batch = append(batch.Batch, op) + + } + proposal, err := timelock.NewMCMSWithTimelockProposal( + "1", + req.ValidUntil, + []mcms.Signature{}, + req.OverrideRoot, + chainMetadata, + timelockAddresses, + "Value transfer proposal", + []timelock.BatchChainOperation{batch}, // 1 batch with all the transfers on the same batch + timelock.Schedule, + req.MinDelay.String(), + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*proposal}, + }, nil + +} diff --git a/deployment/common/changeset/link_approve_test.go b/deployment/common/changeset/link_approve_test.go new file mode 100644 index 00000000000..21df86af85f --- /dev/null +++ b/deployment/common/changeset/link_approve_test.go @@ -0,0 +1,126 @@ +package changeset_test + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token" +) + +// TestLinkApproveTimelock tests the LinkApproveTimelock changeset by approving some tokens and checking allowances. +func TestLinkApproveTimelock(t *testing.T) { + t.Parallel() + ctx := context.Background() + lggr := logger.Test(t) + cfg := memory.MemoryEnvironmentConfig{ + Nodes: 1, + Chains: 2, + } + env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) + chainSelector := env.AllChainSelectors()[0] + chain := env.Chains[chainSelector] + // Deploy Value Token + resp, err := changeset.DeployLinkToken(env, chainSelector) + require.NoError(t, err) + require.NotNil(t, resp) + require.NoError(t, env.ExistingAddresses.Merge(resp.AddressBook)) + + // Deploy MCMS and Timelock + config := changeset.SingleGroupMCMS(t) + respTimelock, err := changeset.DeployMCMSWithTimelock(env, map[uint64]types.MCMSWithTimelockConfig{ + chainSelector: { + Canceller: config, + Bypasser: config, + Proposer: config, + TimelockExecutors: []common.Address{chain.DeployerKey.From}, + TimelockMinDelay: big.NewInt(0), + }, + }) + require.NoError(t, env.ExistingAddresses.Merge(respTimelock.AddressBook)) + require.NoError(t, err) + + addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) + require.NoError(t, err) + require.Len(t, addrs, 5) + + timelockAddress := "" + linkAddress := "" + mcmsAddress := "" + for key, typeAndVer := range addrs { + if typeAndVer.Type == types.LinkToken { + linkAddress = key + } + if typeAndVer.Type == types.RBACTimelock { + timelockAddress = key + } + if typeAndVer.Type == types.ProposerManyChainMultisig { + mcmsAddress = key + } + } + + linkContract, err := link_token.NewLinkToken(common.HexToAddress(linkAddress), chain.Client) + require.NoError(t, err) + + // Transfer some funds + // Check if DeployerKey has minter permissions + tx, err := linkContract.GrantMintRole(chain.DeployerKey, chain.DeployerKey.From) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + + tx, err = linkContract.Mint(chain.DeployerKey, common.HexToAddress(timelockAddress), big.NewInt(750)) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + + tx, err = linkContract.Mint(chain.DeployerKey, common.HexToAddress(mcmsAddress), big.NewInt(750)) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + + timelockContract, err := gethwrappers.NewRBACTimelock(common.HexToAddress(timelockAddress), chain.Client) + require.NoError(t, err) + timelocks := map[uint64]*gethwrappers.RBACTimelock{ + chainSelector: timelockContract, + } + // Apply the changest + _, 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(changeset.LinkApproveTimelock), + Config: &changeset.LinkApproveTimelockRequest{ + Allowances: []changeset.LinkAllowances{ + { + Spender: chain.DeployerKey.From, + Allowance: *big.NewInt(5000), + }, + }, + ChainSelector: chainSelector, + LinkTokenAddress: common.HexToAddress(linkAddress), + TimelockAddress: common.HexToAddress(timelockAddress), + MCMSAddress: common.HexToAddress(mcmsAddress), + ValidUntil: 4131638958, + MinDelay: 0, + }, + }, + }) + require.NoError(t, err) + + // Check allowances + allowance, err := linkContract.Allowance(&bind.CallOpts{Context: ctx}, common.HexToAddress(timelockAddress), chain.DeployerKey.From) + require.NoError(t, err) + require.Equal(t, big.NewInt(5000), allowance) +} diff --git a/deployment/common/changeset/link_transfer_test.go b/deployment/common/changeset/link_transfer_test.go index 978e482ce34..94e6fcd5ca5 100644 --- a/deployment/common/changeset/link_transfer_test.go +++ b/deployment/common/changeset/link_transfer_test.go @@ -19,6 +19,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token" ) +// TestLinkTransferTimelock tests the LinkTransferTimelock changeset by func TestLinkTransferTimelock(t *testing.T) { t.Parallel() ctx := context.Background() @@ -34,6 +35,7 @@ func TestLinkTransferTimelock(t *testing.T) { resp, err := changeset.DeployLinkToken(env, chainSelector) require.NoError(t, err) require.NotNil(t, resp) + require.NoError(t, env.ExistingAddresses.Merge(resp.AddressBook)) // Deploy MCMS and Timelock config := changeset.SingleGroupMCMS(t) @@ -46,12 +48,12 @@ func TestLinkTransferTimelock(t *testing.T) { TimelockMinDelay: big.NewInt(0), }, }) - + require.NoError(t, env.ExistingAddresses.Merge(respTimelock.AddressBook)) require.NoError(t, err) - addrs, err := respTimelock.AddressBook.AddressesForChain(chainSelector) + addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) require.NoError(t, err) - require.Len(t, addrs, 4) + require.Len(t, addrs, 5) timelockAddress := "" linkAddress := "" @@ -71,16 +73,14 @@ func TestLinkTransferTimelock(t *testing.T) { linkContract, err := link_token.NewLinkToken(common.HexToAddress(linkAddress), chain.Client) require.NoError(t, err) - // Transfer some funds - tx, err := linkContract.Transfer(chain.DeployerKey, common.HexToAddress(timelockAddress), big.NewInt(15000)) - require.NoError(t, err) - err = chain.Client.SendTransaction(ctx, tx) + // Mint some funds + // grant minter permissions + tx, err := linkContract.GrantMintRole(chain.DeployerKey, chain.DeployerKey.From) require.NoError(t, err) _, err = deployment.ConfirmIfNoError(chain, tx, err) require.NoError(t, err) - tx, err = linkContract.Transfer(chain.DeployerKey, common.HexToAddress(mcmsAddress), big.NewInt(15000)) - require.NoError(t, err) - err = chain.Client.SendTransaction(context.Background(), tx) + + tx, err = linkContract.Mint(chain.DeployerKey, common.HexToAddress(timelockAddress), big.NewInt(750)) require.NoError(t, err) _, err = deployment.ConfirmIfNoError(chain, tx, err) require.NoError(t, err) @@ -90,25 +90,50 @@ func TestLinkTransferTimelock(t *testing.T) { timelocks := map[uint64]*gethwrappers.RBACTimelock{ chainSelector: timelockContract, } - // Apply the changest + // Apply the proposal for approving token transfers with 5k allowance _, err = changeset.ApplyChangesets(t, env, timelocks, []changeset.ChangesetApplication{ - // this produces proposals, ApplyChangesets will sign & execute them. + // the changeset produces proposals, ApplyChangesets will sign & execute them. // in practice, signing and executing are separated processes. { - Changeset: changeset.WrapChangeSet(changeset.LinkTransferTimelock), - Config: changeset.LinkTransferTimelockRequest{ - Transfers: []changeset.LinkTransfer{ + Changeset: changeset.WrapChangeSet(changeset.LinkApproveTimelock), + Config: &changeset.LinkApproveTimelockRequest{ + Allowances: []changeset.LinkAllowances{ { - From: common.HexToAddress(mcmsAddress), - To: chain.DeployerKey.From, - Value: *big.NewInt(10000), + Spender: chain.DeployerKey.From, + Allowance: *big.NewInt(5000), }, + }, + ChainSelector: chainSelector, + LinkTokenAddress: common.HexToAddress(linkAddress), + TimelockAddress: common.HexToAddress(timelockAddress), + MCMSAddress: common.HexToAddress(mcmsAddress), + ValidUntil: 4131638958, + MinDelay: 0, + }, + }, + }) + + // 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(changeset.LinkTransferTimelock), + Config: &changeset.LinkTransferTimelockRequest{ + Transfers: []changeset.LinkTransfer{ { - From: common.HexToAddress(timelockAddress), To: chain.DeployerKey.From, - Value: *big.NewInt(10000), + Value: *big.NewInt(500), }, }, + ChainSelector: chainSelector, + LinkTokenAddress: common.HexToAddress(linkAddress), + TimelockAddress: common.HexToAddress(timelockAddress), + MCMSAddress: common.HexToAddress(mcmsAddress), + ValidUntil: 4131638958, + MinDelay: 0, + OverrideRoot: true, + StartingOpCount: 1, // we need to set this to 1 to account for the previous mcsm proposal with 1 op. }, }, }) @@ -117,6 +142,12 @@ func TestLinkTransferTimelock(t *testing.T) { // Check new balances endBalance, err := linkContract.BalanceOf(&bind.CallOpts{Context: ctx}, chain.DeployerKey.From) require.NoError(t, err) - expectedBalance := big.NewInt(20000) + expectedBalance := big.NewInt(500) + require.Equal(t, expectedBalance, endBalance) + + // check timelock balance + endBalance, err = linkContract.BalanceOf(&bind.CallOpts{Context: ctx}, common.HexToAddress(timelockAddress)) + require.NoError(t, err) + expectedBalance = big.NewInt(250) require.Equal(t, expectedBalance, endBalance) } diff --git a/deployment/environment.go b/deployment/environment.go index d356148c225..0dcd2a16670 100644 --- a/deployment/environment.go +++ b/deployment/environment.go @@ -156,7 +156,7 @@ func MaybeDataErr(err error) error { var d rpc.DataError ok := errors.As(err, &d) if ok { - return d + return fmt.Errorf("%s: %v", d.Error(), d.ErrorData()) } return err } From 869cf6af4714eac27f54b43fb9d60c0e044f758d Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 4 Dec 2024 14:13:36 -0600 Subject: [PATCH 03/41] feat: rename files --- ...nk_approve.go => link_approve_timelock.go} | 0 ..._test.go => link_approve_timelock_test.go} | 0 ..._transfer.go => link_transfer_timelock.go} | 33 ++++++++++++------- ...test.go => link_transfer_timelock_test.go} | 0 4 files changed, 22 insertions(+), 11 deletions(-) rename deployment/common/changeset/{link_approve.go => link_approve_timelock.go} (100%) rename deployment/common/changeset/{link_approve_test.go => link_approve_timelock_test.go} (100%) rename deployment/common/changeset/{link_transfer.go => link_transfer_timelock.go} (68%) rename deployment/common/changeset/{link_transfer_test.go => link_transfer_timelock_test.go} (100%) diff --git a/deployment/common/changeset/link_approve.go b/deployment/common/changeset/link_approve_timelock.go similarity index 100% rename from deployment/common/changeset/link_approve.go rename to deployment/common/changeset/link_approve_timelock.go diff --git a/deployment/common/changeset/link_approve_test.go b/deployment/common/changeset/link_approve_timelock_test.go similarity index 100% rename from deployment/common/changeset/link_approve_test.go rename to deployment/common/changeset/link_approve_timelock_test.go diff --git a/deployment/common/changeset/link_transfer.go b/deployment/common/changeset/link_transfer_timelock.go similarity index 68% rename from deployment/common/changeset/link_transfer.go rename to deployment/common/changeset/link_transfer_timelock.go index f9e675e7287..fbafa7b41fc 100644 --- a/deployment/common/changeset/link_transfer.go +++ b/deployment/common/changeset/link_transfer_timelock.go @@ -3,8 +3,10 @@ package changeset import ( "fmt" "math/big" + "strings" "time" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" @@ -14,7 +16,6 @@ import ( ) type LinkTransfer struct { - From common.Address To common.Address Value big.Int } @@ -27,20 +28,25 @@ type LinkTransferTimelockRequest struct { ValidUntil uint32 // unix time until the proposal will be valid MinDelay time.Duration // delay for timelock worker to execute the transfers. OverrideRoot bool + StartingOpCount uint64 } var _ deployment.ChangeSet[*LinkTransferTimelockRequest] = LinkTransferTimelock -// LinkTransferTimelock takes the given link transfers and creates an MCMS proposal for them. -func LinkTransferTimelock(env deployment.Environment, req *LinkTransferTimelockRequest) (deployment.ChangesetOutput, error) { - chain := env.Chains[req.ChainSelector] - link, err := link_token.NewLinkToken(req.LinkTokenAddress, chain.Client) +// packTransferFrom packs the transferFrom method call data +func packTransferFrom(parsedABI abi.ABI, to common.Address, value *big.Int) ([]byte, error) { + data, err := parsedABI.Pack("transfer", to, value) if err != nil { - return deployment.ChangesetOutput{}, err + return nil, fmt.Errorf("failed to pack transferFrom: %w", err) } + return data, nil +} + +// LinkTransferTimelock takes the given link transfers and creates an MCMS proposal for them. +func LinkTransferTimelock(_ deployment.Environment, req *LinkTransferTimelockRequest) (deployment.ChangesetOutput, error) { chainID := mcms.ChainIdentifier(req.ChainSelector) chainMetadata := map[mcms.ChainIdentifier]mcms.ChainMetadata{ - chainID: {MCMAddress: req.MCMSAddress, StartingOpCount: 0}, + chainID: {MCMAddress: req.MCMSAddress, StartingOpCount: req.StartingOpCount}, } timelockAddresses := map[mcms.ChainIdentifier]common.Address{ chainID: req.TimelockAddress, @@ -49,14 +55,19 @@ func LinkTransferTimelock(env deployment.Environment, req *LinkTransferTimelockR ChainIdentifier: chainID, Batch: []mcms.Operation{}, } + // Parse the ABI + parsedABI, err := abi.JSON(strings.NewReader(link_token.LinkTokenMetaData.ABI)) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to parse ABI: %w", err) + } for _, transfer := range req.Transfers { - transferTx, err := link.TransferFrom(chain.DeployerKey, transfer.From, transfer.To, &transfer.Value) + data, err := packTransferFrom(parsedABI, transfer.To, &transfer.Value) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("error constructing transfer tx: %w", err) + return deployment.ChangesetOutput{}, fmt.Errorf("error packing transferFrom data: %w", err) } op := mcms.Operation{ To: req.LinkTokenAddress, - Data: transferTx.Data(), + Data: data, Value: big.NewInt(0), ContractType: "LinkToken", } @@ -76,7 +87,7 @@ func LinkTransferTimelock(env deployment.Environment, req *LinkTransferTimelockR req.MinDelay.String(), ) if err != nil { - return deployment.ChangesetOutput{}, nil + return deployment.ChangesetOutput{}, err } return deployment.ChangesetOutput{ Proposals: []timelock.MCMSWithTimelockProposal{*proposal}, diff --git a/deployment/common/changeset/link_transfer_test.go b/deployment/common/changeset/link_transfer_timelock_test.go similarity index 100% rename from deployment/common/changeset/link_transfer_test.go rename to deployment/common/changeset/link_transfer_timelock_test.go From c0f3969bd34a0438582884e3c3f9afd3313bd890 Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 4 Dec 2024 17:12:05 -0600 Subject: [PATCH 04/41] fix: use deployment.SimTransactOpts() to get tx data --- .../common/changeset/link_approve_timelock.go | 18 ++++++------ .../changeset/link_transfer_timelock.go | 28 ++++++------------- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/deployment/common/changeset/link_approve_timelock.go b/deployment/common/changeset/link_approve_timelock.go index c7c39c36c47..7b3adda72a4 100644 --- a/deployment/common/changeset/link_approve_timelock.go +++ b/deployment/common/changeset/link_approve_timelock.go @@ -3,7 +3,6 @@ package changeset import ( "fmt" "math/big" - "strings" "time" "github.com/ethereum/go-ethereum/accounts/abi" @@ -45,6 +44,11 @@ func packApprove(parsedABI abi.ABI, spender common.Address, amount *big.Int) ([] // LinkApproveTimelock takes the given approvals for token transfers and creates an mcms proposal for them func LinkApproveTimelock(_ deployment.Environment, req *LinkApproveTimelockRequest) (deployment.ChangesetOutput, error) { chainID := mcms.ChainIdentifier(req.ChainSelector) + linkContract, err := link_token.NewLinkToken(req.LinkTokenAddress, nil) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to get link contract: %w", err) + } + chainMetadata := map[mcms.ChainIdentifier]mcms.ChainMetadata{ chainID: {MCMAddress: req.MCMSAddress, StartingOpCount: req.StartingOpCount}, } @@ -55,19 +59,15 @@ func LinkApproveTimelock(_ deployment.Environment, req *LinkApproveTimelockReque ChainIdentifier: chainID, Batch: []mcms.Operation{}, } - // Parse the ABI - parsedABI, err := abi.JSON(strings.NewReader(link_token.LinkTokenMetaData.ABI)) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to parse ABI: %w", err) - } + for _, transfer := range req.Allowances { - data, err := packApprove(parsedABI, transfer.Spender, &transfer.Allowance) + tx, err := linkContract.Approve(deployment.SimTransactOpts(), transfer.Spender, &transfer.Allowance) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("error packing transferFrom data: %w", err) + return deployment.ChangesetOutput{}, fmt.Errorf("error packing approve tx data: %w", err) } op := mcms.Operation{ To: req.LinkTokenAddress, - Data: data, + Data: tx.Data(), Value: big.NewInt(0), ContractType: "LinkToken", } diff --git a/deployment/common/changeset/link_transfer_timelock.go b/deployment/common/changeset/link_transfer_timelock.go index fbafa7b41fc..c0ac76c95c9 100644 --- a/deployment/common/changeset/link_transfer_timelock.go +++ b/deployment/common/changeset/link_transfer_timelock.go @@ -3,10 +3,8 @@ package changeset import ( "fmt" "math/big" - "strings" "time" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" @@ -33,18 +31,14 @@ type LinkTransferTimelockRequest struct { var _ deployment.ChangeSet[*LinkTransferTimelockRequest] = LinkTransferTimelock -// packTransferFrom packs the transferFrom method call data -func packTransferFrom(parsedABI abi.ABI, to common.Address, value *big.Int) ([]byte, error) { - data, err := parsedABI.Pack("transfer", to, value) - if err != nil { - return nil, fmt.Errorf("failed to pack transferFrom: %w", err) - } - return data, nil -} - // LinkTransferTimelock takes the given link transfers and creates an MCMS proposal for them. func LinkTransferTimelock(_ deployment.Environment, req *LinkTransferTimelockRequest) (deployment.ChangesetOutput, error) { chainID := mcms.ChainIdentifier(req.ChainSelector) + linkContract, err := link_token.NewLinkToken(req.LinkTokenAddress, nil) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to get link contract: %w", err) + } + chainMetadata := map[mcms.ChainIdentifier]mcms.ChainMetadata{ chainID: {MCMAddress: req.MCMSAddress, StartingOpCount: req.StartingOpCount}, } @@ -55,19 +49,15 @@ func LinkTransferTimelock(_ deployment.Environment, req *LinkTransferTimelockReq ChainIdentifier: chainID, Batch: []mcms.Operation{}, } - // Parse the ABI - parsedABI, err := abi.JSON(strings.NewReader(link_token.LinkTokenMetaData.ABI)) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to parse ABI: %w", err) - } + for _, transfer := range req.Transfers { - data, err := packTransferFrom(parsedABI, transfer.To, &transfer.Value) + tx, err := linkContract.Transfer(deployment.SimTransactOpts(), transfer.To, &transfer.Value) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("error packing transferFrom data: %w", err) + return deployment.ChangesetOutput{}, fmt.Errorf("error packing transfer tx data: %w", err) } op := mcms.Operation{ To: req.LinkTokenAddress, - Data: data, + Data: tx.Data(), Value: big.NewInt(0), ContractType: "LinkToken", } From 239d7b7cc6850ec7aa5fd3ba7e5c05d1b0cb91d3 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 5 Dec 2024 06:19:20 -0600 Subject: [PATCH 05/41] fix: link contract creation --- deployment/common/changeset/link_approve_timelock.go | 5 +++-- deployment/common/changeset/link_transfer_timelock.go | 5 +++-- deployment/common/changeset/link_transfer_timelock_test.go | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/deployment/common/changeset/link_approve_timelock.go b/deployment/common/changeset/link_approve_timelock.go index 7b3adda72a4..91d07078446 100644 --- a/deployment/common/changeset/link_approve_timelock.go +++ b/deployment/common/changeset/link_approve_timelock.go @@ -42,9 +42,10 @@ func packApprove(parsedABI abi.ABI, spender common.Address, amount *big.Int) ([] } // LinkApproveTimelock takes the given approvals for token transfers and creates an mcms proposal for them -func LinkApproveTimelock(_ deployment.Environment, req *LinkApproveTimelockRequest) (deployment.ChangesetOutput, error) { +func LinkApproveTimelock(e deployment.Environment, req *LinkApproveTimelockRequest) (deployment.ChangesetOutput, error) { chainID := mcms.ChainIdentifier(req.ChainSelector) - linkContract, err := link_token.NewLinkToken(req.LinkTokenAddress, nil) + chain := e.Chains[req.ChainSelector] + linkContract, err := link_token.NewLinkToken(req.LinkTokenAddress, chain.Client) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to get link contract: %w", err) } diff --git a/deployment/common/changeset/link_transfer_timelock.go b/deployment/common/changeset/link_transfer_timelock.go index c0ac76c95c9..d282aa9597c 100644 --- a/deployment/common/changeset/link_transfer_timelock.go +++ b/deployment/common/changeset/link_transfer_timelock.go @@ -32,9 +32,10 @@ type LinkTransferTimelockRequest struct { var _ deployment.ChangeSet[*LinkTransferTimelockRequest] = LinkTransferTimelock // LinkTransferTimelock takes the given link transfers and creates an MCMS proposal for them. -func LinkTransferTimelock(_ deployment.Environment, req *LinkTransferTimelockRequest) (deployment.ChangesetOutput, error) { +func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockRequest) (deployment.ChangesetOutput, error) { chainID := mcms.ChainIdentifier(req.ChainSelector) - linkContract, err := link_token.NewLinkToken(req.LinkTokenAddress, nil) + chain := e.Chains[req.ChainSelector] + linkContract, err := link_token.NewLinkToken(req.LinkTokenAddress, chain.Client) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to get link contract: %w", err) } diff --git a/deployment/common/changeset/link_transfer_timelock_test.go b/deployment/common/changeset/link_transfer_timelock_test.go index 94e6fcd5ca5..38e1d39493a 100644 --- a/deployment/common/changeset/link_transfer_timelock_test.go +++ b/deployment/common/changeset/link_transfer_timelock_test.go @@ -90,7 +90,7 @@ func TestLinkTransferTimelock(t *testing.T) { timelocks := map[uint64]*gethwrappers.RBACTimelock{ chainSelector: timelockContract, } - // Apply the proposal for approving token transfers with 5k allowance + //Apply the proposal for approving token transfers with 5k allowance _, 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. @@ -112,6 +112,7 @@ func TestLinkTransferTimelock(t *testing.T) { }, }, }) + require.NoError(t, err) // Apply the changeset _, err = changeset.ApplyChangesets(t, env, timelocks, []changeset.ChangesetApplication{ From 9060af14a764c9f79534be031b332d5624428b65 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 5 Dec 2024 06:29:43 -0600 Subject: [PATCH 06/41] fix: remove approval changeset, not necessary for sending directly from the owner --- .../common/changeset/link_approve_timelock.go | 97 -------------- .../changeset/link_approve_timelock_test.go | 126 ------------------ .../changeset/link_transfer_timelock_test.go | 25 +--- 3 files changed, 1 insertion(+), 247 deletions(-) delete mode 100644 deployment/common/changeset/link_approve_timelock.go delete mode 100644 deployment/common/changeset/link_approve_timelock_test.go diff --git a/deployment/common/changeset/link_approve_timelock.go b/deployment/common/changeset/link_approve_timelock.go deleted file mode 100644 index 91d07078446..00000000000 --- a/deployment/common/changeset/link_approve_timelock.go +++ /dev/null @@ -1,97 +0,0 @@ -package changeset - -import ( - "fmt" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - - "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token" -) - -type LinkAllowances struct { - Spender common.Address - Allowance big.Int -} -type LinkApproveTimelockRequest struct { - Allowances []LinkAllowances - ChainSelector uint64 - LinkTokenAddress common.Address - TimelockAddress common.Address - MCMSAddress common.Address - ValidUntil uint32 // unix time until the proposal will be valid - MinDelay time.Duration // delay for timelock worker to execute the transfers. - OverrideRoot bool - StartingOpCount uint64 -} - -var _ deployment.ChangeSet[*LinkApproveTimelockRequest] = LinkApproveTimelock - -// packApprove packs the transferFrom method call data -func packApprove(parsedABI abi.ABI, spender common.Address, amount *big.Int) ([]byte, error) { - data, err := parsedABI.Pack("approve", spender, amount) - if err != nil { - return nil, fmt.Errorf("failed to pack transferFrom: %w", err) - } - return data, nil -} - -// LinkApproveTimelock takes the given approvals for token transfers and creates an mcms proposal for them -func LinkApproveTimelock(e deployment.Environment, req *LinkApproveTimelockRequest) (deployment.ChangesetOutput, error) { - chainID := mcms.ChainIdentifier(req.ChainSelector) - chain := e.Chains[req.ChainSelector] - linkContract, err := link_token.NewLinkToken(req.LinkTokenAddress, chain.Client) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to get link contract: %w", err) - } - - chainMetadata := map[mcms.ChainIdentifier]mcms.ChainMetadata{ - chainID: {MCMAddress: req.MCMSAddress, StartingOpCount: req.StartingOpCount}, - } - timelockAddresses := map[mcms.ChainIdentifier]common.Address{ - chainID: req.TimelockAddress, - } - batch := timelock.BatchChainOperation{ - ChainIdentifier: chainID, - Batch: []mcms.Operation{}, - } - - for _, transfer := range req.Allowances { - tx, err := linkContract.Approve(deployment.SimTransactOpts(), transfer.Spender, &transfer.Allowance) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("error packing approve tx data: %w", err) - } - op := mcms.Operation{ - To: req.LinkTokenAddress, - Data: tx.Data(), - Value: big.NewInt(0), - ContractType: "LinkToken", - } - batch.Batch = append(batch.Batch, op) - - } - proposal, err := timelock.NewMCMSWithTimelockProposal( - "1", - req.ValidUntil, - []mcms.Signature{}, - req.OverrideRoot, - chainMetadata, - timelockAddresses, - "Value transfer proposal", - []timelock.BatchChainOperation{batch}, // 1 batch with all the transfers on the same batch - timelock.Schedule, - req.MinDelay.String(), - ) - if err != nil { - return deployment.ChangesetOutput{}, err - } - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{*proposal}, - }, nil - -} diff --git a/deployment/common/changeset/link_approve_timelock_test.go b/deployment/common/changeset/link_approve_timelock_test.go deleted file mode 100644 index 21df86af85f..00000000000 --- a/deployment/common/changeset/link_approve_timelock_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package changeset_test - -import ( - "context" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/stretchr/testify/require" - "go.uber.org/zap/zapcore" - - "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/common/changeset" - "github.com/smartcontractkit/chainlink/deployment/common/types" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token" -) - -// TestLinkApproveTimelock tests the LinkApproveTimelock changeset by approving some tokens and checking allowances. -func TestLinkApproveTimelock(t *testing.T) { - t.Parallel() - ctx := context.Background() - lggr := logger.Test(t) - cfg := memory.MemoryEnvironmentConfig{ - Nodes: 1, - Chains: 2, - } - env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) - chainSelector := env.AllChainSelectors()[0] - chain := env.Chains[chainSelector] - // Deploy Value Token - resp, err := changeset.DeployLinkToken(env, chainSelector) - require.NoError(t, err) - require.NotNil(t, resp) - require.NoError(t, env.ExistingAddresses.Merge(resp.AddressBook)) - - // Deploy MCMS and Timelock - config := changeset.SingleGroupMCMS(t) - respTimelock, err := changeset.DeployMCMSWithTimelock(env, map[uint64]types.MCMSWithTimelockConfig{ - chainSelector: { - Canceller: config, - Bypasser: config, - Proposer: config, - TimelockExecutors: []common.Address{chain.DeployerKey.From}, - TimelockMinDelay: big.NewInt(0), - }, - }) - require.NoError(t, env.ExistingAddresses.Merge(respTimelock.AddressBook)) - require.NoError(t, err) - - addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) - require.NoError(t, err) - require.Len(t, addrs, 5) - - timelockAddress := "" - linkAddress := "" - mcmsAddress := "" - for key, typeAndVer := range addrs { - if typeAndVer.Type == types.LinkToken { - linkAddress = key - } - if typeAndVer.Type == types.RBACTimelock { - timelockAddress = key - } - if typeAndVer.Type == types.ProposerManyChainMultisig { - mcmsAddress = key - } - } - - linkContract, err := link_token.NewLinkToken(common.HexToAddress(linkAddress), chain.Client) - require.NoError(t, err) - - // Transfer some funds - // Check if DeployerKey has minter permissions - tx, err := linkContract.GrantMintRole(chain.DeployerKey, chain.DeployerKey.From) - require.NoError(t, err) - _, err = deployment.ConfirmIfNoError(chain, tx, err) - require.NoError(t, err) - - tx, err = linkContract.Mint(chain.DeployerKey, common.HexToAddress(timelockAddress), big.NewInt(750)) - require.NoError(t, err) - _, err = deployment.ConfirmIfNoError(chain, tx, err) - require.NoError(t, err) - - tx, err = linkContract.Mint(chain.DeployerKey, common.HexToAddress(mcmsAddress), big.NewInt(750)) - require.NoError(t, err) - _, err = deployment.ConfirmIfNoError(chain, tx, err) - require.NoError(t, err) - - timelockContract, err := gethwrappers.NewRBACTimelock(common.HexToAddress(timelockAddress), chain.Client) - require.NoError(t, err) - timelocks := map[uint64]*gethwrappers.RBACTimelock{ - chainSelector: timelockContract, - } - // Apply the changest - _, 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(changeset.LinkApproveTimelock), - Config: &changeset.LinkApproveTimelockRequest{ - Allowances: []changeset.LinkAllowances{ - { - Spender: chain.DeployerKey.From, - Allowance: *big.NewInt(5000), - }, - }, - ChainSelector: chainSelector, - LinkTokenAddress: common.HexToAddress(linkAddress), - TimelockAddress: common.HexToAddress(timelockAddress), - MCMSAddress: common.HexToAddress(mcmsAddress), - ValidUntil: 4131638958, - MinDelay: 0, - }, - }, - }) - require.NoError(t, err) - - // Check allowances - allowance, err := linkContract.Allowance(&bind.CallOpts{Context: ctx}, common.HexToAddress(timelockAddress), chain.DeployerKey.From) - require.NoError(t, err) - require.Equal(t, big.NewInt(5000), allowance) -} diff --git a/deployment/common/changeset/link_transfer_timelock_test.go b/deployment/common/changeset/link_transfer_timelock_test.go index 38e1d39493a..b57e28d1ab3 100644 --- a/deployment/common/changeset/link_transfer_timelock_test.go +++ b/deployment/common/changeset/link_transfer_timelock_test.go @@ -90,29 +90,6 @@ func TestLinkTransferTimelock(t *testing.T) { timelocks := map[uint64]*gethwrappers.RBACTimelock{ chainSelector: timelockContract, } - //Apply the proposal for approving token transfers with 5k allowance - _, 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(changeset.LinkApproveTimelock), - Config: &changeset.LinkApproveTimelockRequest{ - Allowances: []changeset.LinkAllowances{ - { - Spender: chain.DeployerKey.From, - Allowance: *big.NewInt(5000), - }, - }, - ChainSelector: chainSelector, - LinkTokenAddress: common.HexToAddress(linkAddress), - TimelockAddress: common.HexToAddress(timelockAddress), - MCMSAddress: common.HexToAddress(mcmsAddress), - ValidUntil: 4131638958, - MinDelay: 0, - }, - }, - }) - require.NoError(t, err) // Apply the changeset _, err = changeset.ApplyChangesets(t, env, timelocks, []changeset.ChangesetApplication{ @@ -134,7 +111,7 @@ func TestLinkTransferTimelock(t *testing.T) { ValidUntil: 4131638958, MinDelay: 0, OverrideRoot: true, - StartingOpCount: 1, // we need to set this to 1 to account for the previous mcsm proposal with 1 op. + StartingOpCount: 0, // we need to set this to 1 to account for the previous mcsm proposal with 1 op. }, }, }) From a1eaa94c3239f49269e1a89e63107492047a158c Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 5 Dec 2024 10:15:25 -0600 Subject: [PATCH 07/41] feat: make config accept a map of chain selectors for the proposal generation --- .../changeset/link_transfer_timelock.go | 81 +++++++++++-------- .../changeset/link_transfer_timelock_test.go | 33 +++++--- 2 files changed, 68 insertions(+), 46 deletions(-) diff --git a/deployment/common/changeset/link_transfer_timelock.go b/deployment/common/changeset/link_transfer_timelock.go index d282aa9597c..3c5b7ba0a13 100644 --- a/deployment/common/changeset/link_transfer_timelock.go +++ b/deployment/common/changeset/link_transfer_timelock.go @@ -18,53 +18,66 @@ type LinkTransfer struct { Value big.Int } type LinkTransferTimelockRequest struct { - Transfers []LinkTransfer - ChainSelector uint64 - LinkTokenAddress common.Address - TimelockAddress common.Address - MCMSAddress common.Address + Transfers map[uint64][]LinkTransfer + LinkTokenAddress map[uint64]common.Address + TimelockAddress map[uint64]common.Address + MCMSAddress map[uint64]common.Address ValidUntil uint32 // unix time until the proposal will be valid MinDelay time.Duration // delay for timelock worker to execute the transfers. OverrideRoot bool - StartingOpCount uint64 + StartingOpCount map[uint64]uint64 } var _ deployment.ChangeSet[*LinkTransferTimelockRequest] = LinkTransferTimelock // LinkTransferTimelock takes the given link transfers and creates an MCMS proposal for them. func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockRequest) (deployment.ChangesetOutput, error) { - chainID := mcms.ChainIdentifier(req.ChainSelector) - chain := e.Chains[req.ChainSelector] - linkContract, err := link_token.NewLinkToken(req.LinkTokenAddress, chain.Client) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to get link contract: %w", err) - } - - chainMetadata := map[mcms.ChainIdentifier]mcms.ChainMetadata{ - chainID: {MCMAddress: req.MCMSAddress, StartingOpCount: req.StartingOpCount}, - } - timelockAddresses := map[mcms.ChainIdentifier]common.Address{ - chainID: req.TimelockAddress, - } - batch := timelock.BatchChainOperation{ - ChainIdentifier: chainID, - Batch: []mcms.Operation{}, - } - - for _, transfer := range req.Transfers { - tx, err := linkContract.Transfer(deployment.SimTransactOpts(), transfer.To, &transfer.Value) + chainMetadata := map[mcms.ChainIdentifier]mcms.ChainMetadata{} + timelockAddresses := map[mcms.ChainIdentifier]common.Address{} + allBatches := []timelock.BatchChainOperation{} + for chainSelector := range req.Transfers { + if _, ok := req.LinkTokenAddress[chainSelector]; !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("missing link token address for chain %d", chainSelector) + } + if _, ok := req.TimelockAddress[chainSelector]; !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("missing timelock address for chain %d", chainSelector) + } + if _, ok := req.MCMSAddress[chainSelector]; !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("missing MCMS address for chain %d", chainSelector) + } + chainID := mcms.ChainIdentifier(chainSelector) + chain := e.Chains[chainSelector] + linkContract, err := link_token.NewLinkToken(req.LinkTokenAddress[chainSelector], chain.Client) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("error packing transfer tx data: %w", err) + return deployment.ChangesetOutput{}, fmt.Errorf("failed to get link contract: %w", err) + } + chainMetadata[chainID] = mcms.ChainMetadata{ + MCMAddress: req.MCMSAddress[chainSelector], + StartingOpCount: req.StartingOpCount[chainSelector], } - op := mcms.Operation{ - To: req.LinkTokenAddress, - Data: tx.Data(), - Value: big.NewInt(0), - ContractType: "LinkToken", + timelockAddresses[chainID] = req.TimelockAddress[chainSelector] + batch := timelock.BatchChainOperation{ + ChainIdentifier: chainID, + Batch: []mcms.Operation{}, } - batch.Batch = append(batch.Batch, op) + for _, transfer := range req.Transfers[chainSelector] { + tx, err := linkContract.Transfer(deployment.SimTransactOpts(), transfer.To, &transfer.Value) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("error packing transfer tx data: %w", err) + } + op := mcms.Operation{ + To: req.LinkTokenAddress[chainSelector], + Data: tx.Data(), + Value: big.NewInt(0), + ContractType: "LinkToken", + } + batch.Batch = append(batch.Batch, op) + + } + allBatches = append(allBatches, batch) } + proposal, err := timelock.NewMCMSWithTimelockProposal( "1", req.ValidUntil, @@ -73,7 +86,7 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockReq chainMetadata, timelockAddresses, "Value transfer proposal", - []timelock.BatchChainOperation{batch}, // 1 batch with all the transfers on the same batch + allBatches, timelock.Schedule, req.MinDelay.String(), ) diff --git a/deployment/common/changeset/link_transfer_timelock_test.go b/deployment/common/changeset/link_transfer_timelock_test.go index b57e28d1ab3..eba4defef4b 100644 --- a/deployment/common/changeset/link_transfer_timelock_test.go +++ b/deployment/common/changeset/link_transfer_timelock_test.go @@ -98,20 +98,29 @@ func TestLinkTransferTimelock(t *testing.T) { { Changeset: changeset.WrapChangeSet(changeset.LinkTransferTimelock), Config: &changeset.LinkTransferTimelockRequest{ - Transfers: []changeset.LinkTransfer{ - { - To: chain.DeployerKey.From, - Value: *big.NewInt(500), + Transfers: map[uint64][]changeset.LinkTransfer{ + chainSelector: { + { + To: chain.DeployerKey.From, + Value: *big.NewInt(500), + }, }, }, - ChainSelector: chainSelector, - LinkTokenAddress: common.HexToAddress(linkAddress), - TimelockAddress: common.HexToAddress(timelockAddress), - MCMSAddress: common.HexToAddress(mcmsAddress), - ValidUntil: 4131638958, - MinDelay: 0, - OverrideRoot: true, - StartingOpCount: 0, // we need to set this to 1 to account for the previous mcsm proposal with 1 op. + LinkTokenAddress: map[uint64]common.Address{ + chainSelector: common.HexToAddress(linkAddress), + }, + TimelockAddress: map[uint64]common.Address{ + chainSelector: common.HexToAddress(timelockAddress), + }, + MCMSAddress: map[uint64]common.Address{ + chainSelector: common.HexToAddress(mcmsAddress), + }, + ValidUntil: 4131638958, + MinDelay: 0, + OverrideRoot: true, + StartingOpCount: map[uint64]uint64{ + chainSelector: 0, + }, }, }, }) From f9fd24c1017d851f7ac03f1cb27cfb542544cfb9 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 5 Dec 2024 10:53:51 -0600 Subject: [PATCH 08/41] fix: params on deploy link --- deployment/common/changeset/link_transfer_timelock_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/common/changeset/link_transfer_timelock_test.go b/deployment/common/changeset/link_transfer_timelock_test.go index eba4defef4b..15fc36e32fa 100644 --- a/deployment/common/changeset/link_transfer_timelock_test.go +++ b/deployment/common/changeset/link_transfer_timelock_test.go @@ -32,7 +32,7 @@ func TestLinkTransferTimelock(t *testing.T) { chainSelector := env.AllChainSelectors()[0] chain := env.Chains[chainSelector] // Deploy Value Token - resp, err := changeset.DeployLinkToken(env, chainSelector) + resp, err := changeset.DeployLinkToken(env, []uint64{chainSelector}) require.NoError(t, err) require.NotNil(t, resp) require.NoError(t, env.ExistingAddresses.Merge(resp.AddressBook)) From b5d343a6d8e2378b872bdc8e9b7ed60c4239941d Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 5 Dec 2024 12:11:59 -0600 Subject: [PATCH 09/41] fix: simplify config args by using state helper functions. --- .../changeset/link_transfer_timelock.go | 44 ++++++++++--------- .../changeset/link_transfer_timelock_test.go | 40 +++++------------ 2 files changed, 35 insertions(+), 49 deletions(-) diff --git a/deployment/common/changeset/link_transfer_timelock.go b/deployment/common/changeset/link_transfer_timelock.go index 3c5b7ba0a13..ab5c3e316f7 100644 --- a/deployment/common/changeset/link_transfer_timelock.go +++ b/deployment/common/changeset/link_transfer_timelock.go @@ -6,6 +6,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" @@ -18,14 +19,11 @@ type LinkTransfer struct { Value big.Int } type LinkTransferTimelockRequest struct { - Transfers map[uint64][]LinkTransfer - LinkTokenAddress map[uint64]common.Address - TimelockAddress map[uint64]common.Address - MCMSAddress map[uint64]common.Address - ValidUntil uint32 // unix time until the proposal will be valid - MinDelay time.Duration // delay for timelock worker to execute the transfers. - OverrideRoot bool - StartingOpCount map[uint64]uint64 + Transfers map[uint64][]LinkTransfer + ValidUntil uint32 // unix time until the proposal will be valid + MinDelay time.Duration // delay for timelock worker to execute the transfers. + OverrideRoot bool + StartingOpCount map[uint64]uint64 } var _ deployment.ChangeSet[*LinkTransferTimelockRequest] = LinkTransferTimelock @@ -36,26 +34,30 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockReq timelockAddresses := map[mcms.ChainIdentifier]common.Address{} allBatches := []timelock.BatchChainOperation{} for chainSelector := range req.Transfers { - if _, ok := req.LinkTokenAddress[chainSelector]; !ok { - return deployment.ChangesetOutput{}, fmt.Errorf("missing link token address for chain %d", chainSelector) - } - if _, ok := req.TimelockAddress[chainSelector]; !ok { - return deployment.ChangesetOutput{}, fmt.Errorf("missing timelock address for chain %d", chainSelector) - } - if _, ok := req.MCMSAddress[chainSelector]; !ok { - return deployment.ChangesetOutput{}, fmt.Errorf("missing MCMS address for chain %d", chainSelector) - } chainID := mcms.ChainIdentifier(chainSelector) chain := e.Chains[chainSelector] - linkContract, err := link_token.NewLinkToken(req.LinkTokenAddress[chainSelector], chain.Client) + addrs, err := e.ExistingAddresses.AddressesForChain(chainSelector) + linkState, err := LoadLinkTokenState(chain, addrs) + if err != nil { + return deployment.ChangesetOutput{}, err + } + linkAddress := linkState.LinkToken.Address() + mcmsState, err := LoadMCMSWithTimelockState(chain, addrs) + if err != nil { + return deployment.ChangesetOutput{}, err + } + mcmAddress := mcmsState.ProposerMcm.Address() + timelockAddress := mcmsState.Timelock.Address() + + linkContract, err := link_token.NewLinkToken(linkAddress, chain.Client) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to get link contract: %w", err) } chainMetadata[chainID] = mcms.ChainMetadata{ - MCMAddress: req.MCMSAddress[chainSelector], + MCMAddress: mcmAddress, StartingOpCount: req.StartingOpCount[chainSelector], } - timelockAddresses[chainID] = req.TimelockAddress[chainSelector] + timelockAddresses[chainID] = timelockAddress batch := timelock.BatchChainOperation{ ChainIdentifier: chainID, Batch: []mcms.Operation{}, @@ -67,7 +69,7 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockReq return deployment.ChangesetOutput{}, fmt.Errorf("error packing transfer tx data: %w", err) } op := mcms.Operation{ - To: req.LinkTokenAddress[chainSelector], + To: linkAddress, Data: tx.Data(), Value: big.NewInt(0), ContractType: "LinkToken", diff --git a/deployment/common/changeset/link_transfer_timelock_test.go b/deployment/common/changeset/link_transfer_timelock_test.go index 15fc36e32fa..ca2a6f356c8 100644 --- a/deployment/common/changeset/link_transfer_timelock_test.go +++ b/deployment/common/changeset/link_transfer_timelock_test.go @@ -7,11 +7,12 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/smartcontractkit/chainlink/deployment/common/types" @@ -55,22 +56,14 @@ func TestLinkTransferTimelock(t *testing.T) { require.NoError(t, err) require.Len(t, addrs, 5) - timelockAddress := "" - linkAddress := "" - mcmsAddress := "" - for key, typeAndVer := range addrs { - if typeAndVer.Type == types.LinkToken { - linkAddress = key - } - if typeAndVer.Type == types.RBACTimelock { - timelockAddress = key - } - if typeAndVer.Type == types.ProposerManyChainMultisig { - mcmsAddress = key - } - } + mcmsState, err := changeset.LoadMCMSWithTimelockState(chain, addrs) + require.NoError(t, err) + linkState, err := changeset.LoadLinkTokenState(chain, addrs) + require.NoError(t, err) + linkAddress := linkState.LinkToken.Address() + timelockAddress := mcmsState.Timelock.Address() - linkContract, err := link_token.NewLinkToken(common.HexToAddress(linkAddress), chain.Client) + linkContract, err := link_token.NewLinkToken(linkAddress, chain.Client) require.NoError(t, err) // Mint some funds @@ -80,12 +73,12 @@ func TestLinkTransferTimelock(t *testing.T) { _, err = deployment.ConfirmIfNoError(chain, tx, err) require.NoError(t, err) - tx, err = linkContract.Mint(chain.DeployerKey, common.HexToAddress(timelockAddress), big.NewInt(750)) + tx, err = linkContract.Mint(chain.DeployerKey, timelockAddress, big.NewInt(750)) require.NoError(t, err) _, err = deployment.ConfirmIfNoError(chain, tx, err) require.NoError(t, err) - timelockContract, err := gethwrappers.NewRBACTimelock(common.HexToAddress(timelockAddress), chain.Client) + timelockContract, err := gethwrappers.NewRBACTimelock(timelockAddress, chain.Client) require.NoError(t, err) timelocks := map[uint64]*gethwrappers.RBACTimelock{ chainSelector: timelockContract, @@ -106,15 +99,6 @@ func TestLinkTransferTimelock(t *testing.T) { }, }, }, - LinkTokenAddress: map[uint64]common.Address{ - chainSelector: common.HexToAddress(linkAddress), - }, - TimelockAddress: map[uint64]common.Address{ - chainSelector: common.HexToAddress(timelockAddress), - }, - MCMSAddress: map[uint64]common.Address{ - chainSelector: common.HexToAddress(mcmsAddress), - }, ValidUntil: 4131638958, MinDelay: 0, OverrideRoot: true, @@ -133,7 +117,7 @@ func TestLinkTransferTimelock(t *testing.T) { require.Equal(t, expectedBalance, endBalance) // check timelock balance - endBalance, err = linkContract.BalanceOf(&bind.CallOpts{Context: ctx}, common.HexToAddress(timelockAddress)) + endBalance, err = linkContract.BalanceOf(&bind.CallOpts{Context: ctx}, timelockAddress) require.NoError(t, err) expectedBalance = big.NewInt(250) require.Equal(t, expectedBalance, endBalance) From c54218f165835468a20d9e43f096caaed2c6e183 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 5 Dec 2024 12:20:05 -0600 Subject: [PATCH 10/41] fix: use pointer for value --- deployment/common/changeset/link_transfer_timelock.go | 4 ++-- deployment/common/changeset/link_transfer_timelock_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deployment/common/changeset/link_transfer_timelock.go b/deployment/common/changeset/link_transfer_timelock.go index ab5c3e316f7..6dbdba8e21a 100644 --- a/deployment/common/changeset/link_transfer_timelock.go +++ b/deployment/common/changeset/link_transfer_timelock.go @@ -16,7 +16,7 @@ import ( type LinkTransfer struct { To common.Address - Value big.Int + Value *big.Int } type LinkTransferTimelockRequest struct { Transfers map[uint64][]LinkTransfer @@ -64,7 +64,7 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockReq } for _, transfer := range req.Transfers[chainSelector] { - tx, err := linkContract.Transfer(deployment.SimTransactOpts(), transfer.To, &transfer.Value) + tx, err := linkContract.Transfer(deployment.SimTransactOpts(), transfer.To, transfer.Value) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("error packing transfer tx data: %w", err) } diff --git a/deployment/common/changeset/link_transfer_timelock_test.go b/deployment/common/changeset/link_transfer_timelock_test.go index ca2a6f356c8..3874998585d 100644 --- a/deployment/common/changeset/link_transfer_timelock_test.go +++ b/deployment/common/changeset/link_transfer_timelock_test.go @@ -95,7 +95,7 @@ func TestLinkTransferTimelock(t *testing.T) { chainSelector: { { To: chain.DeployerKey.From, - Value: *big.NewInt(500), + Value: big.NewInt(500), }, }, }, From 9dca3be71e9f8163f1e49a02dd67b1a96694200f Mon Sep 17 00:00:00 2001 From: Pablo Date: Fri, 6 Dec 2024 14:04:11 -0600 Subject: [PATCH 11/41] feat: add mint permissions and minting link changeset --- .../common/changeset/deploy_link_token.go | 2 +- .../common/changeset/mint_link_timelock.go | 50 ++++++++++++ .../changeset/mint_link_timelock_test.go | 79 +++++++++++++++++++ 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 deployment/common/changeset/mint_link_timelock.go create mode 100644 deployment/common/changeset/mint_link_timelock_test.go diff --git a/deployment/common/changeset/deploy_link_token.go b/deployment/common/changeset/deploy_link_token.go index 5728e977c47..67d5fa9a681 100644 --- a/deployment/common/changeset/deploy_link_token.go +++ b/deployment/common/changeset/deploy_link_token.go @@ -12,7 +12,7 @@ import ( var _ deployment.ChangeSet[[]uint64] = DeployLinkToken -// DeployLinkToken deploys a link token contract to the chain identified by the chainSelector. +// DeployLinkToken deploys a link token contract to the chain identified by the ChainSelector. func DeployLinkToken(e deployment.Environment, chains []uint64) (deployment.ChangesetOutput, error) { for _, chain := range chains { _, ok := e.Chains[chain] diff --git a/deployment/common/changeset/mint_link_timelock.go b/deployment/common/changeset/mint_link_timelock.go new file mode 100644 index 00000000000..2462bf38c07 --- /dev/null +++ b/deployment/common/changeset/mint_link_timelock.go @@ -0,0 +1,50 @@ +package changeset + +import ( + "math/big" + + "github.com/smartcontractkit/chainlink/deployment" +) + +type MintLinkTimelockRequest struct { + Amount *big.Int + ChainSelector uint64 +} + +var _ deployment.ChangeSet[*MintLinkTimelockRequest] = MintLinkTimelock + +// MintLinkTimelock mints LINK to the timelock contract. +func MintLinkTimelock(e deployment.Environment, req *MintLinkTimelockRequest) (deployment.ChangesetOutput, error) { + + chain := e.Chains[req.ChainSelector] + addresses, err := e.ExistingAddresses.AddressesForChain(req.ChainSelector) + if err != nil { + return deployment.ChangesetOutput{}, err + } + linkState, err := LoadLinkTokenState(chain, addresses) + if err != nil { + return deployment.ChangesetOutput{}, err + } + timelockState, err := LoadMCMSWithTimelockState(chain, addresses) + if err != nil { + return deployment.ChangesetOutput{}, err + } + tx, err := linkState.LinkToken.GrantMintRole(chain.DeployerKey, chain.DeployerKey.From) + if err != nil { + return deployment.ChangesetOutput{}, err + } + _, err = deployment.ConfirmIfNoError(chain, tx, err) + if err != nil { + return deployment.ChangesetOutput{}, err + } + tx, err = linkState.LinkToken.Mint(chain.DeployerKey, timelockState.Timelock.Address(), req.Amount) + if err != nil { + return deployment.ChangesetOutput{}, err + } + _, err = deployment.ConfirmIfNoError(chain, tx, err) + if err != nil { + return deployment.ChangesetOutput{}, err + } + return deployment.ChangesetOutput{}, nil + +} diff --git a/deployment/common/changeset/mint_link_timelock_test.go b/deployment/common/changeset/mint_link_timelock_test.go new file mode 100644 index 00000000000..7a4e71ef7ed --- /dev/null +++ b/deployment/common/changeset/mint_link_timelock_test.go @@ -0,0 +1,79 @@ +package changeset_test + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token" +) + +// TestMintLinkTimelock tests the MintLinkTimelock changeset +func TestMintLinkTimelock(t *testing.T) { + t.Parallel() + ctx := context.Background() + lggr := logger.Test(t) + cfg := memory.MemoryEnvironmentConfig{ + Nodes: 1, + Chains: 2, + } + env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) + chainSelector := env.AllChainSelectors()[0] + chain := env.Chains[chainSelector] + + // Deploy Link Token + resp, err := changeset.DeployLinkToken(env, []uint64{chainSelector}) + require.NoError(t, err) + require.NotNil(t, resp) + require.NoError(t, env.ExistingAddresses.Merge(resp.AddressBook)) + + // Deploy MCMS and Timelock + config := changeset.SingleGroupMCMS(t) + respTimelock, err := changeset.DeployMCMSWithTimelock(env, map[uint64]types.MCMSWithTimelockConfig{ + chainSelector: { + Canceller: config, + Bypasser: config, + Proposer: config, + TimelockExecutors: []common.Address{chain.DeployerKey.From}, + TimelockMinDelay: big.NewInt(0), + }, + }) + require.NoError(t, env.ExistingAddresses.Merge(respTimelock.AddressBook)) + require.NoError(t, err) + + addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) + require.NoError(t, err) + require.Len(t, addrs, 5) + + mcmsState, err := changeset.LoadMCMSWithTimelockState(chain, addrs) + require.NoError(t, err) + linkState, err := changeset.LoadLinkTokenState(chain, addrs) + require.NoError(t, err) + linkAddress := linkState.LinkToken.Address() + timelockAddress := mcmsState.Timelock.Address() + + linkContract, err := link_token.NewLinkToken(linkAddress, chain.Client) + require.NoError(t, err) + + // Mint some funds + _, err = changeset.MintLinkTimelock(env, &changeset.MintLinkTimelockRequest{ + ChainSelector: chainSelector, + Amount: big.NewInt(7568), + }) + require.NoError(t, err) + + // check timelock balance + endBalance, err := linkContract.BalanceOf(&bind.CallOpts{Context: ctx}, timelockAddress) + require.NoError(t, err) + expectedBalance := big.NewInt(7568) + require.Equal(t, expectedBalance, endBalance) +} From 4d007c307cef205b1ae25c121961e962d2139b2c Mon Sep 17 00:00:00 2001 From: Akhil Chainani Date: Fri, 6 Dec 2024 18:52:25 -0500 Subject: [PATCH 12/41] Deploy call proxy instead of using deployer executor keys --- .../ccip/changeset/cs_add_chain_test.go | 9 ++--- .../ccip/changeset/cs_deploy_chain_test.go | 9 ++--- deployment/ccip/changeset/test_helpers.go | 9 ++--- deployment/common/changeset/internal/mcms.go | 39 +++++++++++++++++-- .../common/changeset/internal/mcms_test.go | 12 ++---- .../transfer_to_mcms_with_timelock_test.go | 9 ++--- deployment/common/types/types.go | 11 +++--- .../changeset/accept_ownership_test.go | 9 ++--- deployment/keystone/changeset/helpers_test.go | 9 ++--- .../testsetups/ccip/test_helpers.go | 9 ++--- 10 files changed, 72 insertions(+), 53 deletions(-) diff --git a/deployment/ccip/changeset/cs_add_chain_test.go b/deployment/ccip/changeset/cs_add_chain_test.go index f6e1c04c469..52127f7af02 100644 --- a/deployment/ccip/changeset/cs_add_chain_test.go +++ b/deployment/ccip/changeset/cs_add_chain_test.go @@ -52,11 +52,10 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) cfg := commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: e.Env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), } e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ { diff --git a/deployment/ccip/changeset/cs_deploy_chain_test.go b/deployment/ccip/changeset/cs_deploy_chain_test.go index 234d73cc4b5..646575dcaa4 100644 --- a/deployment/ccip/changeset/cs_deploy_chain_test.go +++ b/deployment/ccip/changeset/cs_deploy_chain_test.go @@ -31,11 +31,10 @@ func TestDeployChainContractsChangeset(t *testing.T) { cfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) for _, chain := range e.AllChainSelectors() { cfg[chain] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: e.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), } } e, err = commonchangeset.ApplyChangesets(t, e, nil, []commonchangeset.ChangesetApplication{ diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 1ee8b0d0e42..09156717f99 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -279,11 +279,10 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) for _, c := range e.Env.AllChainSelectors() { mcmsCfg[c] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: e.Env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), } } var ( diff --git a/deployment/common/changeset/internal/mcms.go b/deployment/common/changeset/internal/mcms.go index 281f43924f4..e7cf4aa1489 100644 --- a/deployment/common/changeset/internal/mcms.go +++ b/deployment/common/changeset/internal/mcms.go @@ -106,10 +106,10 @@ func DeployMCMSWithTimelockContracts( // TODO: Could expose this as config? // Or keep this enforced to follow the same pattern? chain.DeployerKey.From, - []common.Address{proposer.Address}, // proposers - config.TimelockExecutors, //executors - []common.Address{canceller.Address}, // cancellers - []common.Address{bypasser.Address}, // bypassers + []common.Address{proposer.Address}, // proposers + []common.Address{}, // executors + []common.Address{canceller.Address, proposer.Address, bypasser.Address}, // cancellers + []common.Address{bypasser.Address}, // bypassers ) return deployment.ContractDeploy[*owner_helpers.RBACTimelock]{ timelock, cc, tx2, deployment.NewTypeAndVersion(types.RBACTimelock, deployment.Version1_0_0), err2, @@ -119,6 +119,37 @@ func DeployMCMSWithTimelockContracts( lggr.Errorw("Failed to deploy timelock", "chain", chain.String(), "err", err) return nil, err } + + callProxy, err := deployment.DeployContract(lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*owner_helpers.CallProxy] { + callProxy, tx2, cc, err2 := owner_helpers.DeployCallProxy( + chain.DeployerKey, + chain.Client, + timelock.Address, + ) + return deployment.ContractDeploy[*owner_helpers.CallProxy]{ + callProxy, cc, tx2, deployment.NewTypeAndVersion(types.CallProxy, deployment.Version1_0_0), err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy call proxy", "chain", chain.String(), "err", err) + return nil, err + } + + grantRoleTx, err := timelock.Contract.GrantRole( + chain.DeployerKey, + v1_0.EXECUTOR_ROLE.ID, + callProxy.Address, + ) + if err != nil { + lggr.Errorw("Failed to grant timelock executor role", "chain", chain.String(), "err", err) + return nil, err + } + + if _, err := deployment.ConfirmIfNoError(chain, grantRoleTx, err); err != nil { + lggr.Errorw("Failed to grant timelock executor role", "chain", chain.String(), "err", err) + return nil, err + } // We grant the timelock the admin role on the MCMS contracts. tx, err := timelock.Contract.GrantRole(chain.DeployerKey, v1_0.ADMIN_ROLE.ID, timelock.Address) diff --git a/deployment/common/changeset/internal/mcms_test.go b/deployment/common/changeset/internal/mcms_test.go index 2269911f4cd..10fb1d980de 100644 --- a/deployment/common/changeset/internal/mcms_test.go +++ b/deployment/common/changeset/internal/mcms_test.go @@ -5,7 +5,6 @@ import ( "math/big" "testing" - "github.com/ethereum/go-ethereum/common" chainsel "github.com/smartcontractkit/chain-selectors" "github.com/stretchr/testify/require" @@ -37,18 +36,15 @@ func TestDeployMCMSWithTimelockContracts(t *testing.T) { _, err := internal.DeployMCMSWithTimelockContracts(lggr, chains[chainsel.TEST_90000001.Selector], ab, types.MCMSWithTimelockConfig{ - Canceller: changeset.SingleGroupMCMS(t), - Bypasser: changeset.SingleGroupMCMS(t), - Proposer: changeset.SingleGroupMCMS(t), - TimelockExecutors: []common.Address{ - chains[chainsel.TEST_90000001.Selector].DeployerKey.From, - }, + Canceller: changeset.SingleGroupMCMS(t), + Bypasser: changeset.SingleGroupMCMS(t), + Proposer: changeset.SingleGroupMCMS(t), TimelockMinDelay: big.NewInt(0), }) require.NoError(t, err) addresses, err := ab.AddressesForChain(chainsel.TEST_90000001.Selector) require.NoError(t, err) - require.Len(t, addresses, 4) + require.Len(t, addresses, 5) mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chains[chainsel.TEST_90000001.Selector], addresses) require.NoError(t, err) v, err := mcmsState.GenerateMCMSWithTimelockView() diff --git a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go index 6cdff286707..7a07270c6f2 100644 --- a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go +++ b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go @@ -30,11 +30,10 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { Changeset: WrapChangeSet(DeployMCMSWithTimelock), Config: map[uint64]types.MCMSWithTimelockConfig{ chain1: { - Canceller: SingleGroupMCMS(t), - Bypasser: SingleGroupMCMS(t), - Proposer: SingleGroupMCMS(t), - TimelockExecutors: e.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: SingleGroupMCMS(t), + Bypasser: SingleGroupMCMS(t), + Proposer: SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), }, }, }, diff --git a/deployment/common/types/types.go b/deployment/common/types/types.go index 386ef8fbb36..0f04421af43 100644 --- a/deployment/common/types/types.go +++ b/deployment/common/types/types.go @@ -5,7 +5,6 @@ import ( "math/big" "time" - "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" "github.com/smartcontractkit/chainlink/deployment" @@ -16,6 +15,7 @@ const ( CancellerManyChainMultisig deployment.ContractType = "CancellerManyChainMultiSig" ProposerManyChainMultisig deployment.ContractType = "ProposerManyChainMultiSig" RBACTimelock deployment.ContractType = "RBACTimelock" + CallProxy deployment.ContractType = "CallProxy" // LinkToken is the burn/mint link token. It should be used everywhere for // new deployments. Corresponds to // https://github.com/smartcontractkit/chainlink/blob/develop/core/gethwrappers/shared/generated/link_token/link_token.go#L34 @@ -29,11 +29,10 @@ const ( ) type MCMSWithTimelockConfig struct { - Canceller config.Config - Bypasser config.Config - Proposer config.Config - TimelockExecutors []common.Address - TimelockMinDelay *big.Int + Canceller config.Config + Bypasser config.Config + Proposer config.Config + TimelockMinDelay *big.Int } type OCRParameters struct { diff --git a/deployment/keystone/changeset/accept_ownership_test.go b/deployment/keystone/changeset/accept_ownership_test.go index f205adda496..df3bfee48ec 100644 --- a/deployment/keystone/changeset/accept_ownership_test.go +++ b/deployment/keystone/changeset/accept_ownership_test.go @@ -42,11 +42,10 @@ func TestAcceptAllOwnership(t *testing.T) { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), Config: map[uint64]types.MCMSWithTimelockConfig{ registrySel: { - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), }, }, }, diff --git a/deployment/keystone/changeset/helpers_test.go b/deployment/keystone/changeset/helpers_test.go index d4435d8f7a6..54686fb396b 100644 --- a/deployment/keystone/changeset/helpers_test.go +++ b/deployment/keystone/changeset/helpers_test.go @@ -265,11 +265,10 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { for sel := range env.Chains { t.Logf("Enabling MCMS on chain %d", sel) timelockCfgs[sel] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), } } env, err = commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ diff --git a/integration-tests/testsetups/ccip/test_helpers.go b/integration-tests/testsetups/ccip/test_helpers.go index 4d51093d419..e8709343f38 100644 --- a/integration-tests/testsetups/ccip/test_helpers.go +++ b/integration-tests/testsetups/ccip/test_helpers.go @@ -147,11 +147,10 @@ func NewLocalDevEnvironment( mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) for _, c := range env.AllChainSelectors() { mcmsCfg[c] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), } } // Need to deploy prerequisites first so that we can form the USDC config From 101b9fde81fc293b9ffbf14fa342357727c4def4 Mon Sep 17 00:00:00 2001 From: Akhil Chainani Date: Sun, 8 Dec 2024 13:15:10 -0500 Subject: [PATCH 13/41] inject call proxies in execution methods --- .../ccip/changeset/accept_ownership_test.go | 6 ++++- .../changeset/cs_active_candidate_test.go | 12 ++++++--- .../ccip/changeset/cs_add_chain_test.go | 11 ++++++-- .../ccip/changeset/cs_deploy_chain_test.go | 2 +- deployment/ccip/changeset/test_helpers.go | 8 +++--- .../changeset/deploy_link_token_test.go | 2 +- deployment/common/changeset/internal/mcms.go | 2 ++ .../common/changeset/mcms_test_helpers.go | 5 ++-- deployment/common/changeset/state.go | 18 ++++++++++++- deployment/common/changeset/test_helpers.go | 9 +++++-- .../transfer_to_mcms_with_timelock_test.go | 4 ++- deployment/common/view/v1_0/mcms.go | 26 ++++++++++++++++--- .../changeset/accept_ownership_test.go | 4 ++- .../changeset/deploy_forwarder_test.go | 5 +++- .../keystone/changeset/deploy_ocr3_test.go | 5 +++- deployment/keystone/changeset/helpers_test.go | 6 ++--- .../smoke/ccip/ccip_fee_boosting_test.go | 2 +- .../testsetups/ccip/test_helpers.go | 6 +++-- 18 files changed, 102 insertions(+), 31 deletions(-) diff --git a/deployment/ccip/changeset/accept_ownership_test.go b/deployment/ccip/changeset/accept_ownership_test.go index 796db6aed09..45a6fc491cf 100644 --- a/deployment/ccip/changeset/accept_ownership_test.go +++ b/deployment/ccip/changeset/accept_ownership_test.go @@ -33,6 +33,10 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { source: state.Chains[source].Timelock, dest: state.Chains[dest].Timelock, } + callProxies := map[uint64]*gethwrappers.CallProxy{ + source: state.Chains[source].CallProxy, + dest: state.Chains[dest].CallProxy, + } // at this point we have the initial deploys done, now we need to transfer ownership // to the timelock contract @@ -40,7 +44,7 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { require.NoError(t, err) // compose the transfer ownership and accept ownership changesets - _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocks, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocks, callProxies, []commonchangeset.ChangesetApplication{ // note this doesn't have proposals. { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), diff --git a/deployment/ccip/changeset/cs_active_candidate_test.go b/deployment/ccip/changeset/cs_active_candidate_test.go index 4bd0c9fd7a4..21a74b68b47 100644 --- a/deployment/ccip/changeset/cs_active_candidate_test.go +++ b/deployment/ccip/changeset/cs_active_candidate_test.go @@ -95,7 +95,11 @@ func TestActiveCandidate(t *testing.T) { for _, chain := range allChains { timelocks[chain] = state.Chains[chain].Timelock } - _, err = commonchangeset.ApplyChangesets(t, e, timelocks, []commonchangeset.ChangesetApplication{ + callProxies := make(map[uint64]*gethwrappers.CallProxy) + for _, chain := range allChains { + callProxies[chain] = state.Chains[chain].CallProxy + } + _, err = commonchangeset.ApplyChangesets(t, e, timelocks, callProxies, []commonchangeset.ChangesetApplication{ // note this doesn't have proposals. { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), @@ -177,7 +181,7 @@ func TestActiveCandidate(t *testing.T) { }}, "set new candidates on commit plugin", 0) require.NoError(t, err) setCommitCandidateSigned := commonchangeset.SignProposal(t, e, setCommitCandidateProposal) - commonchangeset.ExecuteProposal(t, e, setCommitCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) + commonchangeset.ExecuteProposal(t, e, setCommitCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, state.Chains[tenv.HomeChainSel].CallProxy, tenv.HomeChainSel) // create the op for the commit plugin as well setExecCandidateOp, err := setCandidateOnExistingDon( @@ -195,7 +199,7 @@ func TestActiveCandidate(t *testing.T) { }}, "set new candidates on commit and exec plugins", 0) require.NoError(t, err) setExecCandidateSigned := commonchangeset.SignProposal(t, e, setExecCandidateProposal) - commonchangeset.ExecuteProposal(t, e, setExecCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) + commonchangeset.ExecuteProposal(t, e, setExecCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, state.Chains[tenv.HomeChainSel].CallProxy, tenv.HomeChainSel) // check setup was successful by confirming number of nodes from cap reg donInfo, err = state.Chains[tenv.HomeChainSel].CapabilityRegistry.GetDON(nil, donID) @@ -222,7 +226,7 @@ func TestActiveCandidate(t *testing.T) { }}, "promote candidates and revoke actives", 0) require.NoError(t, err) promoteSigned := commonchangeset.SignProposal(t, e, promoteProposal) - commonchangeset.ExecuteProposal(t, e, promoteSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) + commonchangeset.ExecuteProposal(t, e, promoteSigned, state.Chains[tenv.HomeChainSel].Timelock, state.Chains[tenv.HomeChainSel].CallProxy, tenv.HomeChainSel) // [NEW ACTIVE, NO CANDIDATE] done promoting // [NEW ACTIVE, NO CANDIDATE] check onchain state diff --git a/deployment/ccip/changeset/cs_add_chain_test.go b/deployment/ccip/changeset/cs_add_chain_test.go index 52127f7af02..2c660ac0b3f 100644 --- a/deployment/ccip/changeset/cs_add_chain_test.go +++ b/deployment/ccip/changeset/cs_add_chain_test.go @@ -57,7 +57,7 @@ func TestAddChainInbound(t *testing.T) { Proposer: commonchangeset.SingleGroupMCMS(t), TimelockMinDelay: big.NewInt(0), } - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), Config: initialDeploy, @@ -106,7 +106,7 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, err) // Deploy contracts to new chain - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), Config: []uint64{newChain}, @@ -149,6 +149,10 @@ func TestAddChainInbound(t *testing.T) { initialDeploy[0]: state.Chains[initialDeploy[0]].Timelock, initialDeploy[1]: state.Chains[initialDeploy[1]].Timelock, initialDeploy[2]: state.Chains[initialDeploy[2]].Timelock, + }, map[uint64]*gethwrappers.CallProxy{ + initialDeploy[0]: state.Chains[initialDeploy[0]].CallProxy, + initialDeploy[1]: state.Chains[initialDeploy[1]].CallProxy, + initialDeploy[2]: state.Chains[initialDeploy[2]].CallProxy, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), @@ -181,6 +185,9 @@ func TestAddChainInbound(t *testing.T) { _, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*gethwrappers.RBACTimelock{ e.HomeChainSel: state.Chains[e.HomeChainSel].Timelock, newChain: state.Chains[newChain].Timelock, + }, map[uint64]*gethwrappers.CallProxy{ + e.HomeChainSel: state.Chains[e.HomeChainSel].CallProxy, + newChain: state.Chains[newChain].CallProxy, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(AddDonAndSetCandidateChangeset), diff --git a/deployment/ccip/changeset/cs_deploy_chain_test.go b/deployment/ccip/changeset/cs_deploy_chain_test.go index 646575dcaa4..3dd1d6c6103 100644 --- a/deployment/ccip/changeset/cs_deploy_chain_test.go +++ b/deployment/ccip/changeset/cs_deploy_chain_test.go @@ -37,7 +37,7 @@ func TestDeployChainContractsChangeset(t *testing.T) { TimelockMinDelay: big.NewInt(0), } } - e, err = commonchangeset.ApplyChangesets(t, e, nil, []commonchangeset.ChangesetApplication{ + e, err = commonchangeset.ApplyChangesets(t, e, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(DeployHomeChain), Config: DeployHomeChainConfig{ diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 09156717f99..422e0933176 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -297,7 +297,7 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, } // Need to deploy prerequisites first so that we can form the USDC config // no proposals to be made, timelock can be passed as nil here - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), Config: allChains, @@ -366,13 +366,15 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, // Build the per chain config. chainConfigs := make(map[uint64]CCIPOCRParams) timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) + callProxiesPerChain := make(map[uint64]*gethwrappers.CallProxy) for _, chain := range allChains { timelocksPerChain[chain] = state.Chains[chain].Timelock + callProxiesPerChain[chain] = state.Chains[chain].CallProxy tokenInfo := tokenConfig.GetTokenInfo(e.Env.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) chainConfigs[chain] = DefaultOCRParams(e.FeedChainSel, tokenInfo, tokenDataProviders) } // Deploy second set of changesets to deploy and configure the CCIP contracts. - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, callProxiesPerChain, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(ConfigureNewChains), Config: NewChainsConfig{ @@ -822,7 +824,7 @@ func ProcessChangeset(t *testing.T, e deployment.Environment, c deployment.Chang signed := commonchangeset.SignProposal(t, e, &prop) for _, sel := range chains.ToSlice() { - commonchangeset.ExecuteProposal(t, e, signed, state.Chains[sel].Timelock, sel) + commonchangeset.ExecuteProposal(t, e, signed, state.Chains[sel].Timelock, state.Chains[sel].CallProxy, sel) } } } diff --git a/deployment/common/changeset/deploy_link_token_test.go b/deployment/common/changeset/deploy_link_token_test.go index a61743e9bf4..5eb3d0a1e34 100644 --- a/deployment/common/changeset/deploy_link_token_test.go +++ b/deployment/common/changeset/deploy_link_token_test.go @@ -18,7 +18,7 @@ func TestDeployLinkToken(t *testing.T) { Chains: 1, }) chain1 := e.AllChainSelectors()[0] - e, err := changeset.ApplyChangesets(t, e, nil, []changeset.ChangesetApplication{ + e, err := changeset.ApplyChangesets(t, e, nil, nil, []changeset.ChangesetApplication{ { Changeset: changeset.WrapChangeSet(changeset.DeployLinkToken), Config: []uint64{chain1}, diff --git a/deployment/common/changeset/internal/mcms.go b/deployment/common/changeset/internal/mcms.go index e7cf4aa1489..39c319f0cd1 100644 --- a/deployment/common/changeset/internal/mcms.go +++ b/deployment/common/changeset/internal/mcms.go @@ -55,6 +55,7 @@ type MCMSWithTimelockDeploy struct { Bypasser *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] Proposer *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] Timelock *deployment.ContractDeploy[*owner_helpers.RBACTimelock] + CallProxy *deployment.ContractDeploy[*owner_helpers.CallProxy] } func DeployMCMSWithTimelockContractsBatch( @@ -164,5 +165,6 @@ func DeployMCMSWithTimelockContracts( Bypasser: bypasser, Proposer: proposer, Timelock: timelock, + CallProxy: callProxy, }, nil } diff --git a/deployment/common/changeset/mcms_test_helpers.go b/deployment/common/changeset/mcms_test_helpers.go index 3951149815c..b1fd4d878fc 100644 --- a/deployment/common/changeset/mcms_test_helpers.go +++ b/deployment/common/changeset/mcms_test_helpers.go @@ -65,7 +65,7 @@ func SignProposal(t *testing.T, env deployment.Environment, proposal *timelock.M } func ExecuteProposal(t *testing.T, env deployment.Environment, executor *mcms.Executor, - timelock *owner_helpers.RBACTimelock, sel uint64) { + timelock *owner_helpers.RBACTimelock, callProxy *owner_helpers.CallProxy, sel uint64) { t.Log("Executing proposal on chain", sel) // Set the root. tx, err2 := executor.SetRootOnChain(env.Chains[sel].Client, env.Chains[sel].DeployerKey, mcms.ChainIdentifier(sel)) @@ -104,7 +104,8 @@ func ExecuteProposal(t *testing.T, env deployment.Environment, executor *mcms.Ex Value: it.Event.Value, }) } - tx, err := timelock.ExecuteBatch( + timelockExecutorProxy, err := owner_helpers.NewRBACTimelock(callProxy.Address(), env.Chains[sel].Client) + tx, err := timelockExecutorProxy.ExecuteBatch( env.Chains[sel].DeployerKey, calls, pred, salt) require.NoError(t, err) _, err = env.Chains[sel].Confirm(tx) diff --git a/deployment/common/changeset/state.go b/deployment/common/changeset/state.go index 0055c908f8d..a580c13b40b 100644 --- a/deployment/common/changeset/state.go +++ b/deployment/common/changeset/state.go @@ -23,6 +23,7 @@ type MCMSWithTimelockState struct { BypasserMcm *owner_helpers.ManyChainMultiSig ProposerMcm *owner_helpers.ManyChainMultiSig Timelock *owner_helpers.RBACTimelock + CallProxy *owner_helpers.CallProxy } // Validate checks that all fields are non-nil, ensuring it's ready @@ -40,6 +41,9 @@ func (state MCMSWithTimelockState) Validate() error { if state.BypasserMcm == nil { return errors.New("bypasser not found") } + if state.CallProxy == nil { + return errors.New("call proxy not found") + } return nil } @@ -51,6 +55,10 @@ func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWith 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 @@ -68,6 +76,7 @@ func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWith Bypasser: bypasserView, Proposer: proposerView, Canceller: cancellerView, + CallProxy: callProxyView, }, nil } @@ -82,6 +91,7 @@ func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string state := MCMSWithTimelockState{} // We expect one of each contract on the chain. timelock := deployment.NewTypeAndVersion(types.RBACTimelock, deployment.Version1_0_0) + callProxy := deployment.NewTypeAndVersion(types.CallProxy, deployment.Version1_0_0) proposer := deployment.NewTypeAndVersion(types.ProposerManyChainMultisig, deployment.Version1_0_0) canceller := deployment.NewTypeAndVersion(types.CancellerManyChainMultisig, deployment.Version1_0_0) bypasser := deployment.NewTypeAndVersion(types.BypasserManyChainMultisig, deployment.Version1_0_0) @@ -89,7 +99,7 @@ func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string // Ensure we either have the bundle or not. _, err := deployment.AddressesContainBundle(addresses, map[deployment.TypeAndVersion]struct{}{ - timelock: {}, proposer: {}, canceller: {}, bypasser: {}, + timelock: {}, proposer: {}, canceller: {}, bypasser: {}, callProxy: {}, }) if err != nil { return nil, fmt.Errorf("unable to check MCMS contracts on chain %s error: %w", chain.Name(), err) @@ -103,6 +113,12 @@ func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string return nil, err } state.Timelock = tl + case callProxy: + cp, err := owner_helpers.NewCallProxy(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.CallProxy = cp case proposer: mcms, err := owner_helpers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) if err != nil { diff --git a/deployment/common/changeset/test_helpers.go b/deployment/common/changeset/test_helpers.go index 913b4544f30..0d068ade971 100644 --- a/deployment/common/changeset/test_helpers.go +++ b/deployment/common/changeset/test_helpers.go @@ -33,7 +33,7 @@ func WrapChangeSet[C any](fn deployment.ChangeSet[C]) func(e deployment.Environm } // ApplyChangesets applies the changeset applications to the environment and returns the updated environment. -func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain map[uint64]*gethwrappers.RBACTimelock, changesetApplications []ChangesetApplication) (deployment.Environment, error) { +func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain map[uint64]*gethwrappers.RBACTimelock, callProxiesPerChain map[uint64]*gethwrappers.CallProxy, changesetApplications []ChangesetApplication) (deployment.Environment, error) { currentEnv := e for i, csa := range changesetApplications { out, err := csa.Changeset(currentEnv, csa.Config) @@ -79,7 +79,12 @@ func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain m if !ok || timelock == nil { return deployment.Environment{}, fmt.Errorf("timelock not found for chain %d", sel) } - ExecuteProposal(t, e, signed, timelock, sel) + + callProxy, ok := callProxiesPerChain[sel] + if !ok || callProxy == nil { + return deployment.Environment{}, fmt.Errorf("call proxy not found for chain %d", sel) + } + ExecuteProposal(t, e, signed, timelock, callProxy, sel) } } } diff --git a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go index 7a07270c6f2..fd50c9ad66f 100644 --- a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go +++ b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go @@ -21,7 +21,7 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { Nodes: 1, }) chain1 := e.AllChainSelectors()[0] - e, err := ApplyChangesets(t, e, nil, []ChangesetApplication{ + e, err := ApplyChangesets(t, e, nil, nil, []ChangesetApplication{ { Changeset: WrapChangeSet(DeployLinkToken), Config: []uint64{chain1}, @@ -47,6 +47,8 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { require.NoError(t, err) e, err = ApplyChangesets(t, e, map[uint64]*owner_helpers.RBACTimelock{ chain1: state.Timelock, + }, map[uint64]*owner_helpers.CallProxy{ + chain1: state.CallProxy, }, []ChangesetApplication{ { Changeset: WrapChangeSet(TransferToMCMSWithTimelock), diff --git a/deployment/common/view/v1_0/mcms.go b/deployment/common/view/v1_0/mcms.go index 25ca614a553..bc971623545 100644 --- a/deployment/common/view/v1_0/mcms.go +++ b/deployment/common/view/v1_0/mcms.go @@ -107,11 +107,24 @@ func GenerateTimelockView(tl owner_helpers.RBACTimelock) (TimelockView, error) { }, nil } +type CallProxyView struct { + types.ContractMetaData +} + +func GenerateCallProxyView(cp owner_helpers.CallProxy) (CallProxyView, error) { + return CallProxyView{ + ContractMetaData: types.ContractMetaData{ + Address: cp.Address(), + }, + }, nil +} + type MCMSWithTimelockView struct { - Bypasser MCMSView `json:"bypasser"` - Canceller MCMSView `json:"canceller"` - Proposer MCMSView `json:"proposer"` - Timelock TimelockView `json:"timelock"` + Bypasser MCMSView `json:"bypasser"` + Canceller MCMSView `json:"canceller"` + Proposer MCMSView `json:"proposer"` + Timelock TimelockView `json:"timelock"` + CallProxy CallProxyView `json:"callProxy"` } func GenerateMCMSWithTimelockView( @@ -124,6 +137,10 @@ func GenerateMCMSWithTimelockView( if err != nil { return MCMSWithTimelockView{}, nil } + callProxyView, err := GenerateCallProxyView(owner_helpers.CallProxy{}) + if err != nil { + return MCMSWithTimelockView{}, nil + } bypasserView, err := GenerateMCMSView(bypasser) if err != nil { return MCMSWithTimelockView{}, nil @@ -142,5 +159,6 @@ func GenerateMCMSWithTimelockView( Bypasser: bypasserView, Proposer: proposerView, Canceller: cancellerView, + CallProxy: callProxyView, }, nil } diff --git a/deployment/keystone/changeset/accept_ownership_test.go b/deployment/keystone/changeset/accept_ownership_test.go index df3bfee48ec..64f3b3c86fb 100644 --- a/deployment/keystone/changeset/accept_ownership_test.go +++ b/deployment/keystone/changeset/accept_ownership_test.go @@ -25,7 +25,7 @@ func TestAcceptAllOwnership(t *testing.T) { } env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) registrySel := env.AllChainSelectors()[0] - env, err := commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ + env, err := commonchangeset.ApplyChangesets(t, env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.DeployCapabilityRegistry), Config: registrySel, @@ -58,6 +58,8 @@ func TestAcceptAllOwnership(t *testing.T) { _, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*owner_helpers.RBACTimelock{ registrySel: timelock.Timelock, + }, map[uint64]*owner_helpers.CallProxy{ + registrySel: timelock.CallProxy, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.AcceptAllOwnershipsProposal), diff --git a/deployment/keystone/changeset/deploy_forwarder_test.go b/deployment/keystone/changeset/deploy_forwarder_test.go index 32a53f1cf08..2c449000397 100644 --- a/deployment/keystone/changeset/deploy_forwarder_test.go +++ b/deployment/keystone/changeset/deploy_forwarder_test.go @@ -118,11 +118,14 @@ func TestConfigureForwarders(t *testing.T) { require.Nil(t, csOut.AddressBook) timelocks := make(map[uint64]*gethwrappers.RBACTimelock) + callProxies := make(map[uint64]*gethwrappers.CallProxy) for selector, contractSet := range te.ContractSets() { require.NotNil(t, contractSet.Timelock) timelocks[selector] = contractSet.Timelock + require.NotNil(t, contractSet.CallProxy) + callProxies[selector] = contractSet.CallProxy } - _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, callProxies, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureForwardContracts), Config: cfg, diff --git a/deployment/keystone/changeset/deploy_ocr3_test.go b/deployment/keystone/changeset/deploy_ocr3_test.go index 0d49af68823..8184351fcde 100644 --- a/deployment/keystone/changeset/deploy_ocr3_test.go +++ b/deployment/keystone/changeset/deploy_ocr3_test.go @@ -130,10 +130,13 @@ func TestConfigureOCR3(t *testing.T) { var timelocks = map[uint64]*gethwrappers.RBACTimelock{ te.RegistrySelector: contractSetsResp.ContractSets[te.RegistrySelector].Timelock, } + var callProxies = map[uint64]*gethwrappers.CallProxy{ + te.RegistrySelector: contractSetsResp.ContractSets[te.RegistrySelector].CallProxy, + } // now apply the changeset such that the proposal is signed and execed w2 := &bytes.Buffer{} cfg.WriteGeneratedConfig = w2 - _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, callProxies, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureOCR3Contract), Config: cfg, diff --git a/deployment/keystone/changeset/helpers_test.go b/deployment/keystone/changeset/helpers_test.go index 54686fb396b..769e4a38132 100644 --- a/deployment/keystone/changeset/helpers_test.go +++ b/deployment/keystone/changeset/helpers_test.go @@ -130,7 +130,7 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { Chains: chains, ExistingAddresses: deployment.NewMemoryAddressBook(), } - e, err := commonchangeset.ApplyChangesets(t, e, nil, []commonchangeset.ChangesetApplication{ + e, err := commonchangeset.ApplyChangesets(t, e, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(kschangeset.DeployCapabilityRegistry), Config: registryChainSel, @@ -271,7 +271,7 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { TimelockMinDelay: big.NewInt(0), } } - env, err = commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ + env, err = commonchangeset.ApplyChangesets(t, env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), Config: timelockCfgs, @@ -290,7 +290,7 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { require.NoError(t, mcms.Validate()) // transfer ownership of all contracts to the MCMS - env, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*gethwrappers.RBACTimelock{sel: mcms.Timelock}, []commonchangeset.ChangesetApplication{ + env, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*gethwrappers.RBACTimelock{sel: mcms.Timelock}, map[uint64]*gethwrappers.CallProxy{sel: mcms.CallProxy}, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(kschangeset.AcceptAllOwnershipsProposal), Config: &kschangeset.AcceptAllOwnershipRequest{ diff --git a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go index 1fe9d5817c9..af0ac561630 100644 --- a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go +++ b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go @@ -102,7 +102,7 @@ func Test_CCIPFeeBoosting(t *testing.T) { TestRouter: false, } - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.AddLanes), Config: changeset.AddLanesConfig{LaneConfigs: []changeset.LaneConfig{laneCfg}}, diff --git a/integration-tests/testsetups/ccip/test_helpers.go b/integration-tests/testsetups/ccip/test_helpers.go index e8709343f38..58feb3e2a3a 100644 --- a/integration-tests/testsetups/ccip/test_helpers.go +++ b/integration-tests/testsetups/ccip/test_helpers.go @@ -155,7 +155,7 @@ func NewLocalDevEnvironment( } // Need to deploy prerequisites first so that we can form the USDC config // no proposals to be made, timelock can be passed as nil here - env, err = commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ + env, err = commonchangeset.ApplyChangesets(t, env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.DeployHomeChain), Config: changeset.DeployHomeChainConfig{ @@ -226,8 +226,10 @@ func NewLocalDevEnvironment( tokenConfig := changeset.NewTestTokenConfig(state.Chains[feedSel].USDFeeds) chainConfigs := make(map[uint64]changeset.CCIPOCRParams) timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) + callProxiesPerChain := make(map[uint64]*gethwrappers.CallProxy) for _, chain := range allChains { timelocksPerChain[chain] = state.Chains[chain].Timelock + callProxiesPerChain[chain] = state.Chains[chain].CallProxy tokenInfo := tokenConfig.GetTokenInfo(e.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) ocrParams := changeset.DefaultOCRParams(feedSel, tokenInfo, tokenDataProviders) if tCfg.OCRConfigOverride != nil { @@ -237,7 +239,7 @@ func NewLocalDevEnvironment( } // Deploy second set of changesets to deploy and configure the CCIP contracts. - env, err = commonchangeset.ApplyChangesets(t, env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + env, err = commonchangeset.ApplyChangesets(t, env, timelocksPerChain, callProxiesPerChain, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureNewChains), Config: changeset.NewChainsConfig{ From 663f8b0ee0c8009f90c1120bce7c0172019fa0c4 Mon Sep 17 00:00:00 2001 From: Akhil Chainani Date: Sun, 8 Dec 2024 13:21:36 -0500 Subject: [PATCH 14/41] skip call proxy when loading chain state --- deployment/ccip/changeset/state.go | 1 + 1 file changed, 1 insertion(+) diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index 22ae59fc360..122ce8ec13c 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -311,6 +311,7 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type for address, tvStr := range addresses { switch tvStr.String() { case deployment.NewTypeAndVersion(commontypes.RBACTimelock, deployment.Version1_0_0).String(), + deployment.NewTypeAndVersion(commontypes.CallProxy, deployment.Version1_0_0).String(), deployment.NewTypeAndVersion(commontypes.ProposerManyChainMultisig, deployment.Version1_0_0).String(), deployment.NewTypeAndVersion(commontypes.CancellerManyChainMultisig, deployment.Version1_0_0).String(), deployment.NewTypeAndVersion(commontypes.BypasserManyChainMultisig, deployment.Version1_0_0).String(), From c17911eb1ff4382b3f80d0edb055d748096dc59f Mon Sep 17 00:00:00 2001 From: Akhil Chainani Date: Sun, 8 Dec 2024 14:04:33 -0500 Subject: [PATCH 15/41] revert all changes --- .../ccip/changeset/accept_ownership_test.go | 6 +-- .../changeset/cs_active_candidate_test.go | 12 ++---- .../ccip/changeset/cs_add_chain_test.go | 20 ++++----- .../ccip/changeset/cs_deploy_chain_test.go | 11 ++--- deployment/ccip/changeset/state.go | 1 - deployment/ccip/changeset/test_helpers.go | 17 ++++---- .../changeset/deploy_link_token_test.go | 2 +- deployment/common/changeset/internal/mcms.go | 41 ++----------------- .../common/changeset/internal/mcms_test.go | 12 ++++-- .../common/changeset/mcms_test_helpers.go | 5 +-- deployment/common/changeset/state.go | 18 +------- deployment/common/changeset/test_helpers.go | 9 +--- .../transfer_to_mcms_with_timelock_test.go | 13 +++--- deployment/common/types/types.go | 11 ++--- deployment/common/view/v1_0/mcms.go | 26 ++---------- .../changeset/accept_ownership_test.go | 13 +++--- .../changeset/deploy_forwarder_test.go | 5 +-- .../keystone/changeset/deploy_ocr3_test.go | 5 +-- deployment/keystone/changeset/helpers_test.go | 15 +++---- 19 files changed, 76 insertions(+), 166 deletions(-) diff --git a/deployment/ccip/changeset/accept_ownership_test.go b/deployment/ccip/changeset/accept_ownership_test.go index 45a6fc491cf..796db6aed09 100644 --- a/deployment/ccip/changeset/accept_ownership_test.go +++ b/deployment/ccip/changeset/accept_ownership_test.go @@ -33,10 +33,6 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { source: state.Chains[source].Timelock, dest: state.Chains[dest].Timelock, } - callProxies := map[uint64]*gethwrappers.CallProxy{ - source: state.Chains[source].CallProxy, - dest: state.Chains[dest].CallProxy, - } // at this point we have the initial deploys done, now we need to transfer ownership // to the timelock contract @@ -44,7 +40,7 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { require.NoError(t, err) // compose the transfer ownership and accept ownership changesets - _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocks, callProxies, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocks, []commonchangeset.ChangesetApplication{ // note this doesn't have proposals. { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), diff --git a/deployment/ccip/changeset/cs_active_candidate_test.go b/deployment/ccip/changeset/cs_active_candidate_test.go index 21a74b68b47..4bd0c9fd7a4 100644 --- a/deployment/ccip/changeset/cs_active_candidate_test.go +++ b/deployment/ccip/changeset/cs_active_candidate_test.go @@ -95,11 +95,7 @@ func TestActiveCandidate(t *testing.T) { for _, chain := range allChains { timelocks[chain] = state.Chains[chain].Timelock } - callProxies := make(map[uint64]*gethwrappers.CallProxy) - for _, chain := range allChains { - callProxies[chain] = state.Chains[chain].CallProxy - } - _, err = commonchangeset.ApplyChangesets(t, e, timelocks, callProxies, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, e, timelocks, []commonchangeset.ChangesetApplication{ // note this doesn't have proposals. { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), @@ -181,7 +177,7 @@ func TestActiveCandidate(t *testing.T) { }}, "set new candidates on commit plugin", 0) require.NoError(t, err) setCommitCandidateSigned := commonchangeset.SignProposal(t, e, setCommitCandidateProposal) - commonchangeset.ExecuteProposal(t, e, setCommitCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, state.Chains[tenv.HomeChainSel].CallProxy, tenv.HomeChainSel) + commonchangeset.ExecuteProposal(t, e, setCommitCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) // create the op for the commit plugin as well setExecCandidateOp, err := setCandidateOnExistingDon( @@ -199,7 +195,7 @@ func TestActiveCandidate(t *testing.T) { }}, "set new candidates on commit and exec plugins", 0) require.NoError(t, err) setExecCandidateSigned := commonchangeset.SignProposal(t, e, setExecCandidateProposal) - commonchangeset.ExecuteProposal(t, e, setExecCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, state.Chains[tenv.HomeChainSel].CallProxy, tenv.HomeChainSel) + commonchangeset.ExecuteProposal(t, e, setExecCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) // check setup was successful by confirming number of nodes from cap reg donInfo, err = state.Chains[tenv.HomeChainSel].CapabilityRegistry.GetDON(nil, donID) @@ -226,7 +222,7 @@ func TestActiveCandidate(t *testing.T) { }}, "promote candidates and revoke actives", 0) require.NoError(t, err) promoteSigned := commonchangeset.SignProposal(t, e, promoteProposal) - commonchangeset.ExecuteProposal(t, e, promoteSigned, state.Chains[tenv.HomeChainSel].Timelock, state.Chains[tenv.HomeChainSel].CallProxy, tenv.HomeChainSel) + commonchangeset.ExecuteProposal(t, e, promoteSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) // [NEW ACTIVE, NO CANDIDATE] done promoting // [NEW ACTIVE, NO CANDIDATE] check onchain state diff --git a/deployment/ccip/changeset/cs_add_chain_test.go b/deployment/ccip/changeset/cs_add_chain_test.go index 2c660ac0b3f..f6e1c04c469 100644 --- a/deployment/ccip/changeset/cs_add_chain_test.go +++ b/deployment/ccip/changeset/cs_add_chain_test.go @@ -52,12 +52,13 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) cfg := commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: e.Env.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), } - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, nil, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), Config: initialDeploy, @@ -106,7 +107,7 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, err) // Deploy contracts to new chain - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, nil, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), Config: []uint64{newChain}, @@ -149,10 +150,6 @@ func TestAddChainInbound(t *testing.T) { initialDeploy[0]: state.Chains[initialDeploy[0]].Timelock, initialDeploy[1]: state.Chains[initialDeploy[1]].Timelock, initialDeploy[2]: state.Chains[initialDeploy[2]].Timelock, - }, map[uint64]*gethwrappers.CallProxy{ - initialDeploy[0]: state.Chains[initialDeploy[0]].CallProxy, - initialDeploy[1]: state.Chains[initialDeploy[1]].CallProxy, - initialDeploy[2]: state.Chains[initialDeploy[2]].CallProxy, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), @@ -185,9 +182,6 @@ func TestAddChainInbound(t *testing.T) { _, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*gethwrappers.RBACTimelock{ e.HomeChainSel: state.Chains[e.HomeChainSel].Timelock, newChain: state.Chains[newChain].Timelock, - }, map[uint64]*gethwrappers.CallProxy{ - e.HomeChainSel: state.Chains[e.HomeChainSel].CallProxy, - newChain: state.Chains[newChain].CallProxy, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(AddDonAndSetCandidateChangeset), diff --git a/deployment/ccip/changeset/cs_deploy_chain_test.go b/deployment/ccip/changeset/cs_deploy_chain_test.go index 3dd1d6c6103..234d73cc4b5 100644 --- a/deployment/ccip/changeset/cs_deploy_chain_test.go +++ b/deployment/ccip/changeset/cs_deploy_chain_test.go @@ -31,13 +31,14 @@ func TestDeployChainContractsChangeset(t *testing.T) { cfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) for _, chain := range e.AllChainSelectors() { cfg[chain] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: e.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), } } - e, err = commonchangeset.ApplyChangesets(t, e, nil, nil, []commonchangeset.ChangesetApplication{ + e, err = commonchangeset.ApplyChangesets(t, e, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(DeployHomeChain), Config: DeployHomeChainConfig{ diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index 122ce8ec13c..22ae59fc360 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -311,7 +311,6 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type for address, tvStr := range addresses { switch tvStr.String() { case deployment.NewTypeAndVersion(commontypes.RBACTimelock, deployment.Version1_0_0).String(), - deployment.NewTypeAndVersion(commontypes.CallProxy, deployment.Version1_0_0).String(), deployment.NewTypeAndVersion(commontypes.ProposerManyChainMultisig, deployment.Version1_0_0).String(), deployment.NewTypeAndVersion(commontypes.CancellerManyChainMultisig, deployment.Version1_0_0).String(), deployment.NewTypeAndVersion(commontypes.BypasserManyChainMultisig, deployment.Version1_0_0).String(), diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 422e0933176..1ee8b0d0e42 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -279,10 +279,11 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) for _, c := range e.Env.AllChainSelectors() { mcmsCfg[c] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: e.Env.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), } } var ( @@ -297,7 +298,7 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, } // Need to deploy prerequisites first so that we can form the USDC config // no proposals to be made, timelock can be passed as nil here - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, nil, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), Config: allChains, @@ -366,15 +367,13 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, // Build the per chain config. chainConfigs := make(map[uint64]CCIPOCRParams) timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) - callProxiesPerChain := make(map[uint64]*gethwrappers.CallProxy) for _, chain := range allChains { timelocksPerChain[chain] = state.Chains[chain].Timelock - callProxiesPerChain[chain] = state.Chains[chain].CallProxy tokenInfo := tokenConfig.GetTokenInfo(e.Env.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) chainConfigs[chain] = DefaultOCRParams(e.FeedChainSel, tokenInfo, tokenDataProviders) } // Deploy second set of changesets to deploy and configure the CCIP contracts. - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, callProxiesPerChain, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(ConfigureNewChains), Config: NewChainsConfig{ @@ -824,7 +823,7 @@ func ProcessChangeset(t *testing.T, e deployment.Environment, c deployment.Chang signed := commonchangeset.SignProposal(t, e, &prop) for _, sel := range chains.ToSlice() { - commonchangeset.ExecuteProposal(t, e, signed, state.Chains[sel].Timelock, state.Chains[sel].CallProxy, sel) + commonchangeset.ExecuteProposal(t, e, signed, state.Chains[sel].Timelock, sel) } } } diff --git a/deployment/common/changeset/deploy_link_token_test.go b/deployment/common/changeset/deploy_link_token_test.go index 5eb3d0a1e34..a61743e9bf4 100644 --- a/deployment/common/changeset/deploy_link_token_test.go +++ b/deployment/common/changeset/deploy_link_token_test.go @@ -18,7 +18,7 @@ func TestDeployLinkToken(t *testing.T) { Chains: 1, }) chain1 := e.AllChainSelectors()[0] - e, err := changeset.ApplyChangesets(t, e, nil, nil, []changeset.ChangesetApplication{ + e, err := changeset.ApplyChangesets(t, e, nil, []changeset.ChangesetApplication{ { Changeset: changeset.WrapChangeSet(changeset.DeployLinkToken), Config: []uint64{chain1}, diff --git a/deployment/common/changeset/internal/mcms.go b/deployment/common/changeset/internal/mcms.go index 39c319f0cd1..281f43924f4 100644 --- a/deployment/common/changeset/internal/mcms.go +++ b/deployment/common/changeset/internal/mcms.go @@ -55,7 +55,6 @@ type MCMSWithTimelockDeploy struct { Bypasser *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] Proposer *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] Timelock *deployment.ContractDeploy[*owner_helpers.RBACTimelock] - CallProxy *deployment.ContractDeploy[*owner_helpers.CallProxy] } func DeployMCMSWithTimelockContractsBatch( @@ -107,10 +106,10 @@ func DeployMCMSWithTimelockContracts( // TODO: Could expose this as config? // Or keep this enforced to follow the same pattern? chain.DeployerKey.From, - []common.Address{proposer.Address}, // proposers - []common.Address{}, // executors - []common.Address{canceller.Address, proposer.Address, bypasser.Address}, // cancellers - []common.Address{bypasser.Address}, // bypassers + []common.Address{proposer.Address}, // proposers + config.TimelockExecutors, //executors + []common.Address{canceller.Address}, // cancellers + []common.Address{bypasser.Address}, // bypassers ) return deployment.ContractDeploy[*owner_helpers.RBACTimelock]{ timelock, cc, tx2, deployment.NewTypeAndVersion(types.RBACTimelock, deployment.Version1_0_0), err2, @@ -120,37 +119,6 @@ func DeployMCMSWithTimelockContracts( lggr.Errorw("Failed to deploy timelock", "chain", chain.String(), "err", err) return nil, err } - - callProxy, err := deployment.DeployContract(lggr, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*owner_helpers.CallProxy] { - callProxy, tx2, cc, err2 := owner_helpers.DeployCallProxy( - chain.DeployerKey, - chain.Client, - timelock.Address, - ) - return deployment.ContractDeploy[*owner_helpers.CallProxy]{ - callProxy, cc, tx2, deployment.NewTypeAndVersion(types.CallProxy, deployment.Version1_0_0), err2, - } - }) - if err != nil { - lggr.Errorw("Failed to deploy call proxy", "chain", chain.String(), "err", err) - return nil, err - } - - grantRoleTx, err := timelock.Contract.GrantRole( - chain.DeployerKey, - v1_0.EXECUTOR_ROLE.ID, - callProxy.Address, - ) - if err != nil { - lggr.Errorw("Failed to grant timelock executor role", "chain", chain.String(), "err", err) - return nil, err - } - - if _, err := deployment.ConfirmIfNoError(chain, grantRoleTx, err); err != nil { - lggr.Errorw("Failed to grant timelock executor role", "chain", chain.String(), "err", err) - return nil, err - } // We grant the timelock the admin role on the MCMS contracts. tx, err := timelock.Contract.GrantRole(chain.DeployerKey, v1_0.ADMIN_ROLE.ID, timelock.Address) @@ -165,6 +133,5 @@ func DeployMCMSWithTimelockContracts( Bypasser: bypasser, Proposer: proposer, Timelock: timelock, - CallProxy: callProxy, }, nil } diff --git a/deployment/common/changeset/internal/mcms_test.go b/deployment/common/changeset/internal/mcms_test.go index 10fb1d980de..2269911f4cd 100644 --- a/deployment/common/changeset/internal/mcms_test.go +++ b/deployment/common/changeset/internal/mcms_test.go @@ -5,6 +5,7 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/common" chainsel "github.com/smartcontractkit/chain-selectors" "github.com/stretchr/testify/require" @@ -36,15 +37,18 @@ func TestDeployMCMSWithTimelockContracts(t *testing.T) { _, err := internal.DeployMCMSWithTimelockContracts(lggr, chains[chainsel.TEST_90000001.Selector], ab, types.MCMSWithTimelockConfig{ - Canceller: changeset.SingleGroupMCMS(t), - Bypasser: changeset.SingleGroupMCMS(t), - Proposer: changeset.SingleGroupMCMS(t), + Canceller: changeset.SingleGroupMCMS(t), + Bypasser: changeset.SingleGroupMCMS(t), + Proposer: changeset.SingleGroupMCMS(t), + TimelockExecutors: []common.Address{ + chains[chainsel.TEST_90000001.Selector].DeployerKey.From, + }, TimelockMinDelay: big.NewInt(0), }) require.NoError(t, err) addresses, err := ab.AddressesForChain(chainsel.TEST_90000001.Selector) require.NoError(t, err) - require.Len(t, addresses, 5) + require.Len(t, addresses, 4) mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chains[chainsel.TEST_90000001.Selector], addresses) require.NoError(t, err) v, err := mcmsState.GenerateMCMSWithTimelockView() diff --git a/deployment/common/changeset/mcms_test_helpers.go b/deployment/common/changeset/mcms_test_helpers.go index b1fd4d878fc..3951149815c 100644 --- a/deployment/common/changeset/mcms_test_helpers.go +++ b/deployment/common/changeset/mcms_test_helpers.go @@ -65,7 +65,7 @@ func SignProposal(t *testing.T, env deployment.Environment, proposal *timelock.M } func ExecuteProposal(t *testing.T, env deployment.Environment, executor *mcms.Executor, - timelock *owner_helpers.RBACTimelock, callProxy *owner_helpers.CallProxy, sel uint64) { + timelock *owner_helpers.RBACTimelock, sel uint64) { t.Log("Executing proposal on chain", sel) // Set the root. tx, err2 := executor.SetRootOnChain(env.Chains[sel].Client, env.Chains[sel].DeployerKey, mcms.ChainIdentifier(sel)) @@ -104,8 +104,7 @@ func ExecuteProposal(t *testing.T, env deployment.Environment, executor *mcms.Ex Value: it.Event.Value, }) } - timelockExecutorProxy, err := owner_helpers.NewRBACTimelock(callProxy.Address(), env.Chains[sel].Client) - tx, err := timelockExecutorProxy.ExecuteBatch( + tx, err := timelock.ExecuteBatch( env.Chains[sel].DeployerKey, calls, pred, salt) require.NoError(t, err) _, err = env.Chains[sel].Confirm(tx) diff --git a/deployment/common/changeset/state.go b/deployment/common/changeset/state.go index a580c13b40b..0055c908f8d 100644 --- a/deployment/common/changeset/state.go +++ b/deployment/common/changeset/state.go @@ -23,7 +23,6 @@ type MCMSWithTimelockState struct { BypasserMcm *owner_helpers.ManyChainMultiSig ProposerMcm *owner_helpers.ManyChainMultiSig Timelock *owner_helpers.RBACTimelock - CallProxy *owner_helpers.CallProxy } // Validate checks that all fields are non-nil, ensuring it's ready @@ -41,9 +40,6 @@ func (state MCMSWithTimelockState) Validate() error { if state.BypasserMcm == nil { return errors.New("bypasser not found") } - if state.CallProxy == nil { - return errors.New("call proxy not found") - } return nil } @@ -55,10 +51,6 @@ func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWith 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 @@ -76,7 +68,6 @@ func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWith Bypasser: bypasserView, Proposer: proposerView, Canceller: cancellerView, - CallProxy: callProxyView, }, nil } @@ -91,7 +82,6 @@ func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string state := MCMSWithTimelockState{} // We expect one of each contract on the chain. timelock := deployment.NewTypeAndVersion(types.RBACTimelock, deployment.Version1_0_0) - callProxy := deployment.NewTypeAndVersion(types.CallProxy, deployment.Version1_0_0) proposer := deployment.NewTypeAndVersion(types.ProposerManyChainMultisig, deployment.Version1_0_0) canceller := deployment.NewTypeAndVersion(types.CancellerManyChainMultisig, deployment.Version1_0_0) bypasser := deployment.NewTypeAndVersion(types.BypasserManyChainMultisig, deployment.Version1_0_0) @@ -99,7 +89,7 @@ func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string // Ensure we either have the bundle or not. _, err := deployment.AddressesContainBundle(addresses, map[deployment.TypeAndVersion]struct{}{ - timelock: {}, proposer: {}, canceller: {}, bypasser: {}, callProxy: {}, + timelock: {}, proposer: {}, canceller: {}, bypasser: {}, }) if err != nil { return nil, fmt.Errorf("unable to check MCMS contracts on chain %s error: %w", chain.Name(), err) @@ -113,12 +103,6 @@ func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string return nil, err } state.Timelock = tl - case callProxy: - cp, err := owner_helpers.NewCallProxy(common.HexToAddress(address), chain.Client) - if err != nil { - return nil, err - } - state.CallProxy = cp case proposer: mcms, err := owner_helpers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) if err != nil { diff --git a/deployment/common/changeset/test_helpers.go b/deployment/common/changeset/test_helpers.go index 0d068ade971..913b4544f30 100644 --- a/deployment/common/changeset/test_helpers.go +++ b/deployment/common/changeset/test_helpers.go @@ -33,7 +33,7 @@ func WrapChangeSet[C any](fn deployment.ChangeSet[C]) func(e deployment.Environm } // ApplyChangesets applies the changeset applications to the environment and returns the updated environment. -func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain map[uint64]*gethwrappers.RBACTimelock, callProxiesPerChain map[uint64]*gethwrappers.CallProxy, changesetApplications []ChangesetApplication) (deployment.Environment, error) { +func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain map[uint64]*gethwrappers.RBACTimelock, changesetApplications []ChangesetApplication) (deployment.Environment, error) { currentEnv := e for i, csa := range changesetApplications { out, err := csa.Changeset(currentEnv, csa.Config) @@ -79,12 +79,7 @@ func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain m if !ok || timelock == nil { return deployment.Environment{}, fmt.Errorf("timelock not found for chain %d", sel) } - - callProxy, ok := callProxiesPerChain[sel] - if !ok || callProxy == nil { - return deployment.Environment{}, fmt.Errorf("call proxy not found for chain %d", sel) - } - ExecuteProposal(t, e, signed, timelock, callProxy, sel) + ExecuteProposal(t, e, signed, timelock, sel) } } } diff --git a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go index fd50c9ad66f..6cdff286707 100644 --- a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go +++ b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go @@ -21,7 +21,7 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { Nodes: 1, }) chain1 := e.AllChainSelectors()[0] - e, err := ApplyChangesets(t, e, nil, nil, []ChangesetApplication{ + e, err := ApplyChangesets(t, e, nil, []ChangesetApplication{ { Changeset: WrapChangeSet(DeployLinkToken), Config: []uint64{chain1}, @@ -30,10 +30,11 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { Changeset: WrapChangeSet(DeployMCMSWithTimelock), Config: map[uint64]types.MCMSWithTimelockConfig{ chain1: { - Canceller: SingleGroupMCMS(t), - Bypasser: SingleGroupMCMS(t), - Proposer: SingleGroupMCMS(t), - TimelockMinDelay: big.NewInt(0), + Canceller: SingleGroupMCMS(t), + Bypasser: SingleGroupMCMS(t), + Proposer: SingleGroupMCMS(t), + TimelockExecutors: e.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), }, }, }, @@ -47,8 +48,6 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { require.NoError(t, err) e, err = ApplyChangesets(t, e, map[uint64]*owner_helpers.RBACTimelock{ chain1: state.Timelock, - }, map[uint64]*owner_helpers.CallProxy{ - chain1: state.CallProxy, }, []ChangesetApplication{ { Changeset: WrapChangeSet(TransferToMCMSWithTimelock), diff --git a/deployment/common/types/types.go b/deployment/common/types/types.go index 0f04421af43..386ef8fbb36 100644 --- a/deployment/common/types/types.go +++ b/deployment/common/types/types.go @@ -5,6 +5,7 @@ import ( "math/big" "time" + "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" "github.com/smartcontractkit/chainlink/deployment" @@ -15,7 +16,6 @@ const ( CancellerManyChainMultisig deployment.ContractType = "CancellerManyChainMultiSig" ProposerManyChainMultisig deployment.ContractType = "ProposerManyChainMultiSig" RBACTimelock deployment.ContractType = "RBACTimelock" - CallProxy deployment.ContractType = "CallProxy" // LinkToken is the burn/mint link token. It should be used everywhere for // new deployments. Corresponds to // https://github.com/smartcontractkit/chainlink/blob/develop/core/gethwrappers/shared/generated/link_token/link_token.go#L34 @@ -29,10 +29,11 @@ const ( ) type MCMSWithTimelockConfig struct { - Canceller config.Config - Bypasser config.Config - Proposer config.Config - TimelockMinDelay *big.Int + Canceller config.Config + Bypasser config.Config + Proposer config.Config + TimelockExecutors []common.Address + TimelockMinDelay *big.Int } type OCRParameters struct { diff --git a/deployment/common/view/v1_0/mcms.go b/deployment/common/view/v1_0/mcms.go index bc971623545..25ca614a553 100644 --- a/deployment/common/view/v1_0/mcms.go +++ b/deployment/common/view/v1_0/mcms.go @@ -107,24 +107,11 @@ func GenerateTimelockView(tl owner_helpers.RBACTimelock) (TimelockView, error) { }, nil } -type CallProxyView struct { - types.ContractMetaData -} - -func GenerateCallProxyView(cp owner_helpers.CallProxy) (CallProxyView, error) { - return CallProxyView{ - ContractMetaData: types.ContractMetaData{ - Address: cp.Address(), - }, - }, nil -} - type MCMSWithTimelockView struct { - Bypasser MCMSView `json:"bypasser"` - Canceller MCMSView `json:"canceller"` - Proposer MCMSView `json:"proposer"` - Timelock TimelockView `json:"timelock"` - CallProxy CallProxyView `json:"callProxy"` + Bypasser MCMSView `json:"bypasser"` + Canceller MCMSView `json:"canceller"` + Proposer MCMSView `json:"proposer"` + Timelock TimelockView `json:"timelock"` } func GenerateMCMSWithTimelockView( @@ -137,10 +124,6 @@ func GenerateMCMSWithTimelockView( if err != nil { return MCMSWithTimelockView{}, nil } - callProxyView, err := GenerateCallProxyView(owner_helpers.CallProxy{}) - if err != nil { - return MCMSWithTimelockView{}, nil - } bypasserView, err := GenerateMCMSView(bypasser) if err != nil { return MCMSWithTimelockView{}, nil @@ -159,6 +142,5 @@ func GenerateMCMSWithTimelockView( Bypasser: bypasserView, Proposer: proposerView, Canceller: cancellerView, - CallProxy: callProxyView, }, nil } diff --git a/deployment/keystone/changeset/accept_ownership_test.go b/deployment/keystone/changeset/accept_ownership_test.go index 64f3b3c86fb..f205adda496 100644 --- a/deployment/keystone/changeset/accept_ownership_test.go +++ b/deployment/keystone/changeset/accept_ownership_test.go @@ -25,7 +25,7 @@ func TestAcceptAllOwnership(t *testing.T) { } env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) registrySel := env.AllChainSelectors()[0] - env, err := commonchangeset.ApplyChangesets(t, env, nil, nil, []commonchangeset.ChangesetApplication{ + env, err := commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.DeployCapabilityRegistry), Config: registrySel, @@ -42,10 +42,11 @@ func TestAcceptAllOwnership(t *testing.T) { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), Config: map[uint64]types.MCMSWithTimelockConfig{ registrySel: { - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: env.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), }, }, }, @@ -58,8 +59,6 @@ func TestAcceptAllOwnership(t *testing.T) { _, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*owner_helpers.RBACTimelock{ registrySel: timelock.Timelock, - }, map[uint64]*owner_helpers.CallProxy{ - registrySel: timelock.CallProxy, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.AcceptAllOwnershipsProposal), diff --git a/deployment/keystone/changeset/deploy_forwarder_test.go b/deployment/keystone/changeset/deploy_forwarder_test.go index 2c449000397..32a53f1cf08 100644 --- a/deployment/keystone/changeset/deploy_forwarder_test.go +++ b/deployment/keystone/changeset/deploy_forwarder_test.go @@ -118,14 +118,11 @@ func TestConfigureForwarders(t *testing.T) { require.Nil(t, csOut.AddressBook) timelocks := make(map[uint64]*gethwrappers.RBACTimelock) - callProxies := make(map[uint64]*gethwrappers.CallProxy) for selector, contractSet := range te.ContractSets() { require.NotNil(t, contractSet.Timelock) timelocks[selector] = contractSet.Timelock - require.NotNil(t, contractSet.CallProxy) - callProxies[selector] = contractSet.CallProxy } - _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, callProxies, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureForwardContracts), Config: cfg, diff --git a/deployment/keystone/changeset/deploy_ocr3_test.go b/deployment/keystone/changeset/deploy_ocr3_test.go index 8184351fcde..0d49af68823 100644 --- a/deployment/keystone/changeset/deploy_ocr3_test.go +++ b/deployment/keystone/changeset/deploy_ocr3_test.go @@ -130,13 +130,10 @@ func TestConfigureOCR3(t *testing.T) { var timelocks = map[uint64]*gethwrappers.RBACTimelock{ te.RegistrySelector: contractSetsResp.ContractSets[te.RegistrySelector].Timelock, } - var callProxies = map[uint64]*gethwrappers.CallProxy{ - te.RegistrySelector: contractSetsResp.ContractSets[te.RegistrySelector].CallProxy, - } // now apply the changeset such that the proposal is signed and execed w2 := &bytes.Buffer{} cfg.WriteGeneratedConfig = w2 - _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, callProxies, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureOCR3Contract), Config: cfg, diff --git a/deployment/keystone/changeset/helpers_test.go b/deployment/keystone/changeset/helpers_test.go index 769e4a38132..d4435d8f7a6 100644 --- a/deployment/keystone/changeset/helpers_test.go +++ b/deployment/keystone/changeset/helpers_test.go @@ -130,7 +130,7 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { Chains: chains, ExistingAddresses: deployment.NewMemoryAddressBook(), } - e, err := commonchangeset.ApplyChangesets(t, e, nil, nil, []commonchangeset.ChangesetApplication{ + e, err := commonchangeset.ApplyChangesets(t, e, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(kschangeset.DeployCapabilityRegistry), Config: registryChainSel, @@ -265,13 +265,14 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { for sel := range env.Chains { t.Logf("Enabling MCMS on chain %d", sel) timelockCfgs[sel] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: env.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), } } - env, err = commonchangeset.ApplyChangesets(t, env, nil, nil, []commonchangeset.ChangesetApplication{ + env, err = commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), Config: timelockCfgs, @@ -290,7 +291,7 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { require.NoError(t, mcms.Validate()) // transfer ownership of all contracts to the MCMS - env, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*gethwrappers.RBACTimelock{sel: mcms.Timelock}, map[uint64]*gethwrappers.CallProxy{sel: mcms.CallProxy}, []commonchangeset.ChangesetApplication{ + env, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*gethwrappers.RBACTimelock{sel: mcms.Timelock}, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(kschangeset.AcceptAllOwnershipsProposal), Config: &kschangeset.AcceptAllOwnershipRequest{ From 5b966294540073d657e8fb0ac228b68635713ccf Mon Sep 17 00:00:00 2001 From: Akhil Chainani Date: Sun, 8 Dec 2024 14:06:31 -0500 Subject: [PATCH 16/41] Revert "revert all changes" This reverts commit c17911eb1ff4382b3f80d0edb055d748096dc59f. --- .../ccip/changeset/accept_ownership_test.go | 6 ++- .../changeset/cs_active_candidate_test.go | 12 ++++-- .../ccip/changeset/cs_add_chain_test.go | 20 +++++---- .../ccip/changeset/cs_deploy_chain_test.go | 11 +++-- deployment/ccip/changeset/state.go | 1 + deployment/ccip/changeset/test_helpers.go | 17 ++++---- .../changeset/deploy_link_token_test.go | 2 +- deployment/common/changeset/internal/mcms.go | 41 +++++++++++++++++-- .../common/changeset/internal/mcms_test.go | 12 ++---- .../common/changeset/mcms_test_helpers.go | 5 ++- deployment/common/changeset/state.go | 18 +++++++- deployment/common/changeset/test_helpers.go | 9 +++- .../transfer_to_mcms_with_timelock_test.go | 13 +++--- deployment/common/types/types.go | 11 +++-- deployment/common/view/v1_0/mcms.go | 26 ++++++++++-- .../changeset/accept_ownership_test.go | 13 +++--- .../changeset/deploy_forwarder_test.go | 5 ++- .../keystone/changeset/deploy_ocr3_test.go | 5 ++- deployment/keystone/changeset/helpers_test.go | 15 ++++--- 19 files changed, 166 insertions(+), 76 deletions(-) diff --git a/deployment/ccip/changeset/accept_ownership_test.go b/deployment/ccip/changeset/accept_ownership_test.go index 796db6aed09..45a6fc491cf 100644 --- a/deployment/ccip/changeset/accept_ownership_test.go +++ b/deployment/ccip/changeset/accept_ownership_test.go @@ -33,6 +33,10 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { source: state.Chains[source].Timelock, dest: state.Chains[dest].Timelock, } + callProxies := map[uint64]*gethwrappers.CallProxy{ + source: state.Chains[source].CallProxy, + dest: state.Chains[dest].CallProxy, + } // at this point we have the initial deploys done, now we need to transfer ownership // to the timelock contract @@ -40,7 +44,7 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { require.NoError(t, err) // compose the transfer ownership and accept ownership changesets - _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocks, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocks, callProxies, []commonchangeset.ChangesetApplication{ // note this doesn't have proposals. { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), diff --git a/deployment/ccip/changeset/cs_active_candidate_test.go b/deployment/ccip/changeset/cs_active_candidate_test.go index 4bd0c9fd7a4..21a74b68b47 100644 --- a/deployment/ccip/changeset/cs_active_candidate_test.go +++ b/deployment/ccip/changeset/cs_active_candidate_test.go @@ -95,7 +95,11 @@ func TestActiveCandidate(t *testing.T) { for _, chain := range allChains { timelocks[chain] = state.Chains[chain].Timelock } - _, err = commonchangeset.ApplyChangesets(t, e, timelocks, []commonchangeset.ChangesetApplication{ + callProxies := make(map[uint64]*gethwrappers.CallProxy) + for _, chain := range allChains { + callProxies[chain] = state.Chains[chain].CallProxy + } + _, err = commonchangeset.ApplyChangesets(t, e, timelocks, callProxies, []commonchangeset.ChangesetApplication{ // note this doesn't have proposals. { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), @@ -177,7 +181,7 @@ func TestActiveCandidate(t *testing.T) { }}, "set new candidates on commit plugin", 0) require.NoError(t, err) setCommitCandidateSigned := commonchangeset.SignProposal(t, e, setCommitCandidateProposal) - commonchangeset.ExecuteProposal(t, e, setCommitCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) + commonchangeset.ExecuteProposal(t, e, setCommitCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, state.Chains[tenv.HomeChainSel].CallProxy, tenv.HomeChainSel) // create the op for the commit plugin as well setExecCandidateOp, err := setCandidateOnExistingDon( @@ -195,7 +199,7 @@ func TestActiveCandidate(t *testing.T) { }}, "set new candidates on commit and exec plugins", 0) require.NoError(t, err) setExecCandidateSigned := commonchangeset.SignProposal(t, e, setExecCandidateProposal) - commonchangeset.ExecuteProposal(t, e, setExecCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) + commonchangeset.ExecuteProposal(t, e, setExecCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, state.Chains[tenv.HomeChainSel].CallProxy, tenv.HomeChainSel) // check setup was successful by confirming number of nodes from cap reg donInfo, err = state.Chains[tenv.HomeChainSel].CapabilityRegistry.GetDON(nil, donID) @@ -222,7 +226,7 @@ func TestActiveCandidate(t *testing.T) { }}, "promote candidates and revoke actives", 0) require.NoError(t, err) promoteSigned := commonchangeset.SignProposal(t, e, promoteProposal) - commonchangeset.ExecuteProposal(t, e, promoteSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) + commonchangeset.ExecuteProposal(t, e, promoteSigned, state.Chains[tenv.HomeChainSel].Timelock, state.Chains[tenv.HomeChainSel].CallProxy, tenv.HomeChainSel) // [NEW ACTIVE, NO CANDIDATE] done promoting // [NEW ACTIVE, NO CANDIDATE] check onchain state diff --git a/deployment/ccip/changeset/cs_add_chain_test.go b/deployment/ccip/changeset/cs_add_chain_test.go index f6e1c04c469..2c660ac0b3f 100644 --- a/deployment/ccip/changeset/cs_add_chain_test.go +++ b/deployment/ccip/changeset/cs_add_chain_test.go @@ -52,13 +52,12 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) cfg := commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: e.Env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), } - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), Config: initialDeploy, @@ -107,7 +106,7 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, err) // Deploy contracts to new chain - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), Config: []uint64{newChain}, @@ -150,6 +149,10 @@ func TestAddChainInbound(t *testing.T) { initialDeploy[0]: state.Chains[initialDeploy[0]].Timelock, initialDeploy[1]: state.Chains[initialDeploy[1]].Timelock, initialDeploy[2]: state.Chains[initialDeploy[2]].Timelock, + }, map[uint64]*gethwrappers.CallProxy{ + initialDeploy[0]: state.Chains[initialDeploy[0]].CallProxy, + initialDeploy[1]: state.Chains[initialDeploy[1]].CallProxy, + initialDeploy[2]: state.Chains[initialDeploy[2]].CallProxy, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), @@ -182,6 +185,9 @@ func TestAddChainInbound(t *testing.T) { _, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*gethwrappers.RBACTimelock{ e.HomeChainSel: state.Chains[e.HomeChainSel].Timelock, newChain: state.Chains[newChain].Timelock, + }, map[uint64]*gethwrappers.CallProxy{ + e.HomeChainSel: state.Chains[e.HomeChainSel].CallProxy, + newChain: state.Chains[newChain].CallProxy, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(AddDonAndSetCandidateChangeset), diff --git a/deployment/ccip/changeset/cs_deploy_chain_test.go b/deployment/ccip/changeset/cs_deploy_chain_test.go index 234d73cc4b5..3dd1d6c6103 100644 --- a/deployment/ccip/changeset/cs_deploy_chain_test.go +++ b/deployment/ccip/changeset/cs_deploy_chain_test.go @@ -31,14 +31,13 @@ func TestDeployChainContractsChangeset(t *testing.T) { cfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) for _, chain := range e.AllChainSelectors() { cfg[chain] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: e.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), } } - e, err = commonchangeset.ApplyChangesets(t, e, nil, []commonchangeset.ChangesetApplication{ + e, err = commonchangeset.ApplyChangesets(t, e, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(DeployHomeChain), Config: DeployHomeChainConfig{ diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index 22ae59fc360..122ce8ec13c 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -311,6 +311,7 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type for address, tvStr := range addresses { switch tvStr.String() { case deployment.NewTypeAndVersion(commontypes.RBACTimelock, deployment.Version1_0_0).String(), + deployment.NewTypeAndVersion(commontypes.CallProxy, deployment.Version1_0_0).String(), deployment.NewTypeAndVersion(commontypes.ProposerManyChainMultisig, deployment.Version1_0_0).String(), deployment.NewTypeAndVersion(commontypes.CancellerManyChainMultisig, deployment.Version1_0_0).String(), deployment.NewTypeAndVersion(commontypes.BypasserManyChainMultisig, deployment.Version1_0_0).String(), diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 1ee8b0d0e42..422e0933176 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -279,11 +279,10 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) for _, c := range e.Env.AllChainSelectors() { mcmsCfg[c] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: e.Env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), } } var ( @@ -298,7 +297,7 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, } // Need to deploy prerequisites first so that we can form the USDC config // no proposals to be made, timelock can be passed as nil here - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), Config: allChains, @@ -367,13 +366,15 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, // Build the per chain config. chainConfigs := make(map[uint64]CCIPOCRParams) timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) + callProxiesPerChain := make(map[uint64]*gethwrappers.CallProxy) for _, chain := range allChains { timelocksPerChain[chain] = state.Chains[chain].Timelock + callProxiesPerChain[chain] = state.Chains[chain].CallProxy tokenInfo := tokenConfig.GetTokenInfo(e.Env.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) chainConfigs[chain] = DefaultOCRParams(e.FeedChainSel, tokenInfo, tokenDataProviders) } // Deploy second set of changesets to deploy and configure the CCIP contracts. - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, callProxiesPerChain, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(ConfigureNewChains), Config: NewChainsConfig{ @@ -823,7 +824,7 @@ func ProcessChangeset(t *testing.T, e deployment.Environment, c deployment.Chang signed := commonchangeset.SignProposal(t, e, &prop) for _, sel := range chains.ToSlice() { - commonchangeset.ExecuteProposal(t, e, signed, state.Chains[sel].Timelock, sel) + commonchangeset.ExecuteProposal(t, e, signed, state.Chains[sel].Timelock, state.Chains[sel].CallProxy, sel) } } } diff --git a/deployment/common/changeset/deploy_link_token_test.go b/deployment/common/changeset/deploy_link_token_test.go index a61743e9bf4..5eb3d0a1e34 100644 --- a/deployment/common/changeset/deploy_link_token_test.go +++ b/deployment/common/changeset/deploy_link_token_test.go @@ -18,7 +18,7 @@ func TestDeployLinkToken(t *testing.T) { Chains: 1, }) chain1 := e.AllChainSelectors()[0] - e, err := changeset.ApplyChangesets(t, e, nil, []changeset.ChangesetApplication{ + e, err := changeset.ApplyChangesets(t, e, nil, nil, []changeset.ChangesetApplication{ { Changeset: changeset.WrapChangeSet(changeset.DeployLinkToken), Config: []uint64{chain1}, diff --git a/deployment/common/changeset/internal/mcms.go b/deployment/common/changeset/internal/mcms.go index 281f43924f4..39c319f0cd1 100644 --- a/deployment/common/changeset/internal/mcms.go +++ b/deployment/common/changeset/internal/mcms.go @@ -55,6 +55,7 @@ type MCMSWithTimelockDeploy struct { Bypasser *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] Proposer *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] Timelock *deployment.ContractDeploy[*owner_helpers.RBACTimelock] + CallProxy *deployment.ContractDeploy[*owner_helpers.CallProxy] } func DeployMCMSWithTimelockContractsBatch( @@ -106,10 +107,10 @@ func DeployMCMSWithTimelockContracts( // TODO: Could expose this as config? // Or keep this enforced to follow the same pattern? chain.DeployerKey.From, - []common.Address{proposer.Address}, // proposers - config.TimelockExecutors, //executors - []common.Address{canceller.Address}, // cancellers - []common.Address{bypasser.Address}, // bypassers + []common.Address{proposer.Address}, // proposers + []common.Address{}, // executors + []common.Address{canceller.Address, proposer.Address, bypasser.Address}, // cancellers + []common.Address{bypasser.Address}, // bypassers ) return deployment.ContractDeploy[*owner_helpers.RBACTimelock]{ timelock, cc, tx2, deployment.NewTypeAndVersion(types.RBACTimelock, deployment.Version1_0_0), err2, @@ -119,6 +120,37 @@ func DeployMCMSWithTimelockContracts( lggr.Errorw("Failed to deploy timelock", "chain", chain.String(), "err", err) return nil, err } + + callProxy, err := deployment.DeployContract(lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*owner_helpers.CallProxy] { + callProxy, tx2, cc, err2 := owner_helpers.DeployCallProxy( + chain.DeployerKey, + chain.Client, + timelock.Address, + ) + return deployment.ContractDeploy[*owner_helpers.CallProxy]{ + callProxy, cc, tx2, deployment.NewTypeAndVersion(types.CallProxy, deployment.Version1_0_0), err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy call proxy", "chain", chain.String(), "err", err) + return nil, err + } + + grantRoleTx, err := timelock.Contract.GrantRole( + chain.DeployerKey, + v1_0.EXECUTOR_ROLE.ID, + callProxy.Address, + ) + if err != nil { + lggr.Errorw("Failed to grant timelock executor role", "chain", chain.String(), "err", err) + return nil, err + } + + if _, err := deployment.ConfirmIfNoError(chain, grantRoleTx, err); err != nil { + lggr.Errorw("Failed to grant timelock executor role", "chain", chain.String(), "err", err) + return nil, err + } // We grant the timelock the admin role on the MCMS contracts. tx, err := timelock.Contract.GrantRole(chain.DeployerKey, v1_0.ADMIN_ROLE.ID, timelock.Address) @@ -133,5 +165,6 @@ func DeployMCMSWithTimelockContracts( Bypasser: bypasser, Proposer: proposer, Timelock: timelock, + CallProxy: callProxy, }, nil } diff --git a/deployment/common/changeset/internal/mcms_test.go b/deployment/common/changeset/internal/mcms_test.go index 2269911f4cd..10fb1d980de 100644 --- a/deployment/common/changeset/internal/mcms_test.go +++ b/deployment/common/changeset/internal/mcms_test.go @@ -5,7 +5,6 @@ import ( "math/big" "testing" - "github.com/ethereum/go-ethereum/common" chainsel "github.com/smartcontractkit/chain-selectors" "github.com/stretchr/testify/require" @@ -37,18 +36,15 @@ func TestDeployMCMSWithTimelockContracts(t *testing.T) { _, err := internal.DeployMCMSWithTimelockContracts(lggr, chains[chainsel.TEST_90000001.Selector], ab, types.MCMSWithTimelockConfig{ - Canceller: changeset.SingleGroupMCMS(t), - Bypasser: changeset.SingleGroupMCMS(t), - Proposer: changeset.SingleGroupMCMS(t), - TimelockExecutors: []common.Address{ - chains[chainsel.TEST_90000001.Selector].DeployerKey.From, - }, + Canceller: changeset.SingleGroupMCMS(t), + Bypasser: changeset.SingleGroupMCMS(t), + Proposer: changeset.SingleGroupMCMS(t), TimelockMinDelay: big.NewInt(0), }) require.NoError(t, err) addresses, err := ab.AddressesForChain(chainsel.TEST_90000001.Selector) require.NoError(t, err) - require.Len(t, addresses, 4) + require.Len(t, addresses, 5) mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chains[chainsel.TEST_90000001.Selector], addresses) require.NoError(t, err) v, err := mcmsState.GenerateMCMSWithTimelockView() diff --git a/deployment/common/changeset/mcms_test_helpers.go b/deployment/common/changeset/mcms_test_helpers.go index 3951149815c..b1fd4d878fc 100644 --- a/deployment/common/changeset/mcms_test_helpers.go +++ b/deployment/common/changeset/mcms_test_helpers.go @@ -65,7 +65,7 @@ func SignProposal(t *testing.T, env deployment.Environment, proposal *timelock.M } func ExecuteProposal(t *testing.T, env deployment.Environment, executor *mcms.Executor, - timelock *owner_helpers.RBACTimelock, sel uint64) { + timelock *owner_helpers.RBACTimelock, callProxy *owner_helpers.CallProxy, sel uint64) { t.Log("Executing proposal on chain", sel) // Set the root. tx, err2 := executor.SetRootOnChain(env.Chains[sel].Client, env.Chains[sel].DeployerKey, mcms.ChainIdentifier(sel)) @@ -104,7 +104,8 @@ func ExecuteProposal(t *testing.T, env deployment.Environment, executor *mcms.Ex Value: it.Event.Value, }) } - tx, err := timelock.ExecuteBatch( + timelockExecutorProxy, err := owner_helpers.NewRBACTimelock(callProxy.Address(), env.Chains[sel].Client) + tx, err := timelockExecutorProxy.ExecuteBatch( env.Chains[sel].DeployerKey, calls, pred, salt) require.NoError(t, err) _, err = env.Chains[sel].Confirm(tx) diff --git a/deployment/common/changeset/state.go b/deployment/common/changeset/state.go index 0055c908f8d..a580c13b40b 100644 --- a/deployment/common/changeset/state.go +++ b/deployment/common/changeset/state.go @@ -23,6 +23,7 @@ type MCMSWithTimelockState struct { BypasserMcm *owner_helpers.ManyChainMultiSig ProposerMcm *owner_helpers.ManyChainMultiSig Timelock *owner_helpers.RBACTimelock + CallProxy *owner_helpers.CallProxy } // Validate checks that all fields are non-nil, ensuring it's ready @@ -40,6 +41,9 @@ func (state MCMSWithTimelockState) Validate() error { if state.BypasserMcm == nil { return errors.New("bypasser not found") } + if state.CallProxy == nil { + return errors.New("call proxy not found") + } return nil } @@ -51,6 +55,10 @@ func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWith 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 @@ -68,6 +76,7 @@ func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWith Bypasser: bypasserView, Proposer: proposerView, Canceller: cancellerView, + CallProxy: callProxyView, }, nil } @@ -82,6 +91,7 @@ func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string state := MCMSWithTimelockState{} // We expect one of each contract on the chain. timelock := deployment.NewTypeAndVersion(types.RBACTimelock, deployment.Version1_0_0) + callProxy := deployment.NewTypeAndVersion(types.CallProxy, deployment.Version1_0_0) proposer := deployment.NewTypeAndVersion(types.ProposerManyChainMultisig, deployment.Version1_0_0) canceller := deployment.NewTypeAndVersion(types.CancellerManyChainMultisig, deployment.Version1_0_0) bypasser := deployment.NewTypeAndVersion(types.BypasserManyChainMultisig, deployment.Version1_0_0) @@ -89,7 +99,7 @@ func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string // Ensure we either have the bundle or not. _, err := deployment.AddressesContainBundle(addresses, map[deployment.TypeAndVersion]struct{}{ - timelock: {}, proposer: {}, canceller: {}, bypasser: {}, + timelock: {}, proposer: {}, canceller: {}, bypasser: {}, callProxy: {}, }) if err != nil { return nil, fmt.Errorf("unable to check MCMS contracts on chain %s error: %w", chain.Name(), err) @@ -103,6 +113,12 @@ func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string return nil, err } state.Timelock = tl + case callProxy: + cp, err := owner_helpers.NewCallProxy(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.CallProxy = cp case proposer: mcms, err := owner_helpers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) if err != nil { diff --git a/deployment/common/changeset/test_helpers.go b/deployment/common/changeset/test_helpers.go index 913b4544f30..0d068ade971 100644 --- a/deployment/common/changeset/test_helpers.go +++ b/deployment/common/changeset/test_helpers.go @@ -33,7 +33,7 @@ func WrapChangeSet[C any](fn deployment.ChangeSet[C]) func(e deployment.Environm } // ApplyChangesets applies the changeset applications to the environment and returns the updated environment. -func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain map[uint64]*gethwrappers.RBACTimelock, changesetApplications []ChangesetApplication) (deployment.Environment, error) { +func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain map[uint64]*gethwrappers.RBACTimelock, callProxiesPerChain map[uint64]*gethwrappers.CallProxy, changesetApplications []ChangesetApplication) (deployment.Environment, error) { currentEnv := e for i, csa := range changesetApplications { out, err := csa.Changeset(currentEnv, csa.Config) @@ -79,7 +79,12 @@ func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain m if !ok || timelock == nil { return deployment.Environment{}, fmt.Errorf("timelock not found for chain %d", sel) } - ExecuteProposal(t, e, signed, timelock, sel) + + callProxy, ok := callProxiesPerChain[sel] + if !ok || callProxy == nil { + return deployment.Environment{}, fmt.Errorf("call proxy not found for chain %d", sel) + } + ExecuteProposal(t, e, signed, timelock, callProxy, sel) } } } diff --git a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go index 6cdff286707..fd50c9ad66f 100644 --- a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go +++ b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go @@ -21,7 +21,7 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { Nodes: 1, }) chain1 := e.AllChainSelectors()[0] - e, err := ApplyChangesets(t, e, nil, []ChangesetApplication{ + e, err := ApplyChangesets(t, e, nil, nil, []ChangesetApplication{ { Changeset: WrapChangeSet(DeployLinkToken), Config: []uint64{chain1}, @@ -30,11 +30,10 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { Changeset: WrapChangeSet(DeployMCMSWithTimelock), Config: map[uint64]types.MCMSWithTimelockConfig{ chain1: { - Canceller: SingleGroupMCMS(t), - Bypasser: SingleGroupMCMS(t), - Proposer: SingleGroupMCMS(t), - TimelockExecutors: e.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: SingleGroupMCMS(t), + Bypasser: SingleGroupMCMS(t), + Proposer: SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), }, }, }, @@ -48,6 +47,8 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { require.NoError(t, err) e, err = ApplyChangesets(t, e, map[uint64]*owner_helpers.RBACTimelock{ chain1: state.Timelock, + }, map[uint64]*owner_helpers.CallProxy{ + chain1: state.CallProxy, }, []ChangesetApplication{ { Changeset: WrapChangeSet(TransferToMCMSWithTimelock), diff --git a/deployment/common/types/types.go b/deployment/common/types/types.go index 386ef8fbb36..0f04421af43 100644 --- a/deployment/common/types/types.go +++ b/deployment/common/types/types.go @@ -5,7 +5,6 @@ import ( "math/big" "time" - "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" "github.com/smartcontractkit/chainlink/deployment" @@ -16,6 +15,7 @@ const ( CancellerManyChainMultisig deployment.ContractType = "CancellerManyChainMultiSig" ProposerManyChainMultisig deployment.ContractType = "ProposerManyChainMultiSig" RBACTimelock deployment.ContractType = "RBACTimelock" + CallProxy deployment.ContractType = "CallProxy" // LinkToken is the burn/mint link token. It should be used everywhere for // new deployments. Corresponds to // https://github.com/smartcontractkit/chainlink/blob/develop/core/gethwrappers/shared/generated/link_token/link_token.go#L34 @@ -29,11 +29,10 @@ const ( ) type MCMSWithTimelockConfig struct { - Canceller config.Config - Bypasser config.Config - Proposer config.Config - TimelockExecutors []common.Address - TimelockMinDelay *big.Int + Canceller config.Config + Bypasser config.Config + Proposer config.Config + TimelockMinDelay *big.Int } type OCRParameters struct { diff --git a/deployment/common/view/v1_0/mcms.go b/deployment/common/view/v1_0/mcms.go index 25ca614a553..bc971623545 100644 --- a/deployment/common/view/v1_0/mcms.go +++ b/deployment/common/view/v1_0/mcms.go @@ -107,11 +107,24 @@ func GenerateTimelockView(tl owner_helpers.RBACTimelock) (TimelockView, error) { }, nil } +type CallProxyView struct { + types.ContractMetaData +} + +func GenerateCallProxyView(cp owner_helpers.CallProxy) (CallProxyView, error) { + return CallProxyView{ + ContractMetaData: types.ContractMetaData{ + Address: cp.Address(), + }, + }, nil +} + type MCMSWithTimelockView struct { - Bypasser MCMSView `json:"bypasser"` - Canceller MCMSView `json:"canceller"` - Proposer MCMSView `json:"proposer"` - Timelock TimelockView `json:"timelock"` + Bypasser MCMSView `json:"bypasser"` + Canceller MCMSView `json:"canceller"` + Proposer MCMSView `json:"proposer"` + Timelock TimelockView `json:"timelock"` + CallProxy CallProxyView `json:"callProxy"` } func GenerateMCMSWithTimelockView( @@ -124,6 +137,10 @@ func GenerateMCMSWithTimelockView( if err != nil { return MCMSWithTimelockView{}, nil } + callProxyView, err := GenerateCallProxyView(owner_helpers.CallProxy{}) + if err != nil { + return MCMSWithTimelockView{}, nil + } bypasserView, err := GenerateMCMSView(bypasser) if err != nil { return MCMSWithTimelockView{}, nil @@ -142,5 +159,6 @@ func GenerateMCMSWithTimelockView( Bypasser: bypasserView, Proposer: proposerView, Canceller: cancellerView, + CallProxy: callProxyView, }, nil } diff --git a/deployment/keystone/changeset/accept_ownership_test.go b/deployment/keystone/changeset/accept_ownership_test.go index f205adda496..64f3b3c86fb 100644 --- a/deployment/keystone/changeset/accept_ownership_test.go +++ b/deployment/keystone/changeset/accept_ownership_test.go @@ -25,7 +25,7 @@ func TestAcceptAllOwnership(t *testing.T) { } env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) registrySel := env.AllChainSelectors()[0] - env, err := commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ + env, err := commonchangeset.ApplyChangesets(t, env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.DeployCapabilityRegistry), Config: registrySel, @@ -42,11 +42,10 @@ func TestAcceptAllOwnership(t *testing.T) { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), Config: map[uint64]types.MCMSWithTimelockConfig{ registrySel: { - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), }, }, }, @@ -59,6 +58,8 @@ func TestAcceptAllOwnership(t *testing.T) { _, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*owner_helpers.RBACTimelock{ registrySel: timelock.Timelock, + }, map[uint64]*owner_helpers.CallProxy{ + registrySel: timelock.CallProxy, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.AcceptAllOwnershipsProposal), diff --git a/deployment/keystone/changeset/deploy_forwarder_test.go b/deployment/keystone/changeset/deploy_forwarder_test.go index 32a53f1cf08..2c449000397 100644 --- a/deployment/keystone/changeset/deploy_forwarder_test.go +++ b/deployment/keystone/changeset/deploy_forwarder_test.go @@ -118,11 +118,14 @@ func TestConfigureForwarders(t *testing.T) { require.Nil(t, csOut.AddressBook) timelocks := make(map[uint64]*gethwrappers.RBACTimelock) + callProxies := make(map[uint64]*gethwrappers.CallProxy) for selector, contractSet := range te.ContractSets() { require.NotNil(t, contractSet.Timelock) timelocks[selector] = contractSet.Timelock + require.NotNil(t, contractSet.CallProxy) + callProxies[selector] = contractSet.CallProxy } - _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, callProxies, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureForwardContracts), Config: cfg, diff --git a/deployment/keystone/changeset/deploy_ocr3_test.go b/deployment/keystone/changeset/deploy_ocr3_test.go index 0d49af68823..8184351fcde 100644 --- a/deployment/keystone/changeset/deploy_ocr3_test.go +++ b/deployment/keystone/changeset/deploy_ocr3_test.go @@ -130,10 +130,13 @@ func TestConfigureOCR3(t *testing.T) { var timelocks = map[uint64]*gethwrappers.RBACTimelock{ te.RegistrySelector: contractSetsResp.ContractSets[te.RegistrySelector].Timelock, } + var callProxies = map[uint64]*gethwrappers.CallProxy{ + te.RegistrySelector: contractSetsResp.ContractSets[te.RegistrySelector].CallProxy, + } // now apply the changeset such that the proposal is signed and execed w2 := &bytes.Buffer{} cfg.WriteGeneratedConfig = w2 - _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, callProxies, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureOCR3Contract), Config: cfg, diff --git a/deployment/keystone/changeset/helpers_test.go b/deployment/keystone/changeset/helpers_test.go index d4435d8f7a6..769e4a38132 100644 --- a/deployment/keystone/changeset/helpers_test.go +++ b/deployment/keystone/changeset/helpers_test.go @@ -130,7 +130,7 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { Chains: chains, ExistingAddresses: deployment.NewMemoryAddressBook(), } - e, err := commonchangeset.ApplyChangesets(t, e, nil, []commonchangeset.ChangesetApplication{ + e, err := commonchangeset.ApplyChangesets(t, e, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(kschangeset.DeployCapabilityRegistry), Config: registryChainSel, @@ -265,14 +265,13 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { for sel := range env.Chains { t.Logf("Enabling MCMS on chain %d", sel) timelockCfgs[sel] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), } } - env, err = commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ + env, err = commonchangeset.ApplyChangesets(t, env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), Config: timelockCfgs, @@ -291,7 +290,7 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { require.NoError(t, mcms.Validate()) // transfer ownership of all contracts to the MCMS - env, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*gethwrappers.RBACTimelock{sel: mcms.Timelock}, []commonchangeset.ChangesetApplication{ + env, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*gethwrappers.RBACTimelock{sel: mcms.Timelock}, map[uint64]*gethwrappers.CallProxy{sel: mcms.CallProxy}, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(kschangeset.AcceptAllOwnershipsProposal), Config: &kschangeset.AcceptAllOwnershipRequest{ From a51f09af3095b23011be1d5d43851ff34d376691 Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 9 Dec 2024 13:39:38 -0600 Subject: [PATCH 17/41] chore: rename load state funcs --- deployment/common/changeset/internal/mcms.go | 1 + deployment/common/changeset/link_transfer_timelock.go | 4 ++-- deployment/common/changeset/mint_link_timelock.go | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/deployment/common/changeset/internal/mcms.go b/deployment/common/changeset/internal/mcms.go index 39c319f0cd1..c23780f173c 100644 --- a/deployment/common/changeset/internal/mcms.go +++ b/deployment/common/changeset/internal/mcms.go @@ -2,6 +2,7 @@ package internal import ( "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" diff --git a/deployment/common/changeset/link_transfer_timelock.go b/deployment/common/changeset/link_transfer_timelock.go index 6dbdba8e21a..6f9b4c494a2 100644 --- a/deployment/common/changeset/link_transfer_timelock.go +++ b/deployment/common/changeset/link_transfer_timelock.go @@ -37,12 +37,12 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockReq chainID := mcms.ChainIdentifier(chainSelector) chain := e.Chains[chainSelector] addrs, err := e.ExistingAddresses.AddressesForChain(chainSelector) - linkState, err := LoadLinkTokenState(chain, addrs) + linkState, err := MaybeLoadLinkTokenState(chain, addrs) if err != nil { return deployment.ChangesetOutput{}, err } linkAddress := linkState.LinkToken.Address() - mcmsState, err := LoadMCMSWithTimelockState(chain, addrs) + mcmsState, err := MaybeLoadMCMSWithTimelockState(chain, addrs) if err != nil { return deployment.ChangesetOutput{}, err } diff --git a/deployment/common/changeset/mint_link_timelock.go b/deployment/common/changeset/mint_link_timelock.go index 2462bf38c07..5af807499d7 100644 --- a/deployment/common/changeset/mint_link_timelock.go +++ b/deployment/common/changeset/mint_link_timelock.go @@ -21,11 +21,11 @@ func MintLinkTimelock(e deployment.Environment, req *MintLinkTimelockRequest) (d if err != nil { return deployment.ChangesetOutput{}, err } - linkState, err := LoadLinkTokenState(chain, addresses) + linkState, err := MaybeLoadLinkTokenState(chain, addresses) if err != nil { return deployment.ChangesetOutput{}, err } - timelockState, err := LoadMCMSWithTimelockState(chain, addresses) + timelockState, err := MaybeLoadMCMSWithTimelockState(chain, addresses) if err != nil { return deployment.ChangesetOutput{}, err } From 898a20d0d53e7fcdb29c43a5b59555f4a50bac66 Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 10 Dec 2024 08:36:19 -0600 Subject: [PATCH 18/41] feat: add mcms config flag --- .../changeset/link_transfer_timelock.go | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/deployment/common/changeset/link_transfer_timelock.go b/deployment/common/changeset/link_transfer_timelock.go index 6f9b4c494a2..bd56694921c 100644 --- a/deployment/common/changeset/link_transfer_timelock.go +++ b/deployment/common/changeset/link_transfer_timelock.go @@ -24,11 +24,12 @@ type LinkTransferTimelockRequest struct { MinDelay time.Duration // delay for timelock worker to execute the transfers. OverrideRoot bool StartingOpCount map[uint64]uint64 + UseMCMS bool } var _ deployment.ChangeSet[*LinkTransferTimelockRequest] = LinkTransferTimelock -// LinkTransferTimelock takes the given link transfers and creates an MCMS proposal for them. +// LinkTransferTimelock takes the given link transfers and executes them or creates an MCMS proposal for them. func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockRequest) (deployment.ChangesetOutput, error) { chainMetadata := map[mcms.ChainIdentifier]mcms.ChainMetadata{} timelockAddresses := map[mcms.ChainIdentifier]common.Address{} @@ -62,9 +63,12 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockReq ChainIdentifier: chainID, Batch: []mcms.Operation{}, } - + opts := deployment.SimTransactOpts() + if !req.UseMCMS { + opts = chain.DeployerKey + } for _, transfer := range req.Transfers[chainSelector] { - tx, err := linkContract.Transfer(deployment.SimTransactOpts(), transfer.To, transfer.Value) + tx, err := linkContract.Transfer(opts, transfer.To, transfer.Value) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("error packing transfer tx data: %w", err) } @@ -79,24 +83,27 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockReq } allBatches = append(allBatches, batch) } + if req.UseMCMS { + proposal, err := timelock.NewMCMSWithTimelockProposal( + "1", + req.ValidUntil, + []mcms.Signature{}, + req.OverrideRoot, + chainMetadata, + timelockAddresses, + "Value transfer proposal", + allBatches, + timelock.Schedule, + req.MinDelay.String(), + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } - proposal, err := timelock.NewMCMSWithTimelockProposal( - "1", - req.ValidUntil, - []mcms.Signature{}, - req.OverrideRoot, - chainMetadata, - timelockAddresses, - "Value transfer proposal", - allBatches, - timelock.Schedule, - req.MinDelay.String(), - ) - if err != nil { - return deployment.ChangesetOutput{}, err + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*proposal}, + }, nil } - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{*proposal}, - }, nil + return deployment.ChangesetOutput{}, nil } From 96d2a8f76a471f43165e41fac2fbdaa1de4dd036 Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 10 Dec 2024 09:29:26 -0600 Subject: [PATCH 19/41] fix: integration tests after merging develop --- .../changeset/deploy_link_token_test.go | 2 +- .../changeset/link_transfer_timelock_test.go | 34 +++++++++---------- .../changeset/mint_link_timelock_test.go | 21 ++++++------ 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/deployment/common/changeset/deploy_link_token_test.go b/deployment/common/changeset/deploy_link_token_test.go index 5eb3d0a1e34..a61743e9bf4 100644 --- a/deployment/common/changeset/deploy_link_token_test.go +++ b/deployment/common/changeset/deploy_link_token_test.go @@ -18,7 +18,7 @@ func TestDeployLinkToken(t *testing.T) { Chains: 1, }) chain1 := e.AllChainSelectors()[0] - e, err := changeset.ApplyChangesets(t, e, nil, nil, []changeset.ChangesetApplication{ + e, err := changeset.ApplyChangesets(t, e, nil, []changeset.ChangesetApplication{ { Changeset: changeset.WrapChangeSet(changeset.DeployLinkToken), Config: []uint64{chain1}, diff --git a/deployment/common/changeset/link_transfer_timelock_test.go b/deployment/common/changeset/link_transfer_timelock_test.go index 3874998585d..82f7cdd6c9a 100644 --- a/deployment/common/changeset/link_transfer_timelock_test.go +++ b/deployment/common/changeset/link_transfer_timelock_test.go @@ -6,13 +6,12 @@ import ( "testing" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" - "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/smartcontractkit/chainlink/deployment/common/types" @@ -24,7 +23,7 @@ import ( func TestLinkTransferTimelock(t *testing.T) { t.Parallel() ctx := context.Background() - lggr := logger.Test(t) + lggr := logger.TestLogger(t) cfg := memory.MemoryEnvironmentConfig{ Nodes: 1, Chains: 2, @@ -42,11 +41,10 @@ func TestLinkTransferTimelock(t *testing.T) { config := changeset.SingleGroupMCMS(t) respTimelock, err := changeset.DeployMCMSWithTimelock(env, map[uint64]types.MCMSWithTimelockConfig{ chainSelector: { - Canceller: config, - Bypasser: config, - Proposer: config, - TimelockExecutors: []common.Address{chain.DeployerKey.From}, - TimelockMinDelay: big.NewInt(0), + Canceller: config, + Bypasser: config, + Proposer: config, + TimelockMinDelay: big.NewInt(0), }, }) require.NoError(t, env.ExistingAddresses.Merge(respTimelock.AddressBook)) @@ -54,11 +52,11 @@ func TestLinkTransferTimelock(t *testing.T) { addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) require.NoError(t, err) - require.Len(t, addrs, 5) + require.Len(t, addrs, 6) - mcmsState, err := changeset.LoadMCMSWithTimelockState(chain, addrs) + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chain, addrs) require.NoError(t, err) - linkState, err := changeset.LoadLinkTokenState(chain, addrs) + linkState, err := changeset.MaybeLoadLinkTokenState(chain, addrs) require.NoError(t, err) linkAddress := linkState.LinkToken.Address() timelockAddress := mcmsState.Timelock.Address() @@ -78,10 +76,11 @@ func TestLinkTransferTimelock(t *testing.T) { _, err = deployment.ConfirmIfNoError(chain, tx, err) require.NoError(t, err) - timelockContract, err := gethwrappers.NewRBACTimelock(timelockAddress, chain.Client) - require.NoError(t, err) - timelocks := map[uint64]*gethwrappers.RBACTimelock{ - chainSelector: timelockContract, + timelocks := map[uint64]*changeset.TimelockExecutionContracts{ + chainSelector: { + Timelock: mcmsState.Timelock, + CallProxy: mcmsState.CallProxy, + }, } // Apply the changeset @@ -105,6 +104,7 @@ func TestLinkTransferTimelock(t *testing.T) { StartingOpCount: map[uint64]uint64{ chainSelector: 0, }, + UseMCMS: true, }, }, }) diff --git a/deployment/common/changeset/mint_link_timelock_test.go b/deployment/common/changeset/mint_link_timelock_test.go index 7a4e71ef7ed..c52cffae7b1 100644 --- a/deployment/common/changeset/mint_link_timelock_test.go +++ b/deployment/common/changeset/mint_link_timelock_test.go @@ -6,11 +6,11 @@ import ( "testing" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/deployment/environment/memory" @@ -21,7 +21,7 @@ import ( func TestMintLinkTimelock(t *testing.T) { t.Parallel() ctx := context.Background() - lggr := logger.Test(t) + lggr := logger.TestLogger(t) cfg := memory.MemoryEnvironmentConfig{ Nodes: 1, Chains: 2, @@ -40,11 +40,10 @@ func TestMintLinkTimelock(t *testing.T) { config := changeset.SingleGroupMCMS(t) respTimelock, err := changeset.DeployMCMSWithTimelock(env, map[uint64]types.MCMSWithTimelockConfig{ chainSelector: { - Canceller: config, - Bypasser: config, - Proposer: config, - TimelockExecutors: []common.Address{chain.DeployerKey.From}, - TimelockMinDelay: big.NewInt(0), + Canceller: config, + Bypasser: config, + Proposer: config, + TimelockMinDelay: big.NewInt(0), }, }) require.NoError(t, env.ExistingAddresses.Merge(respTimelock.AddressBook)) @@ -52,11 +51,11 @@ func TestMintLinkTimelock(t *testing.T) { addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) require.NoError(t, err) - require.Len(t, addrs, 5) + require.Len(t, addrs, 6) - mcmsState, err := changeset.LoadMCMSWithTimelockState(chain, addrs) + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chain, addrs) require.NoError(t, err) - linkState, err := changeset.LoadLinkTokenState(chain, addrs) + linkState, err := changeset.MaybeLoadLinkTokenState(chain, addrs) require.NoError(t, err) linkAddress := linkState.LinkToken.Address() timelockAddress := mcmsState.Timelock.Address() From c3e528c8ad744a1b9f789ee73320d4a9e9a3d805 Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 10 Dec 2024 10:11:35 -0600 Subject: [PATCH 20/41] fix: use contracts from states and code improvements --- .../changeset/link_transfer_timelock.go | 49 ++++++++++++++++--- .../changeset/link_transfer_timelock_test.go | 42 ++++++++-------- 2 files changed, 62 insertions(+), 29 deletions(-) diff --git a/deployment/common/changeset/link_transfer_timelock.go b/deployment/common/changeset/link_transfer_timelock.go index bd56694921c..df268b61a1c 100644 --- a/deployment/common/changeset/link_transfer_timelock.go +++ b/deployment/common/changeset/link_transfer_timelock.go @@ -11,7 +11,7 @@ import ( "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token" + "github.com/smartcontractkit/chainlink/deployment/common/types" ) type LinkTransfer struct { @@ -29,8 +29,47 @@ type LinkTransferTimelockRequest struct { var _ deployment.ChangeSet[*LinkTransferTimelockRequest] = LinkTransferTimelock +// Validate checks that the LinkTransferTimelockRequest is valid. +func (cfg LinkTransferTimelockRequest) Validate() error { + // Check that Transfers map has at least one key + if len(cfg.Transfers) == 0 { + return fmt.Errorf("transfers map must have at least one key") + } + + // Check that each key in Transfers has at least one LinkTransfer + for key, transfers := range cfg.Transfers { + if len(transfers) == 0 { + return fmt.Errorf("transfers for key %d must have at least one LinkTransfer", key) + } + } + + // Check that StartingOpCount map has at least one key + if len(cfg.StartingOpCount) == 0 { + return fmt.Errorf("startingOpCount map must have at least one key") + } + + // Check that Transfers and StartingOpCount have the same keys + for key := range cfg.Transfers { + if _, exists := cfg.StartingOpCount[key]; !exists { + return fmt.Errorf("startingOpCount map is missing key %d from transfers map", key) + } + } + + for key := range cfg.StartingOpCount { + if _, exists := cfg.Transfers[key]; !exists { + return fmt.Errorf("transfers map is missing key %d from startingOpCount map", key) + } + } + + return nil +} + // LinkTransferTimelock takes the given link transfers and executes them or creates an MCMS proposal for them. func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockRequest) (deployment.ChangesetOutput, error) { + err := req.Validate() + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid LinkTransferTimelockRequest: %w", err) + } chainMetadata := map[mcms.ChainIdentifier]mcms.ChainMetadata{} timelockAddresses := map[mcms.ChainIdentifier]common.Address{} allBatches := []timelock.BatchChainOperation{} @@ -50,10 +89,6 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockReq mcmAddress := mcmsState.ProposerMcm.Address() timelockAddress := mcmsState.Timelock.Address() - linkContract, err := link_token.NewLinkToken(linkAddress, chain.Client) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to get link contract: %w", err) - } chainMetadata[chainID] = mcms.ChainMetadata{ MCMAddress: mcmAddress, StartingOpCount: req.StartingOpCount[chainSelector], @@ -68,7 +103,7 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockReq opts = chain.DeployerKey } for _, transfer := range req.Transfers[chainSelector] { - tx, err := linkContract.Transfer(opts, transfer.To, transfer.Value) + tx, err := linkState.LinkToken.Transfer(opts, transfer.To, transfer.Value) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("error packing transfer tx data: %w", err) } @@ -76,7 +111,7 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockReq To: linkAddress, Data: tx.Data(), Value: big.NewInt(0), - ContractType: "LinkToken", + ContractType: string(types.LinkToken), } batch.Batch = append(batch.Batch, op) diff --git a/deployment/common/changeset/link_transfer_timelock_test.go b/deployment/common/changeset/link_transfer_timelock_test.go index 82f7cdd6c9a..c47abac1ff3 100644 --- a/deployment/common/changeset/link_transfer_timelock_test.go +++ b/deployment/common/changeset/link_transfer_timelock_test.go @@ -16,7 +16,6 @@ import ( "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/deployment/environment/memory" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token" ) // TestLinkTransferTimelock tests the LinkTransferTimelock changeset by @@ -31,23 +30,26 @@ func TestLinkTransferTimelock(t *testing.T) { env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) chainSelector := env.AllChainSelectors()[0] chain := env.Chains[chainSelector] - // Deploy Value Token - resp, err := changeset.DeployLinkToken(env, []uint64{chainSelector}) - require.NoError(t, err) - require.NotNil(t, resp) - require.NoError(t, env.ExistingAddresses.Merge(resp.AddressBook)) + config := changeset.SingleGroupMCMS(t) // Deploy MCMS and Timelock - config := changeset.SingleGroupMCMS(t) - respTimelock, err := changeset.DeployMCMSWithTimelock(env, map[uint64]types.MCMSWithTimelockConfig{ - chainSelector: { - Canceller: config, - Bypasser: config, - Proposer: config, - TimelockMinDelay: big.NewInt(0), + env, err := changeset.ApplyChangesets(t, env, nil, []changeset.ChangesetApplication{ + { + Changeset: changeset.WrapChangeSet(changeset.DeployLinkToken), + Config: []uint64{chainSelector}, + }, + { + Changeset: changeset.WrapChangeSet(changeset.DeployMCMSWithTimelock), + Config: map[uint64]types.MCMSWithTimelockConfig{ + chainSelector: { + Canceller: config, + Bypasser: config, + Proposer: config, + TimelockMinDelay: big.NewInt(0), + }, + }, }, }) - require.NoError(t, env.ExistingAddresses.Merge(respTimelock.AddressBook)) require.NoError(t, err) addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) @@ -58,20 +60,16 @@ func TestLinkTransferTimelock(t *testing.T) { require.NoError(t, err) linkState, err := changeset.MaybeLoadLinkTokenState(chain, addrs) require.NoError(t, err) - linkAddress := linkState.LinkToken.Address() timelockAddress := mcmsState.Timelock.Address() - linkContract, err := link_token.NewLinkToken(linkAddress, chain.Client) - require.NoError(t, err) - // Mint some funds // grant minter permissions - tx, err := linkContract.GrantMintRole(chain.DeployerKey, chain.DeployerKey.From) + 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 = linkContract.Mint(chain.DeployerKey, timelockAddress, big.NewInt(750)) + 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) @@ -111,13 +109,13 @@ func TestLinkTransferTimelock(t *testing.T) { require.NoError(t, err) // Check new balances - endBalance, err := linkContract.BalanceOf(&bind.CallOpts{Context: ctx}, chain.DeployerKey.From) + 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 = linkContract.BalanceOf(&bind.CallOpts{Context: ctx}, timelockAddress) + endBalance, err = linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, timelockAddress) require.NoError(t, err) expectedBalance = big.NewInt(250) require.Equal(t, expectedBalance, endBalance) From 127aa4dcbca21bacee7b622083c3e8133ded9731 Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 10 Dec 2024 10:24:54 -0600 Subject: [PATCH 21/41] fix: cs deploy chain args --- deployment/ccip/changeset/cs_deploy_chain_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/ccip/changeset/cs_deploy_chain_test.go b/deployment/ccip/changeset/cs_deploy_chain_test.go index 3dd1d6c6103..646575dcaa4 100644 --- a/deployment/ccip/changeset/cs_deploy_chain_test.go +++ b/deployment/ccip/changeset/cs_deploy_chain_test.go @@ -37,7 +37,7 @@ func TestDeployChainContractsChangeset(t *testing.T) { TimelockMinDelay: big.NewInt(0), } } - e, err = commonchangeset.ApplyChangesets(t, e, nil, nil, []commonchangeset.ChangesetApplication{ + e, err = commonchangeset.ApplyChangesets(t, e, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(DeployHomeChain), Config: DeployHomeChainConfig{ From c8983223f7c56f9dab6c0a19c5b63a29006504b2 Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 10 Dec 2024 10:30:46 -0600 Subject: [PATCH 22/41] fix: params ccip boosting --- integration-tests/smoke/ccip/ccip_fee_boosting_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go index af0ac561630..26f35a6243e 100644 --- a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go +++ b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/smartcontractkit/chainlink-common/pkg/config" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -102,7 +103,7 @@ func Test_CCIPFeeBoosting(t *testing.T) { TestRouter: false, } - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, nil, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.AddLanes), Config: changeset.AddLanesConfig{LaneConfigs: []changeset.LaneConfig{laneCfg}}, From ba6b57daeb9509733ba0e4adc4ff4229b7c1ac82 Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 10 Dec 2024 12:46:53 -0600 Subject: [PATCH 23/41] fix: bundle mcms config into single struct --- .../changeset/link_transfer_timelock.go | 43 +++++++++++-------- .../changeset/link_transfer_timelock_test.go | 16 ++++--- .../common/changeset/mint_link_timelock.go | 6 +-- .../changeset/mint_link_timelock_test.go | 2 +- 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/deployment/common/changeset/link_transfer_timelock.go b/deployment/common/changeset/link_transfer_timelock.go index df268b61a1c..16875490be3 100644 --- a/deployment/common/changeset/link_transfer_timelock.go +++ b/deployment/common/changeset/link_transfer_timelock.go @@ -18,19 +18,22 @@ type LinkTransfer struct { To common.Address Value *big.Int } -type LinkTransferTimelockRequest struct { - Transfers map[uint64][]LinkTransfer +type LinkTransferTimelockConfig struct { + Transfers map[uint64][]LinkTransfer + UseMCMS bool + McmsConfig *MCMSConfig +} +type MCMSConfig struct { ValidUntil uint32 // unix time until the proposal will be valid MinDelay time.Duration // delay for timelock worker to execute the transfers. OverrideRoot bool StartingOpCount map[uint64]uint64 - UseMCMS bool } -var _ deployment.ChangeSet[*LinkTransferTimelockRequest] = LinkTransferTimelock +var _ deployment.ChangeSet[*LinkTransferTimelockConfig] = LinkTransferTimelock -// Validate checks that the LinkTransferTimelockRequest is valid. -func (cfg LinkTransferTimelockRequest) Validate() error { +// Validate checks that the LinkTransferTimelockConfig is valid. +func (cfg LinkTransferTimelockConfig) Validate() error { // Check that Transfers map has at least one key if len(cfg.Transfers) == 0 { return fmt.Errorf("transfers map must have at least one key") @@ -42,20 +45,24 @@ func (cfg LinkTransferTimelockRequest) Validate() error { return fmt.Errorf("transfers for key %d must have at least one LinkTransfer", key) } } - + if !cfg.UseMCMS { + return nil + } + // Mcms specific configs + if cfg.McmsConfig == nil { + return fmt.Errorf("mcmsConfig must be set when UseMCMS is true") + } // Check that StartingOpCount map has at least one key - if len(cfg.StartingOpCount) == 0 { + if len(cfg.McmsConfig.StartingOpCount) == 0 { return fmt.Errorf("startingOpCount map must have at least one key") } - // Check that Transfers and StartingOpCount have the same keys for key := range cfg.Transfers { - if _, exists := cfg.StartingOpCount[key]; !exists { + if _, exists := cfg.McmsConfig.StartingOpCount[key]; !exists { return fmt.Errorf("startingOpCount map is missing key %d from transfers map", key) } } - - for key := range cfg.StartingOpCount { + for key := range cfg.McmsConfig.StartingOpCount { if _, exists := cfg.Transfers[key]; !exists { return fmt.Errorf("transfers map is missing key %d from startingOpCount map", key) } @@ -65,10 +72,10 @@ func (cfg LinkTransferTimelockRequest) Validate() error { } // LinkTransferTimelock takes the given link transfers and executes them or creates an MCMS proposal for them. -func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockRequest) (deployment.ChangesetOutput, error) { +func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockConfig) (deployment.ChangesetOutput, error) { err := req.Validate() if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("invalid LinkTransferTimelockRequest: %w", err) + return deployment.ChangesetOutput{}, fmt.Errorf("invalid LinkTransferTimelockConfig: %w", err) } chainMetadata := map[mcms.ChainIdentifier]mcms.ChainMetadata{} timelockAddresses := map[mcms.ChainIdentifier]common.Address{} @@ -91,7 +98,7 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockReq chainMetadata[chainID] = mcms.ChainMetadata{ MCMAddress: mcmAddress, - StartingOpCount: req.StartingOpCount[chainSelector], + StartingOpCount: req.McmsConfig.StartingOpCount[chainSelector], } timelockAddresses[chainID] = timelockAddress batch := timelock.BatchChainOperation{ @@ -121,15 +128,15 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockReq if req.UseMCMS { proposal, err := timelock.NewMCMSWithTimelockProposal( "1", - req.ValidUntil, + req.McmsConfig.ValidUntil, []mcms.Signature{}, - req.OverrideRoot, + req.McmsConfig.OverrideRoot, chainMetadata, timelockAddresses, "Value transfer proposal", allBatches, timelock.Schedule, - req.MinDelay.String(), + req.McmsConfig.MinDelay.String(), ) if err != nil { return deployment.ChangesetOutput{}, err diff --git a/deployment/common/changeset/link_transfer_timelock_test.go b/deployment/common/changeset/link_transfer_timelock_test.go index c47abac1ff3..70da0130b38 100644 --- a/deployment/common/changeset/link_transfer_timelock_test.go +++ b/deployment/common/changeset/link_transfer_timelock_test.go @@ -87,7 +87,7 @@ func TestLinkTransferTimelock(t *testing.T) { // in practice, signing and executing are separated processes. { Changeset: changeset.WrapChangeSet(changeset.LinkTransferTimelock), - Config: &changeset.LinkTransferTimelockRequest{ + Config: &changeset.LinkTransferTimelockConfig{ Transfers: map[uint64][]changeset.LinkTransfer{ chainSelector: { { @@ -96,13 +96,15 @@ func TestLinkTransferTimelock(t *testing.T) { }, }, }, - ValidUntil: 4131638958, - MinDelay: 0, - OverrideRoot: true, - StartingOpCount: map[uint64]uint64{ - chainSelector: 0, - }, UseMCMS: true, + McmsConfig: &changeset.MCMSConfig{ + ValidUntil: 4131638958, + MinDelay: 0, + OverrideRoot: true, + StartingOpCount: map[uint64]uint64{ + chainSelector: 0, + }, + }, }, }, }) diff --git a/deployment/common/changeset/mint_link_timelock.go b/deployment/common/changeset/mint_link_timelock.go index 5af807499d7..8e9bbeebc30 100644 --- a/deployment/common/changeset/mint_link_timelock.go +++ b/deployment/common/changeset/mint_link_timelock.go @@ -6,15 +6,15 @@ import ( "github.com/smartcontractkit/chainlink/deployment" ) -type MintLinkTimelockRequest struct { +type MintLinkTimelockConfig struct { Amount *big.Int ChainSelector uint64 } -var _ deployment.ChangeSet[*MintLinkTimelockRequest] = MintLinkTimelock +var _ deployment.ChangeSet[*MintLinkTimelockConfig] = MintLinkTimelock // MintLinkTimelock mints LINK to the timelock contract. -func MintLinkTimelock(e deployment.Environment, req *MintLinkTimelockRequest) (deployment.ChangesetOutput, error) { +func MintLinkTimelock(e deployment.Environment, req *MintLinkTimelockConfig) (deployment.ChangesetOutput, error) { chain := e.Chains[req.ChainSelector] addresses, err := e.ExistingAddresses.AddressesForChain(req.ChainSelector) diff --git a/deployment/common/changeset/mint_link_timelock_test.go b/deployment/common/changeset/mint_link_timelock_test.go index c52cffae7b1..ad68a79e2f7 100644 --- a/deployment/common/changeset/mint_link_timelock_test.go +++ b/deployment/common/changeset/mint_link_timelock_test.go @@ -64,7 +64,7 @@ func TestMintLinkTimelock(t *testing.T) { require.NoError(t, err) // Mint some funds - _, err = changeset.MintLinkTimelock(env, &changeset.MintLinkTimelockRequest{ + _, err = changeset.MintLinkTimelock(env, &changeset.MintLinkTimelockConfig{ ChainSelector: chainSelector, Amount: big.NewInt(7568), }) From e6ac21a94c96c2457410ef6818ddeaa16e196414 Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 10 Dec 2024 13:13:20 -0600 Subject: [PATCH 24/41] fix: add more validations for config --- .../changeset/link_transfer_timelock.go | 36 +++++++++++++++++-- deployment/common/changeset/test_helpers.go | 1 + 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/deployment/common/changeset/link_transfer_timelock.go b/deployment/common/changeset/link_transfer_timelock.go index 16875490be3..b6834f1f71a 100644 --- a/deployment/common/changeset/link_transfer_timelock.go +++ b/deployment/common/changeset/link_transfer_timelock.go @@ -5,7 +5,9 @@ import ( "math/big" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + 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" @@ -34,16 +36,32 @@ var _ deployment.ChangeSet[*LinkTransferTimelockConfig] = LinkTransferTimelock // Validate checks that the LinkTransferTimelockConfig is valid. func (cfg LinkTransferTimelockConfig) Validate() error { + // Upper bound for min delay (7 days) + if cfg.McmsConfig.MinDelay > 24*7*time.Hour { + return fmt.Errorf("minDelay must be less than 7 days") + } + // Check that Transfers map has at least one key if len(cfg.Transfers) == 0 { return fmt.Errorf("transfers map must have at least one key") } - // Check that each key in Transfers has at least one LinkTransfer + // Check transfers config values. for key, transfers := range cfg.Transfers { if len(transfers) == 0 { return fmt.Errorf("transfers for key %d must have at least one LinkTransfer", key) } + for _, transfer := range transfers { + if transfer.To == (common.Address{}) { + return fmt.Errorf("'to' address for transfers must be set") + } + if transfer.Value == nil { + return fmt.Errorf("value for transfers must be set") + } + if transfer.Value == big.NewInt(0) { + return fmt.Errorf("value for transfers must be non-zero") + } + } } if !cfg.UseMCMS { return nil @@ -58,6 +76,10 @@ func (cfg LinkTransferTimelockConfig) Validate() error { } // Check that Transfers and StartingOpCount have the same keys for key := range cfg.Transfers { + _, err := chain_selectors.GetSelectorFamily(key) + if err != nil { + return fmt.Errorf("invalid chain selector: %w", err) + } if _, exists := cfg.McmsConfig.StartingOpCount[key]; !exists { return fmt.Errorf("startingOpCount map is missing key %d from transfers map", key) } @@ -74,6 +96,7 @@ func (cfg LinkTransferTimelockConfig) Validate() error { // LinkTransferTimelock takes the given link transfers and executes them or creates an MCMS proposal for them. func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockConfig) (deployment.ChangesetOutput, error) { err := req.Validate() + ctx := e.GetContext() if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("invalid LinkTransferTimelockConfig: %w", err) } @@ -109,6 +132,7 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockCon if !req.UseMCMS { opts = chain.DeployerKey } + totalAmount := big.NewInt(0) for _, transfer := range req.Transfers[chainSelector] { tx, err := linkState.LinkToken.Transfer(opts, transfer.To, transfer.Value) if err != nil { @@ -121,7 +145,15 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockCon ContractType: string(types.LinkToken), } batch.Batch = append(batch.Batch, op) - + totalAmount.Add(totalAmount, transfer.Value) + } + // check that from address has enough funds for the transfers + balance, err := linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, timelockAddress) + if err != nil { + return deployment.ChangesetOutput{}, err + } + if balance.Cmp(totalAmount) < 0 { + return deployment.ChangesetOutput{}, fmt.Errorf("timelock address does not have enough funds for transfers for chainID %d", chainSelector) } allBatches = append(allBatches, batch) } diff --git a/deployment/common/changeset/test_helpers.go b/deployment/common/changeset/test_helpers.go index 8fce5ea79f2..4e76f2e7568 100644 --- a/deployment/common/changeset/test_helpers.go +++ b/deployment/common/changeset/test_helpers.go @@ -91,6 +91,7 @@ func ApplyChangesets(t *testing.T, e deployment.Environment, timelockContractsPe NodeIDs: e.NodeIDs, Offchain: e.Offchain, OCRSecrets: e.OCRSecrets, + GetContext: e.GetContext, } } return currentEnv, nil From 001b002962bdc1699dd53f77919781a94b71ef15 Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 10 Dec 2024 13:31:15 -0600 Subject: [PATCH 25/41] fix: remove startingOpCount and use proposal utils to derive it --- .../changeset/link_transfer_timelock.go | 60 +++++++++---------- .../changeset/link_transfer_timelock_test.go | 3 - deployment/common/proposalutils/propose.go | 4 +- 3 files changed, 29 insertions(+), 38 deletions(-) diff --git a/deployment/common/changeset/link_transfer_timelock.go b/deployment/common/changeset/link_transfer_timelock.go index b6834f1f71a..7bf576e3285 100644 --- a/deployment/common/changeset/link_transfer_timelock.go +++ b/deployment/common/changeset/link_transfer_timelock.go @@ -7,12 +7,14 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + 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" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/deployment/common/types" ) @@ -26,20 +28,15 @@ type LinkTransferTimelockConfig struct { McmsConfig *MCMSConfig } type MCMSConfig struct { - ValidUntil uint32 // unix time until the proposal will be valid - MinDelay time.Duration // delay for timelock worker to execute the transfers. - OverrideRoot bool - StartingOpCount map[uint64]uint64 + ValidUntil uint32 // unix time until the proposal will be valid + MinDelay time.Duration // delay for timelock worker to execute the transfers. + OverrideRoot bool } var _ deployment.ChangeSet[*LinkTransferTimelockConfig] = LinkTransferTimelock // Validate checks that the LinkTransferTimelockConfig is valid. func (cfg LinkTransferTimelockConfig) Validate() error { - // Upper bound for min delay (7 days) - if cfg.McmsConfig.MinDelay > 24*7*time.Hour { - return fmt.Errorf("minDelay must be less than 7 days") - } // Check that Transfers map has at least one key if len(cfg.Transfers) == 0 { @@ -63,33 +60,24 @@ func (cfg LinkTransferTimelockConfig) Validate() error { } } } - if !cfg.UseMCMS { - return nil - } - // Mcms specific configs - if cfg.McmsConfig == nil { - return fmt.Errorf("mcmsConfig must be set when UseMCMS is true") - } - // Check that StartingOpCount map has at least one key - if len(cfg.McmsConfig.StartingOpCount) == 0 { - return fmt.Errorf("startingOpCount map must have at least one key") - } // Check that Transfers and StartingOpCount have the same keys for key := range cfg.Transfers { _, err := chain_selectors.GetSelectorFamily(key) if err != nil { return fmt.Errorf("invalid chain selector: %w", err) } - if _, exists := cfg.McmsConfig.StartingOpCount[key]; !exists { - return fmt.Errorf("startingOpCount map is missing key %d from transfers map", key) - } } - for key := range cfg.McmsConfig.StartingOpCount { - if _, exists := cfg.Transfers[key]; !exists { - return fmt.Errorf("transfers map is missing key %d from startingOpCount map", key) - } + if !cfg.UseMCMS { + return nil + } + // Mcms specific configs + if cfg.McmsConfig == nil { + return fmt.Errorf("mcmsConfig must be set when UseMCMS is true") + } + // Upper bound for min delay (7 days) + if cfg.McmsConfig.MinDelay > 24*7*time.Hour { + return fmt.Errorf("minDelay must be less than 7 days") } - return nil } @@ -100,7 +88,12 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockCon if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("invalid LinkTransferTimelockConfig: %w", err) } - chainMetadata := map[mcms.ChainIdentifier]mcms.ChainMetadata{} + chainSelectors := []uint64{} + for chainSelector := range req.Transfers { + chainSelectors = append(chainSelectors, chainSelector) + } + mcmsPerChain := map[uint64]*owner_helpers.ManyChainMultiSig{} + timelockAddresses := map[mcms.ChainIdentifier]common.Address{} allBatches := []timelock.BatchChainOperation{} for chainSelector := range req.Transfers { @@ -116,13 +109,10 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockCon if err != nil { return deployment.ChangesetOutput{}, err } - mcmAddress := mcmsState.ProposerMcm.Address() timelockAddress := mcmsState.Timelock.Address() - chainMetadata[chainID] = mcms.ChainMetadata{ - MCMAddress: mcmAddress, - StartingOpCount: req.McmsConfig.StartingOpCount[chainSelector], - } + mcmsPerChain[uint64(chainID)] = mcmsState.ProposerMcm + timelockAddresses[chainID] = timelockAddress batch := timelock.BatchChainOperation{ ChainIdentifier: chainID, @@ -157,6 +147,10 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockCon } allBatches = append(allBatches, batch) } + chainMetadata, err := proposalutils.BuildProposalMetadata(chainSelectors, mcmsPerChain) + if err != nil { + return deployment.ChangesetOutput{}, err + } if req.UseMCMS { proposal, err := timelock.NewMCMSWithTimelockProposal( "1", diff --git a/deployment/common/changeset/link_transfer_timelock_test.go b/deployment/common/changeset/link_transfer_timelock_test.go index 70da0130b38..d3620e8369b 100644 --- a/deployment/common/changeset/link_transfer_timelock_test.go +++ b/deployment/common/changeset/link_transfer_timelock_test.go @@ -101,9 +101,6 @@ func TestLinkTransferTimelock(t *testing.T) { ValidUntil: 4131638958, MinDelay: 0, OverrideRoot: true, - StartingOpCount: map[uint64]uint64{ - chainSelector: 0, - }, }, }, }, diff --git a/deployment/common/proposalutils/propose.go b/deployment/common/proposalutils/propose.go index f525c0b6643..8c1a4eebb93 100644 --- a/deployment/common/proposalutils/propose.go +++ b/deployment/common/proposalutils/propose.go @@ -11,7 +11,7 @@ import ( "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" ) -func buildProposalMetadata( +func BuildProposalMetadata( chainSelectors []uint64, proposerMcmsesPerChain map[uint64]*gethwrappers.ManyChainMultiSig, ) (map[mcms.ChainIdentifier]mcms.ChainMetadata, error) { @@ -52,7 +52,7 @@ func BuildProposalFromBatches( chains.Add(uint64(op.ChainIdentifier)) } - mcmsMd, err := buildProposalMetadata(chains.ToSlice(), proposerMcmsesPerChain) + mcmsMd, err := BuildProposalMetadata(chains.ToSlice(), proposerMcmsesPerChain) if err != nil { return nil, err } From 6641f4f9112ee08257fa68e04bcc8d751cc649d8 Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 10 Dec 2024 18:00:21 -0600 Subject: [PATCH 26/41] fix: adjust variable names, remove boolean for mcms config, add constants move balance validation to Validate() function --- .../link_transfer.go} | 107 +++++++++--------- .../link_transfer_test.go} | 17 +-- .../{ => example}/mint_link_timelock.go | 7 +- .../{ => example}/mint_link_timelock_test.go | 5 +- 4 files changed, 72 insertions(+), 64 deletions(-) rename deployment/common/changeset/{link_transfer_timelock.go => example/link_transfer.go} (65%) rename deployment/common/changeset/{link_transfer_timelock_test.go => example/link_transfer_test.go} (87%) rename deployment/common/changeset/{ => example}/mint_link_timelock.go (84%) rename deployment/common/changeset/{ => example}/mint_link_timelock_test.go (93%) diff --git a/deployment/common/changeset/link_transfer_timelock.go b/deployment/common/changeset/example/link_transfer.go similarity index 65% rename from deployment/common/changeset/link_transfer_timelock.go rename to deployment/common/changeset/example/link_transfer.go index 7bf576e3285..fab592f6c9d 100644 --- a/deployment/common/changeset/link_transfer_timelock.go +++ b/deployment/common/changeset/example/link_transfer.go @@ -1,4 +1,4 @@ -package changeset +package example import ( "fmt" @@ -14,39 +14,55 @@ import ( "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/deployment/common/types" ) -type LinkTransfer struct { +const MaxTimelockDelay = 24 * 7 * time.Hour + +type TransferConfig struct { To common.Address Value *big.Int } -type LinkTransferTimelockConfig struct { - Transfers map[uint64][]LinkTransfer - UseMCMS bool - McmsConfig *MCMSConfig -} + type MCMSConfig struct { ValidUntil uint32 // unix time until the proposal will be valid MinDelay time.Duration // delay for timelock worker to execute the transfers. OverrideRoot bool } -var _ deployment.ChangeSet[*LinkTransferTimelockConfig] = LinkTransferTimelock +type LinkTransferConfig struct { + Transfers map[uint64][]TransferConfig + From common.Address + McmsConfig *MCMSConfig +} -// Validate checks that the LinkTransferTimelockConfig is valid. -func (cfg LinkTransferTimelockConfig) Validate() error { +var _ deployment.ChangeSet[*LinkTransferConfig] = LinkTransfer - // Check that Transfers map has at least one key +// Validate checks that the LinkTransferConfig is valid. +func (cfg LinkTransferConfig) Validate(e deployment.Environment) error { + ctx := e.GetContext() + // Check that Transfers map has at least one chainSel if len(cfg.Transfers) == 0 { - return fmt.Errorf("transfers map must have at least one key") + return fmt.Errorf("transfers map must have at least one chainSel") } // Check transfers config values. - for key, transfers := range cfg.Transfers { + for chainSel, transfers := range cfg.Transfers { + _, err := chain_selectors.GetSelectorFamily(chainSel) + if err != nil { + return fmt.Errorf("invalid chain selector: %w", err) + } + chain := e.Chains[chainSel] + addrs, err := e.ExistingAddresses.AddressesForChain(chainSel) if len(transfers) == 0 { - return fmt.Errorf("transfers for key %d must have at least one LinkTransfer", key) + return fmt.Errorf("transfers for chainSel %d must have at least one LinkTransfer", chainSel) + } + totalAmount := big.NewInt(0) + linkState, err := changeset.MaybeLoadLinkTokenState(chain, addrs) + if err != nil { + return fmt.Errorf("error loading link token state during validation: %w", err) } for _, transfer := range transfers { if transfer.To == (common.Address{}) { @@ -59,53 +75,49 @@ func (cfg LinkTransferTimelockConfig) Validate() error { return fmt.Errorf("value for transfers must be non-zero") } } - } - // Check that Transfers and StartingOpCount have the same keys - for key := range cfg.Transfers { - _, err := chain_selectors.GetSelectorFamily(key) - if err != nil { - return fmt.Errorf("invalid chain selector: %w", err) + // check that from address has enough funds for the transfers + balance, err := linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, cfg.From) + if balance.Cmp(totalAmount) < 0 { + return fmt.Errorf("timelock address does not have enough funds for transfers for chain selector %d", chainSel) } } - if !cfg.UseMCMS { - return nil - } - // Mcms specific configs + if cfg.McmsConfig == nil { - return fmt.Errorf("mcmsConfig must be set when UseMCMS is true") + return nil } + // Upper bound for min delay (7 days) - if cfg.McmsConfig.MinDelay > 24*7*time.Hour { + if cfg.McmsConfig.MinDelay > MaxTimelockDelay { return fmt.Errorf("minDelay must be less than 7 days") } return nil } -// LinkTransferTimelock takes the given link transfers and executes them or creates an MCMS proposal for them. -func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockConfig) (deployment.ChangesetOutput, error) { - err := req.Validate() - ctx := e.GetContext() +// LinkTransfer takes the given link transfers and executes them or creates an MCMS proposal for them. +func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment.ChangesetOutput, error) { + + err := cfg.Validate(e) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("invalid LinkTransferTimelockConfig: %w", err) + return deployment.ChangesetOutput{}, fmt.Errorf("invalid LinkTransferConfig: %w", err) } chainSelectors := []uint64{} - for chainSelector := range req.Transfers { + for chainSelector := range cfg.Transfers { chainSelectors = append(chainSelectors, chainSelector) } mcmsPerChain := map[uint64]*owner_helpers.ManyChainMultiSig{} timelockAddresses := map[mcms.ChainIdentifier]common.Address{} allBatches := []timelock.BatchChainOperation{} - for chainSelector := range req.Transfers { + for chainSelector := range cfg.Transfers { chainID := mcms.ChainIdentifier(chainSelector) chain := e.Chains[chainSelector] addrs, err := e.ExistingAddresses.AddressesForChain(chainSelector) - linkState, err := MaybeLoadLinkTokenState(chain, addrs) + linkState, err := changeset.MaybeLoadLinkTokenState(chain, addrs) if err != nil { return deployment.ChangesetOutput{}, err } linkAddress := linkState.LinkToken.Address() - mcmsState, err := MaybeLoadMCMSWithTimelockState(chain, addrs) + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chain, addrs) if err != nil { return deployment.ChangesetOutput{}, err } @@ -118,12 +130,12 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockCon ChainIdentifier: chainID, Batch: []mcms.Operation{}, } - opts := deployment.SimTransactOpts() - if !req.UseMCMS { - opts = chain.DeployerKey + opts := chain.DeployerKey + if cfg.McmsConfig != nil { + opts = deployment.SimTransactOpts() } totalAmount := big.NewInt(0) - for _, transfer := range req.Transfers[chainSelector] { + for _, transfer := range cfg.Transfers[chainSelector] { tx, err := linkState.LinkToken.Transfer(opts, transfer.To, transfer.Value) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("error packing transfer tx data: %w", err) @@ -137,32 +149,25 @@ func LinkTransferTimelock(e deployment.Environment, req *LinkTransferTimelockCon batch.Batch = append(batch.Batch, op) totalAmount.Add(totalAmount, transfer.Value) } - // check that from address has enough funds for the transfers - balance, err := linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, timelockAddress) - if err != nil { - return deployment.ChangesetOutput{}, err - } - if balance.Cmp(totalAmount) < 0 { - return deployment.ChangesetOutput{}, fmt.Errorf("timelock address does not have enough funds for transfers for chainID %d", chainSelector) - } + allBatches = append(allBatches, batch) } chainMetadata, err := proposalutils.BuildProposalMetadata(chainSelectors, mcmsPerChain) if err != nil { return deployment.ChangesetOutput{}, err } - if req.UseMCMS { + if cfg.McmsConfig != nil { proposal, err := timelock.NewMCMSWithTimelockProposal( "1", - req.McmsConfig.ValidUntil, + cfg.McmsConfig.ValidUntil, []mcms.Signature{}, - req.McmsConfig.OverrideRoot, + cfg.McmsConfig.OverrideRoot, chainMetadata, timelockAddresses, "Value transfer proposal", allBatches, timelock.Schedule, - req.McmsConfig.MinDelay.String(), + cfg.McmsConfig.MinDelay.String(), ) if err != nil { return deployment.ChangesetOutput{}, err diff --git a/deployment/common/changeset/link_transfer_timelock_test.go b/deployment/common/changeset/example/link_transfer_test.go similarity index 87% rename from deployment/common/changeset/link_transfer_timelock_test.go rename to deployment/common/changeset/example/link_transfer_test.go index d3620e8369b..1057fba0c9a 100644 --- a/deployment/common/changeset/link_transfer_timelock_test.go +++ b/deployment/common/changeset/example/link_transfer_test.go @@ -1,4 +1,4 @@ -package changeset_test +package example_test import ( "context" @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/smartcontractkit/chainlink/deployment/common/changeset/example" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/stretchr/testify/require" @@ -18,8 +19,8 @@ import ( "github.com/smartcontractkit/chainlink/deployment/environment/memory" ) -// TestLinkTransferTimelock tests the LinkTransferTimelock changeset by -func TestLinkTransferTimelock(t *testing.T) { +// TestLinkTransfer tests the LinkTransfer changeset by sending LINK from a timelock contract to the deployer key. +func TestLinkTransfer(t *testing.T) { t.Parallel() ctx := context.Background() lggr := logger.TestLogger(t) @@ -86,9 +87,10 @@ func TestLinkTransferTimelock(t *testing.T) { // the changeset produces proposals, ApplyChangesets will sign & execute them. // in practice, signing and executing are separated processes. { - Changeset: changeset.WrapChangeSet(changeset.LinkTransferTimelock), - Config: &changeset.LinkTransferTimelockConfig{ - Transfers: map[uint64][]changeset.LinkTransfer{ + Changeset: changeset.WrapChangeSet(example.LinkTransfer), + Config: &example.LinkTransferConfig{ + From: timelockAddress, + Transfers: map[uint64][]example.TransferConfig{ chainSelector: { { To: chain.DeployerKey.From, @@ -96,8 +98,7 @@ func TestLinkTransferTimelock(t *testing.T) { }, }, }, - UseMCMS: true, - McmsConfig: &changeset.MCMSConfig{ + McmsConfig: &example.MCMSConfig{ ValidUntil: 4131638958, MinDelay: 0, OverrideRoot: true, diff --git a/deployment/common/changeset/mint_link_timelock.go b/deployment/common/changeset/example/mint_link_timelock.go similarity index 84% rename from deployment/common/changeset/mint_link_timelock.go rename to deployment/common/changeset/example/mint_link_timelock.go index 8e9bbeebc30..f711adc6658 100644 --- a/deployment/common/changeset/mint_link_timelock.go +++ b/deployment/common/changeset/example/mint_link_timelock.go @@ -1,9 +1,10 @@ -package changeset +package example import ( "math/big" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" ) type MintLinkTimelockConfig struct { @@ -21,11 +22,11 @@ func MintLinkTimelock(e deployment.Environment, req *MintLinkTimelockConfig) (de if err != nil { return deployment.ChangesetOutput{}, err } - linkState, err := MaybeLoadLinkTokenState(chain, addresses) + linkState, err := changeset.MaybeLoadLinkTokenState(chain, addresses) if err != nil { return deployment.ChangesetOutput{}, err } - timelockState, err := MaybeLoadMCMSWithTimelockState(chain, addresses) + timelockState, err := changeset.MaybeLoadMCMSWithTimelockState(chain, addresses) if err != nil { return deployment.ChangesetOutput{}, err } diff --git a/deployment/common/changeset/mint_link_timelock_test.go b/deployment/common/changeset/example/mint_link_timelock_test.go similarity index 93% rename from deployment/common/changeset/mint_link_timelock_test.go rename to deployment/common/changeset/example/mint_link_timelock_test.go index ad68a79e2f7..70a20d6ac53 100644 --- a/deployment/common/changeset/mint_link_timelock_test.go +++ b/deployment/common/changeset/example/mint_link_timelock_test.go @@ -1,4 +1,4 @@ -package changeset_test +package example_test import ( "context" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" + "github.com/smartcontractkit/chainlink/deployment/common/changeset/example" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/deployment/common/changeset" @@ -64,7 +65,7 @@ func TestMintLinkTimelock(t *testing.T) { require.NoError(t, err) // Mint some funds - _, err = changeset.MintLinkTimelock(env, &changeset.MintLinkTimelockConfig{ + _, err = example.MintLinkTimelock(env, &example.MintLinkTimelockConfig{ ChainSelector: chainSelector, Amount: big.NewInt(7568), }) From f889dfc35991573e31c5a56015b3780ff673a9a5 Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 11 Dec 2024 07:43:13 -0600 Subject: [PATCH 27/41] feat: add tests for non mcms case, improve validations, and wait for tx confirmation. --- .../common/changeset/example/link_transfer.go | 78 +++++++++++++--- .../changeset/example/link_transfer_test.go | 92 +++++++++++++++++-- .../changeset/example/mint_link_timelock.go | 22 ++--- .../example/mint_link_timelock_test.go | 5 +- 4 files changed, 165 insertions(+), 32 deletions(-) diff --git a/deployment/common/changeset/example/link_transfer.go b/deployment/common/changeset/example/link_transfer.go index fab592f6c9d..057aa2c07f1 100644 --- a/deployment/common/changeset/example/link_transfer.go +++ b/deployment/common/changeset/example/link_transfer.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "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" @@ -54,7 +55,10 @@ func (cfg LinkTransferConfig) Validate(e deployment.Environment) error { if err != nil { return fmt.Errorf("invalid chain selector: %w", err) } - chain := e.Chains[chainSel] + chain, ok := e.Chains[chainSel] + if !ok { + return fmt.Errorf("chain with selector %d not found", chainSel) + } addrs, err := e.ExistingAddresses.AddressesForChain(chainSel) if len(transfers) == 0 { return fmt.Errorf("transfers for chainSel %d must have at least one LinkTransfer", chainSel) @@ -93,6 +97,57 @@ func (cfg LinkTransferConfig) Validate(e deployment.Environment) error { return nil } +// initStatePerChain initializes the state for each chain selector on the provided config +func initStatePerChain(cfg *LinkTransferConfig, e deployment.Environment) ( + linkStatePerChain map[uint64]*changeset.LinkTokenState, + mcmsStatePerChain map[uint64]*changeset.MCMSWithTimelockState, + err error) { + linkStatePerChain = map[uint64]*changeset.LinkTokenState{} + mcmsStatePerChain = map[uint64]*changeset.MCMSWithTimelockState{} + // Load state for each chain + for chainSelector := range cfg.Transfers { + chain := e.Chains[chainSelector] + addrs, err := e.ExistingAddresses.AddressesForChain(chainSelector) + if err != nil { + return nil, nil, err + } + linkState, err := changeset.MaybeLoadLinkTokenState(chain, addrs) + if err != nil { + return nil, nil, err + } + linkStatePerChain[chainSelector] = linkState + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chain, addrs) + if err != nil { + return nil, nil, err + } + mcmsStatePerChain[chainSelector] = mcmsState + } + return linkStatePerChain, mcmsStatePerChain, nil +} + +// transferOrBuildTx transfers the LINK tokens or builds the tx for the MCMS proposal +func transferOrBuildTx( + e deployment.Environment, + linkState *changeset.LinkTokenState, + transfer TransferConfig, + opts *bind.TransactOpts, + chain deployment.Chain, + mcmsConfig *MCMSConfig) (*ethTypes.Transaction, error) { + tx, err := linkState.LinkToken.Transfer(opts, transfer.To, transfer.Value) + if err != nil { + return nil, fmt.Errorf("error packing transfer tx data: %w", err) + } + // only wait for tx if we are not using MCMS + if mcmsConfig == nil { + if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { + e.Logger.Errorw("Failed to confirm transfer tx", "chain", chain.String(), "err", err) + return nil, err + } + } + return tx, nil + +} + // LinkTransfer takes the given link transfers and executes them or creates an MCMS proposal for them. func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment.ChangesetOutput, error) { @@ -107,20 +162,17 @@ func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment mcmsPerChain := map[uint64]*owner_helpers.ManyChainMultiSig{} timelockAddresses := map[mcms.ChainIdentifier]common.Address{} + // Initialize state for each chain + linkStatePerChain, mcmsStatePerChain, err := initStatePerChain(cfg, e) + allBatches := []timelock.BatchChainOperation{} for chainSelector := range cfg.Transfers { chainID := mcms.ChainIdentifier(chainSelector) chain := e.Chains[chainSelector] - addrs, err := e.ExistingAddresses.AddressesForChain(chainSelector) - linkState, err := changeset.MaybeLoadLinkTokenState(chain, addrs) - if err != nil { - return deployment.ChangesetOutput{}, err - } - linkAddress := linkState.LinkToken.Address() - mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chain, addrs) - if err != nil { - return deployment.ChangesetOutput{}, err - } + linkAddress := linkStatePerChain[chainSelector].LinkToken.Address() + mcmsState := mcmsStatePerChain[chainSelector] + linkState := linkStatePerChain[chainSelector] + timelockAddress := mcmsState.Timelock.Address() mcmsPerChain[uint64(chainID)] = mcmsState.ProposerMcm @@ -136,9 +188,9 @@ func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment } totalAmount := big.NewInt(0) for _, transfer := range cfg.Transfers[chainSelector] { - tx, err := linkState.LinkToken.Transfer(opts, transfer.To, transfer.Value) + tx, err := transferOrBuildTx(e, linkState, transfer, opts, chain, cfg.McmsConfig) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("error packing transfer tx data: %w", err) + return deployment.ChangesetOutput{}, err } op := mcms.Operation{ To: linkAddress, diff --git a/deployment/common/changeset/example/link_transfer_test.go b/deployment/common/changeset/example/link_transfer_test.go index 1057fba0c9a..8c895499486 100644 --- a/deployment/common/changeset/example/link_transfer_test.go +++ b/deployment/common/changeset/example/link_transfer_test.go @@ -19,10 +19,9 @@ import ( "github.com/smartcontractkit/chainlink/deployment/environment/memory" ) -// TestLinkTransfer tests the LinkTransfer changeset by sending LINK from a timelock contract to the deployer key. -func TestLinkTransfer(t *testing.T) { - t.Parallel() - ctx := context.Background() +// setupLinkTransferContracts deploys all required contracts for the link transfer tests and returns the updated env. +func setupLinkTransferTestEnv(t *testing.T) deployment.Environment { + lggr := logger.TestLogger(t) cfg := memory.MemoryEnvironmentConfig{ Nodes: 1, @@ -30,7 +29,6 @@ func TestLinkTransfer(t *testing.T) { } env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) chainSelector := env.AllChainSelectors()[0] - chain := env.Chains[chainSelector] config := changeset.SingleGroupMCMS(t) // Deploy MCMS and Timelock @@ -52,7 +50,18 @@ func TestLinkTransfer(t *testing.T) { }, }) require.NoError(t, err) + return env +} + +// TestLinkTransferMCMS tests the LinkTransfer changeset by sending LINK from a timelock contract +// to the deployer key via mcms proposal. +func TestLinkTransferMCMS(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) @@ -81,7 +90,6 @@ func TestLinkTransfer(t *testing.T) { CallProxy: mcmsState.CallProxy, }, } - // Apply the changeset _, err = changeset.ApplyChangesets(t, env, timelocks, []changeset.ChangesetApplication{ // the changeset produces proposals, ApplyChangesets will sign & execute them. @@ -120,3 +128,75 @@ func TestLinkTransfer(t *testing.T) { expectedBalance = big.NewInt(250) require.Equal(t, expectedBalance, endBalance) } + +// TestLinkTransfer tests the LinkTransfer changeset by sending LINK from a timelock contract to the deployer key. +func TestLinkTransfer(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.MaybeLoadMCMSWithTimelockState(chain, addrs) + require.NoError(t, err) + linkState, err := changeset.MaybeLoadLinkTokenState(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, chain.DeployerKey.From, big.NewInt(750)) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + require.NoError(t, err) + + timelocks := map[uint64]*changeset.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.LinkTransfer), + Config: &example.LinkTransferConfig{ + From: chain.DeployerKey.From, + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: { + { + To: timelockAddress, + Value: big.NewInt(500), + }, + }, + }, + // No MCMSConfig here means we'll execute the txs directly. + }, + }, + }) + 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(250) + 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(500) + require.Equal(t, expectedBalance, endBalance) +} diff --git a/deployment/common/changeset/example/mint_link_timelock.go b/deployment/common/changeset/example/mint_link_timelock.go index f711adc6658..ab2e1d9fa63 100644 --- a/deployment/common/changeset/example/mint_link_timelock.go +++ b/deployment/common/changeset/example/mint_link_timelock.go @@ -3,22 +3,25 @@ package example import ( "math/big" + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/common/changeset" ) -type MintLinkTimelockConfig struct { +type MintLinkConfig struct { Amount *big.Int ChainSelector uint64 + To common.Address } -var _ deployment.ChangeSet[*MintLinkTimelockConfig] = MintLinkTimelock +var _ deployment.ChangeSet[*MintLinkConfig] = MintLink -// MintLinkTimelock mints LINK to the timelock contract. -func MintLinkTimelock(e deployment.Environment, req *MintLinkTimelockConfig) (deployment.ChangesetOutput, error) { +// MintLink mints LINK to the provided contract. +func MintLink(e deployment.Environment, cfg *MintLinkConfig) (deployment.ChangesetOutput, error) { - chain := e.Chains[req.ChainSelector] - addresses, err := e.ExistingAddresses.AddressesForChain(req.ChainSelector) + chain := e.Chains[cfg.ChainSelector] + addresses, err := e.ExistingAddresses.AddressesForChain(cfg.ChainSelector) if err != nil { return deployment.ChangesetOutput{}, err } @@ -26,10 +29,7 @@ func MintLinkTimelock(e deployment.Environment, req *MintLinkTimelockConfig) (de if err != nil { return deployment.ChangesetOutput{}, err } - timelockState, err := changeset.MaybeLoadMCMSWithTimelockState(chain, addresses) - if err != nil { - return deployment.ChangesetOutput{}, err - } + tx, err := linkState.LinkToken.GrantMintRole(chain.DeployerKey, chain.DeployerKey.From) if err != nil { return deployment.ChangesetOutput{}, err @@ -38,7 +38,7 @@ func MintLinkTimelock(e deployment.Environment, req *MintLinkTimelockConfig) (de if err != nil { return deployment.ChangesetOutput{}, err } - tx, err = linkState.LinkToken.Mint(chain.DeployerKey, timelockState.Timelock.Address(), req.Amount) + tx, err = linkState.LinkToken.Mint(chain.DeployerKey, cfg.To, cfg.Amount) if err != nil { return deployment.ChangesetOutput{}, err } diff --git a/deployment/common/changeset/example/mint_link_timelock_test.go b/deployment/common/changeset/example/mint_link_timelock_test.go index 70a20d6ac53..94fe23725eb 100644 --- a/deployment/common/changeset/example/mint_link_timelock_test.go +++ b/deployment/common/changeset/example/mint_link_timelock_test.go @@ -18,7 +18,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token" ) -// TestMintLinkTimelock tests the MintLinkTimelock changeset +// TestMintLinkTimelock tests the MintLink changeset func TestMintLinkTimelock(t *testing.T) { t.Parallel() ctx := context.Background() @@ -65,8 +65,9 @@ func TestMintLinkTimelock(t *testing.T) { require.NoError(t, err) // Mint some funds - _, err = example.MintLinkTimelock(env, &example.MintLinkTimelockConfig{ + _, err = example.MintLink(env, &example.MintLinkConfig{ ChainSelector: chainSelector, + To: timelockAddress, Amount: big.NewInt(7568), }) require.NoError(t, err) From b9479fc3e69e6e0528d01bfdaecb96f99d61dcb3 Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 11 Dec 2024 07:44:47 -0600 Subject: [PATCH 28/41] feat: check valid until is in future --- deployment/common/changeset/example/link_transfer.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deployment/common/changeset/example/link_transfer.go b/deployment/common/changeset/example/link_transfer.go index 057aa2c07f1..cb22b379258 100644 --- a/deployment/common/changeset/example/link_transfer.go +++ b/deployment/common/changeset/example/link_transfer.go @@ -94,6 +94,10 @@ func (cfg LinkTransferConfig) Validate(e deployment.Environment) error { if cfg.McmsConfig.MinDelay > MaxTimelockDelay { return fmt.Errorf("minDelay must be less than 7 days") } + // Check that validUntil is in the future + if time.Unix(int64(cfg.McmsConfig.ValidUntil), 0).Before(time.Now()) { + return fmt.Errorf("validUntil must be in the future") + } return nil } From 03e93827fffe21bbf491f371b84918ae965ffa7e Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 11 Dec 2024 08:51:29 -0600 Subject: [PATCH 29/41] feat: add tests for Validate() and small validation fixes --- .../common/changeset/example/link_transfer.go | 5 +- .../changeset/example/link_transfer_test.go | 167 ++++++++++++++++++ 2 files changed, 170 insertions(+), 2 deletions(-) diff --git a/deployment/common/changeset/example/link_transfer.go b/deployment/common/changeset/example/link_transfer.go index cb22b379258..7505443f0c8 100644 --- a/deployment/common/changeset/example/link_transfer.go +++ b/deployment/common/changeset/example/link_transfer.go @@ -75,14 +75,15 @@ func (cfg LinkTransferConfig) Validate(e deployment.Environment) error { if transfer.Value == nil { return fmt.Errorf("value for transfers must be set") } - if transfer.Value == big.NewInt(0) { + if transfer.Value.Cmp(big.NewInt(0)) == 0 { return fmt.Errorf("value for transfers must be non-zero") } + totalAmount.Add(totalAmount, transfer.Value) } // check that from address has enough funds for the transfers balance, err := linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, cfg.From) if balance.Cmp(totalAmount) < 0 { - return fmt.Errorf("timelock address does not have enough funds for transfers for chain selector %d", chainSel) + return fmt.Errorf("sender does not have enough funds for transfers for chain selector %d, required: %s, available: %s", chainSel, totalAmount.String(), balance.String()) } } diff --git a/deployment/common/changeset/example/link_transfer_test.go b/deployment/common/changeset/example/link_transfer_test.go index 8c895499486..267ca355c72 100644 --- a/deployment/common/changeset/example/link_transfer_test.go +++ b/deployment/common/changeset/example/link_transfer_test.go @@ -4,8 +4,11 @@ import ( "context" "math/big" "testing" + "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + chain_selectors "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink/deployment/common/changeset/example" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -200,3 +203,167 @@ func TestLinkTransfer(t *testing.T) { expectedBalance = big.NewInt(500) require.Equal(t, expectedBalance, endBalance) } + +func TestValidate(t *testing.T) { + 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.MaybeLoadMCMSWithTimelockState(chain, addrs) + require.NoError(t, err) + linkState, err := changeset.MaybeLoadLinkTokenState(chain, addrs) + require.NoError(t, err) + 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, chain.DeployerKey.From, big.NewInt(750)) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(chain, tx, err) + + require.NoError(t, err) + tests := []struct { + name string + cfg example.LinkTransferConfig + errorMsg string + }{ + { + name: "valid config", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: {{To: mcmsState.Timelock.Address(), Value: big.NewInt(100)}}}, + From: chain.DeployerKey.From, + McmsConfig: &example.MCMSConfig{ + ValidUntil: uint32(time.Now().Add(1 * time.Hour).Unix()), + MinDelay: time.Hour, + }, + }, + }, + { + name: "valid non mcms config", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: {{To: mcmsState.Timelock.Address(), Value: big.NewInt(100)}}}, + From: chain.DeployerKey.From, + }, + }, + { + name: "insufficient funds", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: { + {To: chain.DeployerKey.From, Value: big.NewInt(100)}, + {To: chain.DeployerKey.From, Value: big.NewInt(500)}, + {To: chain.DeployerKey.From, Value: big.NewInt(1250)}, + }, + }, + From: mcmsState.Timelock.Address(), + McmsConfig: &example.MCMSConfig{ + ValidUntil: uint32(time.Now().Add(1 * time.Hour).Unix()), + MinDelay: time.Hour, + }, + }, + errorMsg: "sender does not have enough funds for transfers for chain selector 909606746561742123, required: 1850, available: 0", + }, + { + name: "invalid config: empty transfers", + cfg: example.LinkTransferConfig{Transfers: map[uint64][]example.TransferConfig{}}, + errorMsg: "transfers map must have at least one chainSel", + }, + { + name: "invalid chain selector", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + 1: {{To: common.Address{}, Value: big.NewInt(100)}}}, + }, + errorMsg: "invalid chain selector: unknown chain selector 1", + }, + { + name: "chain selector not found", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chain_selectors.ETHEREUM_TESTNET_GOERLI_ARBITRUM_1.Selector: {{To: common.Address{}, Value: big.NewInt(100)}}}, + }, + errorMsg: "chain with selector 6101244977088475029 not found", + }, + { + name: "empty transfer list", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: {}, + }, + }, + errorMsg: "transfers for chainSel 909606746561742123 must have at least one LinkTransfer", + }, + { + name: "empty value", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: { + {To: chain.DeployerKey.From, Value: nil}, + }, + }, + }, + errorMsg: "value for transfers must be set", + }, + { + name: "zero value", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: { + {To: chain.DeployerKey.From, Value: big.NewInt(0)}, + }, + }, + }, + errorMsg: "value for transfers must be non-zero", + }, + { + name: "delay greater than max allowed", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: {{To: mcmsState.Timelock.Address(), Value: big.NewInt(100)}}}, + From: chain.DeployerKey.From, + McmsConfig: &example.MCMSConfig{ + ValidUntil: uint32(time.Now().Add(1 * time.Hour).Unix()), + MinDelay: time.Hour * 24 * 10, + }, + }, + errorMsg: "minDelay must be less than 7 days", + }, + { + name: "invalid config: transfer to address missing", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: {{To: common.Address{}, Value: big.NewInt(100)}}}, + }, + errorMsg: "'to' address for transfers must be set", + }, + { + name: "invalid config: validUntil in the past", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: {{To: mcmsState.Timelock.Address(), Value: big.NewInt(100)}}}, + From: chain.DeployerKey.From, + McmsConfig: &example.MCMSConfig{ + ValidUntil: uint32(time.Now().Add(-1 * time.Hour).Unix()), + MinDelay: time.Hour, + }, + }, + errorMsg: "validUntil must be in the future", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.cfg.Validate(env) + if tt.errorMsg != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.errorMsg) + } else { + require.NoError(t, err) + } + }) + } +} From 3d2dad126d7309dbc9f1beb1971fa0670a5aaadb Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 11 Dec 2024 09:16:01 -0600 Subject: [PATCH 30/41] fix: rename MaybeLoadLinkTokenState to MaybeLoadLinkTokenChainState to abstract loading state per chain --- deployment/ccip/changeset/state.go | 4 +- .../changeset/deploy_link_token_test.go | 2 +- .../common/changeset/example/link_transfer.go | 28 +++++------ .../changeset/example/link_transfer_test.go | 12 ++--- .../changeset/example/mint_link_timelock.go | 2 +- .../example/mint_link_timelock_test.go | 4 +- .../common/changeset/internal/mcms_test.go | 2 +- deployment/common/changeset/state.go | 48 +++++++++++++++++-- .../transfer_to_mcms_with_timelock_test.go | 9 ++-- .../changeset/accept_ownership_test.go | 2 +- deployment/keystone/state.go | 2 +- 11 files changed, 76 insertions(+), 39 deletions(-) diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index 122ce8ec13c..888238247cd 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -292,13 +292,13 @@ func LoadOnchainState(e deployment.Environment) (CCIPOnChainState, error) { // LoadChainState Loads all state for a chain into state func LoadChainState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (CCIPChainState, error) { var state CCIPChainState - mcmsWithTimelock, err := commoncs.MaybeLoadMCMSWithTimelockState(chain, addresses) + mcmsWithTimelock, err := commoncs.MaybeLoadMCMSWithTimelockChainState(chain, addresses) if err != nil { return state, err } state.MCMSWithTimelockState = *mcmsWithTimelock - linkState, err := commoncs.MaybeLoadLinkTokenState(chain, addresses) + linkState, err := commoncs.MaybeLoadLinkTokenChainState(chain, addresses) if err != nil { return state, err } diff --git a/deployment/common/changeset/deploy_link_token_test.go b/deployment/common/changeset/deploy_link_token_test.go index a61743e9bf4..bc472d2a247 100644 --- a/deployment/common/changeset/deploy_link_token_test.go +++ b/deployment/common/changeset/deploy_link_token_test.go @@ -27,7 +27,7 @@ func TestDeployLinkToken(t *testing.T) { require.NoError(t, err) addrs, err := e.ExistingAddresses.AddressesForChain(chain1) require.NoError(t, err) - state, err := changeset.MaybeLoadLinkTokenState(e.Chains[chain1], addrs) + state, err := changeset.MaybeLoadLinkTokenChainState(e.Chains[chain1], addrs) require.NoError(t, err) // View itself already unit tested _, err = state.GenerateLinkView() diff --git a/deployment/common/changeset/example/link_transfer.go b/deployment/common/changeset/example/link_transfer.go index 7505443f0c8..2bc8298e81e 100644 --- a/deployment/common/changeset/example/link_transfer.go +++ b/deployment/common/changeset/example/link_transfer.go @@ -64,7 +64,7 @@ func (cfg LinkTransferConfig) Validate(e deployment.Environment) error { return fmt.Errorf("transfers for chainSel %d must have at least one LinkTransfer", chainSel) } totalAmount := big.NewInt(0) - linkState, err := changeset.MaybeLoadLinkTokenState(chain, addrs) + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addrs) if err != nil { return fmt.Errorf("error loading link token state during validation: %w", err) } @@ -110,22 +110,18 @@ func initStatePerChain(cfg *LinkTransferConfig, e deployment.Environment) ( linkStatePerChain = map[uint64]*changeset.LinkTokenState{} mcmsStatePerChain = map[uint64]*changeset.MCMSWithTimelockState{} // Load state for each chain + chainSelectors := []uint64{} for chainSelector := range cfg.Transfers { - chain := e.Chains[chainSelector] - addrs, err := e.ExistingAddresses.AddressesForChain(chainSelector) - if err != nil { - return nil, nil, err - } - linkState, err := changeset.MaybeLoadLinkTokenState(chain, addrs) - if err != nil { - return nil, nil, err - } - linkStatePerChain[chainSelector] = linkState - mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chain, addrs) - if err != nil { - return nil, nil, err - } - mcmsStatePerChain[chainSelector] = mcmsState + chainSelectors = append(chainSelectors, chainSelector) + } + linkStatePerChain, err = changeset.MaybeLoadLinkTokenState(e, chainSelectors) + if err != nil { + return nil, nil, err + } + mcmsStatePerChain, err = changeset.MaybeLoadMCMSWithTimelockState(e, chainSelectors) + if err != nil { + return nil, nil, err + } return linkStatePerChain, mcmsStatePerChain, nil } diff --git a/deployment/common/changeset/example/link_transfer_test.go b/deployment/common/changeset/example/link_transfer_test.go index 267ca355c72..f70226fea78 100644 --- a/deployment/common/changeset/example/link_transfer_test.go +++ b/deployment/common/changeset/example/link_transfer_test.go @@ -69,9 +69,9 @@ func TestLinkTransferMCMS(t *testing.T) { require.NoError(t, err) require.Len(t, addrs, 6) - mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chain, addrs) + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) require.NoError(t, err) - linkState, err := changeset.MaybeLoadLinkTokenState(chain, addrs) + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addrs) require.NoError(t, err) timelockAddress := mcmsState.Timelock.Address() @@ -144,9 +144,9 @@ func TestLinkTransfer(t *testing.T) { require.NoError(t, err) require.Len(t, addrs, 6) - mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chain, addrs) + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) require.NoError(t, err) - linkState, err := changeset.MaybeLoadLinkTokenState(chain, addrs) + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addrs) require.NoError(t, err) timelockAddress := mcmsState.Timelock.Address() @@ -211,9 +211,9 @@ func TestValidate(t *testing.T) { addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) require.NoError(t, err) require.Len(t, addrs, 6) - mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chain, addrs) + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) require.NoError(t, err) - linkState, err := changeset.MaybeLoadLinkTokenState(chain, addrs) + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addrs) require.NoError(t, err) tx, err := linkState.LinkToken.GrantMintRole(chain.DeployerKey, chain.DeployerKey.From) require.NoError(t, err) diff --git a/deployment/common/changeset/example/mint_link_timelock.go b/deployment/common/changeset/example/mint_link_timelock.go index ab2e1d9fa63..dc722da7f75 100644 --- a/deployment/common/changeset/example/mint_link_timelock.go +++ b/deployment/common/changeset/example/mint_link_timelock.go @@ -25,7 +25,7 @@ func MintLink(e deployment.Environment, cfg *MintLinkConfig) (deployment.Changes if err != nil { return deployment.ChangesetOutput{}, err } - linkState, err := changeset.MaybeLoadLinkTokenState(chain, addresses) + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addresses) if err != nil { return deployment.ChangesetOutput{}, err } diff --git a/deployment/common/changeset/example/mint_link_timelock_test.go b/deployment/common/changeset/example/mint_link_timelock_test.go index 94fe23725eb..bbeca8e24f6 100644 --- a/deployment/common/changeset/example/mint_link_timelock_test.go +++ b/deployment/common/changeset/example/mint_link_timelock_test.go @@ -54,9 +54,9 @@ func TestMintLinkTimelock(t *testing.T) { require.NoError(t, err) require.Len(t, addrs, 6) - mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chain, addrs) + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) require.NoError(t, err) - linkState, err := changeset.MaybeLoadLinkTokenState(chain, addrs) + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addrs) require.NoError(t, err) linkAddress := linkState.LinkToken.Address() timelockAddress := mcmsState.Timelock.Address() diff --git a/deployment/common/changeset/internal/mcms_test.go b/deployment/common/changeset/internal/mcms_test.go index 10fb1d980de..24737ad7632 100644 --- a/deployment/common/changeset/internal/mcms_test.go +++ b/deployment/common/changeset/internal/mcms_test.go @@ -45,7 +45,7 @@ func TestDeployMCMSWithTimelockContracts(t *testing.T) { addresses, err := ab.AddressesForChain(chainsel.TEST_90000001.Selector) require.NoError(t, err) require.Len(t, addresses, 5) - mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chains[chainsel.TEST_90000001.Selector], addresses) + mcmsState, err := changeset.MaybeLoadMCMSWithTimelockChainState(chains[chainsel.TEST_90000001.Selector], addresses) require.NoError(t, err) v, err := mcmsState.GenerateMCMSWithTimelockView() b, err := json.MarshalIndent(v, "", " ") diff --git a/deployment/common/changeset/state.go b/deployment/common/changeset/state.go index a580c13b40b..40f5c7e369f 100644 --- a/deployment/common/changeset/state.go +++ b/deployment/common/changeset/state.go @@ -80,14 +80,35 @@ func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWith }, nil } -// MaybeLoadMCMSWithTimelockState looks for the addresses corresponding to +// MaybeLoadMCMSWithTimelockState loads the MCMSWithTimelockState state for each chain in the given environment. +func MaybeLoadMCMSWithTimelockState(env deployment.Environment, chainSelectors []uint64) (map[uint64]*MCMSWithTimelockState, error) { + result := map[uint64]*MCMSWithTimelockState{} + for _, chainSelector := range chainSelectors { + chain, ok := env.Chains[chainSelector] + if !ok { + return nil, fmt.Errorf("chain %d not found", chainSelector) + } + addressesChain, err := env.ExistingAddresses.AddressesForChain(chainSelector) + if err != nil { + return nil, err + } + state, err := MaybeLoadMCMSWithTimelockChainState(chain, addressesChain) + if err != nil { + return nil, err + } + result[chainSelector] = state + } + return result, nil +} + +// MaybeLoadMCMSWithTimelockChainState looks for the addresses corresponding to // contracts deployed with DeployMCMSWithTimelock and loads them into a // MCMSWithTimelockState struct. If none of the contracts are found, the state struct will be nil. // An error indicates: // - Found but was unable to load a contract // - It only found part of the bundle of contracts // - If found more than one instance of a contract (we expect one bundle in the given addresses) -func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*MCMSWithTimelockState, error) { +func MaybeLoadMCMSWithTimelockChainState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*MCMSWithTimelockState, error) { state := MCMSWithTimelockState{} // We expect one of each contract on the chain. timelock := deployment.NewTypeAndVersion(types.RBACTimelock, deployment.Version1_0_0) @@ -153,7 +174,28 @@ func (s LinkTokenState) GenerateLinkView() (v1_0.LinkTokenView, error) { return v1_0.GenerateLinkTokenView(s.LinkToken) } -func MaybeLoadLinkTokenState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*LinkTokenState, error) { +// MaybeLoadLinkTokenState loads the LinkToken state for each chain in the given environment. +func MaybeLoadLinkTokenState(env deployment.Environment, chainSelectors []uint64) (map[uint64]*LinkTokenState, error) { + result := map[uint64]*LinkTokenState{} + for _, chainSelector := range chainSelectors { + chain, ok := env.Chains[chainSelector] + if !ok { + return nil, fmt.Errorf("chain %d not found", chainSelector) + } + addressesChain, err := env.ExistingAddresses.AddressesForChain(chainSelector) + if err != nil { + return nil, err + } + state, err := MaybeLoadLinkTokenChainState(chain, addressesChain) + if err != nil { + return nil, err + } + result[chainSelector] = state + } + return result, nil +} + +func MaybeLoadLinkTokenChainState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*LinkTokenState, error) { state := LinkTokenState{} linkToken := deployment.NewTypeAndVersion(types.LinkToken, deployment.Version1_0_0) // Perhaps revisit if we have a use case for multiple. diff --git a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go index 6c68924b35e..d64d6af1f7e 100644 --- a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go +++ b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go @@ -1,13 +1,12 @@ package changeset import ( + "math/big" "testing" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - "math/big" - "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -40,9 +39,9 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { require.NoError(t, err) addrs, err := e.ExistingAddresses.AddressesForChain(chain1) require.NoError(t, err) - state, err := MaybeLoadMCMSWithTimelockState(e.Chains[chain1], addrs) + state, err := MaybeLoadMCMSWithTimelockChainState(e.Chains[chain1], addrs) require.NoError(t, err) - link, err := MaybeLoadLinkTokenState(e.Chains[chain1], addrs) + link, err := MaybeLoadLinkTokenChainState(e.Chains[chain1], addrs) require.NoError(t, err) e, err = ApplyChangesets(t, e, map[uint64]*TimelockExecutionContracts{ chain1: { @@ -62,7 +61,7 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { }) require.NoError(t, err) // We expect now that the link token is owned by the MCMS timelock. - link, err = MaybeLoadLinkTokenState(e.Chains[chain1], addrs) + link, err = MaybeLoadLinkTokenChainState(e.Chains[chain1], addrs) require.NoError(t, err) o, err := link.LinkToken.Owner(nil) require.NoError(t, err) diff --git a/deployment/keystone/changeset/accept_ownership_test.go b/deployment/keystone/changeset/accept_ownership_test.go index 9ac0063143e..dd5fc5bafe9 100644 --- a/deployment/keystone/changeset/accept_ownership_test.go +++ b/deployment/keystone/changeset/accept_ownership_test.go @@ -52,7 +52,7 @@ func TestAcceptAllOwnership(t *testing.T) { require.NoError(t, err) addrs, err := env.ExistingAddresses.AddressesForChain(registrySel) require.NoError(t, err) - timelock, err := commonchangeset.MaybeLoadMCMSWithTimelockState(env.Chains[registrySel], addrs) + timelock, err := commonchangeset.MaybeLoadMCMSWithTimelockChainState(env.Chains[registrySel], addrs) require.NoError(t, err) _, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*commonchangeset.TimelockExecutionContracts{ diff --git a/deployment/keystone/state.go b/deployment/keystone/state.go index cbf449c7f31..0ac7cdc89ed 100644 --- a/deployment/keystone/state.go +++ b/deployment/keystone/state.go @@ -78,7 +78,7 @@ func GetContractSets(lggr logger.Logger, req *GetContractSetsRequest) (*GetContr func loadContractSet(lggr logger.Logger, chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*ContractSet, error) { var out ContractSet - mcmsWithTimelock, err := commonchangeset.MaybeLoadMCMSWithTimelockState(chain, addresses) + mcmsWithTimelock, err := commonchangeset.MaybeLoadMCMSWithTimelockChainState(chain, addresses) if err != nil { return nil, fmt.Errorf("failed to load mcms contract: %w", err) } From 911130aecc519dbc9f763040040800390e7e412f Mon Sep 17 00:00:00 2001 From: Pablo Estrada <139084212+ecPablo@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:22:26 -0600 Subject: [PATCH 31/41] Update deployment/common/changeset/example/link_transfer.go Co-authored-by: Graham Goh --- deployment/common/changeset/example/link_transfer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/common/changeset/example/link_transfer.go b/deployment/common/changeset/example/link_transfer.go index 2bc8298e81e..871da8a2950 100644 --- a/deployment/common/changeset/example/link_transfer.go +++ b/deployment/common/changeset/example/link_transfer.go @@ -70,7 +70,7 @@ func (cfg LinkTransferConfig) Validate(e deployment.Environment) error { } for _, transfer := range transfers { if transfer.To == (common.Address{}) { - return fmt.Errorf("'to' address for transfers must be set") + return errors.New("'to' address for transfers must be set") } if transfer.Value == nil { return fmt.Errorf("value for transfers must be set") From 7dd0fddaf73fa65fb61ca5217b41657b4baa49f6 Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 11 Dec 2024 10:32:36 -0600 Subject: [PATCH 32/41] fix: error handling and validations --- .../common/changeset/example/link_transfer.go | 11 +++++++++- .../changeset/example/link_transfer_test.go | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/deployment/common/changeset/example/link_transfer.go b/deployment/common/changeset/example/link_transfer.go index 871da8a2950..d5595e9aae0 100644 --- a/deployment/common/changeset/example/link_transfer.go +++ b/deployment/common/changeset/example/link_transfer.go @@ -51,15 +51,21 @@ func (cfg LinkTransferConfig) Validate(e deployment.Environment) error { // Check transfers config values. for chainSel, transfers := range cfg.Transfers { - _, err := chain_selectors.GetSelectorFamily(chainSel) + selector, err := chain_selectors.GetSelectorFamily(chainSel) if err != nil { return fmt.Errorf("invalid chain selector: %w", err) } + if selector != chain_selectors.FamilyEVM { + return fmt.Errorf("chain selector %d is not an EVM chain", chainSel) + } chain, ok := e.Chains[chainSel] if !ok { return fmt.Errorf("chain with selector %d not found", chainSel) } addrs, err := e.ExistingAddresses.AddressesForChain(chainSel) + if err != nil { + return fmt.Errorf("error getting addresses for chain %d: %w", chainSel, err) + } if len(transfers) == 0 { return fmt.Errorf("transfers for chainSel %d must have at least one LinkTransfer", chainSel) } @@ -78,6 +84,9 @@ func (cfg LinkTransferConfig) Validate(e deployment.Environment) error { if transfer.Value.Cmp(big.NewInt(0)) == 0 { return fmt.Errorf("value for transfers must be non-zero") } + if transfer.Value.Cmp(big.NewInt(0)) == -1 { + return fmt.Errorf("value for transfers must be positive") + } totalAmount.Add(totalAmount, transfer.Value) } // check that from address has enough funds for the transfers diff --git a/deployment/common/changeset/example/link_transfer_test.go b/deployment/common/changeset/example/link_transfer_test.go index f70226fea78..70e7f5fd8b1 100644 --- a/deployment/common/changeset/example/link_transfer_test.go +++ b/deployment/common/changeset/example/link_transfer_test.go @@ -319,6 +319,26 @@ func TestValidate(t *testing.T) { }, errorMsg: "value for transfers must be non-zero", }, + { + name: "negative value", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chainSelector: { + {To: chain.DeployerKey.From, Value: big.NewInt(-5)}, + }, + }, + }, + errorMsg: "value for transfers must be positive", + }, + { + name: "non-evm-chain", + cfg: example.LinkTransferConfig{ + Transfers: map[uint64][]example.TransferConfig{ + chain_selectors.APTOS_MAINNET.Selector: {{To: mcmsState.Timelock.Address(), Value: big.NewInt(100)}}}, + From: chain.DeployerKey.From, + }, + errorMsg: "chain selector 4741433654826277614 is not an EVM chain", + }, { name: "delay greater than max allowed", cfg: example.LinkTransferConfig{ From 9bc7467f52565089b5e1a420de6fe0f88be42ccf Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 11 Dec 2024 10:41:07 -0600 Subject: [PATCH 33/41] fix: use getDeployer helper --- .../common/changeset/example/link_transfer.go | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/deployment/common/changeset/example/link_transfer.go b/deployment/common/changeset/example/link_transfer.go index d5595e9aae0..5073d90c235 100644 --- a/deployment/common/changeset/example/link_transfer.go +++ b/deployment/common/changeset/example/link_transfer.go @@ -1,6 +1,7 @@ package example import ( + "errors" "fmt" "math/big" "time" @@ -41,12 +42,20 @@ type LinkTransferConfig struct { var _ deployment.ChangeSet[*LinkTransferConfig] = LinkTransfer +func getDeployer(e deployment.Environment, chain uint64, mcmConfig *MCMSConfig) *bind.TransactOpts { + if mcmConfig == nil { + return e.Chains[chain].DeployerKey + } + + return deployment.SimTransactOpts() +} + // Validate checks that the LinkTransferConfig is valid. func (cfg LinkTransferConfig) Validate(e deployment.Environment) error { ctx := e.GetContext() // Check that Transfers map has at least one chainSel if len(cfg.Transfers) == 0 { - return fmt.Errorf("transfers map must have at least one chainSel") + return errors.New("transfers map must have at least one chainSel") } // Check transfers config values. @@ -79,13 +88,13 @@ func (cfg LinkTransferConfig) Validate(e deployment.Environment) error { return errors.New("'to' address for transfers must be set") } if transfer.Value == nil { - return fmt.Errorf("value for transfers must be set") + return errors.New("value for transfers must be set") } if transfer.Value.Cmp(big.NewInt(0)) == 0 { - return fmt.Errorf("value for transfers must be non-zero") + return errors.New("value for transfers must be non-zero") } if transfer.Value.Cmp(big.NewInt(0)) == -1 { - return fmt.Errorf("value for transfers must be positive") + return errors.New("value for transfers must be positive") } totalAmount.Add(totalAmount, transfer.Value) } @@ -102,7 +111,7 @@ func (cfg LinkTransferConfig) Validate(e deployment.Environment) error { // Upper bound for min delay (7 days) if cfg.McmsConfig.MinDelay > MaxTimelockDelay { - return fmt.Errorf("minDelay must be less than 7 days") + return errors.New("minDelay must be less than 7 days") } // Check that validUntil is in the future if time.Unix(int64(cfg.McmsConfig.ValidUntil), 0).Before(time.Now()) { @@ -192,10 +201,8 @@ func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment ChainIdentifier: chainID, Batch: []mcms.Operation{}, } - opts := chain.DeployerKey - if cfg.McmsConfig != nil { - opts = deployment.SimTransactOpts() - } + + 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) From c8f90363d18d704a669b21bd1817e05ef038e1dc Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 11 Dec 2024 12:02:17 -0600 Subject: [PATCH 34/41] feat: split mint burners into a separate changeset --- .../example/add_mint_burners_link.go | 70 ++++++++++++++++ .../example/add_mint_burners_link_test.go | 79 +++++++++++++++++++ .../changeset/example/link_transfer_test.go | 2 +- .../{mint_link_timelock.go => mint_link.go} | 10 +-- ...ink_timelock_test.go => mint_link_test.go} | 18 +++-- 5 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 deployment/common/changeset/example/add_mint_burners_link.go create mode 100644 deployment/common/changeset/example/add_mint_burners_link_test.go rename deployment/common/changeset/example/{mint_link_timelock.go => mint_link.go} (75%) rename deployment/common/changeset/example/{mint_link_timelock_test.go => mint_link_test.go} (83%) diff --git a/deployment/common/changeset/example/add_mint_burners_link.go b/deployment/common/changeset/example/add_mint_burners_link.go new file mode 100644 index 00000000000..4181284ef80 --- /dev/null +++ b/deployment/common/changeset/example/add_mint_burners_link.go @@ -0,0 +1,70 @@ +package example + +import ( + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" +) + +type AddMintersBurnersLinkConfig struct { + ChainSelector uint64 + Minters []common.Address + Burners []common.Address +} + +var _ deployment.ChangeSet[*AddMintersBurnersLinkConfig] = AddMintersBurnersLink + +// AddMintersBurnersLink grants the minter / burner role to the provided addresses. +func AddMintersBurnersLink(e deployment.Environment, cfg *AddMintersBurnersLinkConfig) (deployment.ChangesetOutput, error) { + + chain := e.Chains[cfg.ChainSelector] + addresses, err := e.ExistingAddresses.AddressesForChain(cfg.ChainSelector) + if err != nil { + return deployment.ChangesetOutput{}, err + } + linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addresses) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + for _, minter := range cfg.Minters { + // check if minter is already a minter + isMinter, err := linkState.LinkToken.IsMinter(&bind.CallOpts{Context: e.GetContext()}, minter) + if err != nil { + return deployment.ChangesetOutput{}, err + } + if isMinter { + continue + } + tx, err := linkState.LinkToken.GrantMintRole(chain.DeployerKey, minter) + if err != nil { + return deployment.ChangesetOutput{}, err + } + _, err = deployment.ConfirmIfNoError(chain, tx, err) + if err != nil { + return deployment.ChangesetOutput{}, err + } + } + for _, burner := range cfg.Burners { + // check if burner is already a burner + isMinter, err := linkState.LinkToken.IsBurner(&bind.CallOpts{Context: e.GetContext()}, burner) + if err != nil { + return deployment.ChangesetOutput{}, err + } + if isMinter { + continue + } + tx, err := linkState.LinkToken.GrantBurnRole(chain.DeployerKey, burner) + if err != nil { + return deployment.ChangesetOutput{}, err + } + _, err = deployment.ConfirmIfNoError(chain, tx, err) + if err != nil { + return deployment.ChangesetOutput{}, err + } + } + return deployment.ChangesetOutput{}, nil + +} diff --git a/deployment/common/changeset/example/add_mint_burners_link_test.go b/deployment/common/changeset/example/add_mint_burners_link_test.go new file mode 100644 index 00000000000..600bd09b8d9 --- /dev/null +++ b/deployment/common/changeset/example/add_mint_burners_link_test.go @@ -0,0 +1,79 @@ +package example_test + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/deployment/common/changeset/example" + "github.com/smartcontractkit/chainlink/v2/core/logger" + + "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" +) + +// TestAddMintersBurnersLink tests the AddMintersBurnersLink changeset +func TestAddMintersBurnersLink(t *testing.T) { + t.Parallel() + ctx := context.Background() + lggr := logger.TestLogger(t) + cfg := memory.MemoryEnvironmentConfig{ + Nodes: 1, + Chains: 2, + } + env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) + chainSelector := env.AllChainSelectors()[0] + chain := env.Chains[chainSelector] + + // Deploy Link Token + resp, err := changeset.DeployLinkToken(env, []uint64{chainSelector}) + require.NoError(t, err) + require.NotNil(t, resp) + require.NoError(t, env.ExistingAddresses.Merge(resp.AddressBook)) + + // Deploy MCMS and Timelock + config := changeset.SingleGroupMCMS(t) + respTimelock, err := changeset.DeployMCMSWithTimelock(env, map[uint64]types.MCMSWithTimelockConfig{ + chainSelector: { + Canceller: config, + Bypasser: config, + Proposer: config, + TimelockMinDelay: big.NewInt(0), + }, + }) + require.NoError(t, env.ExistingAddresses.Merge(respTimelock.AddressBook)) + require.NoError(t, err) + + 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 + _, err = example.AddMintersBurnersLink(env, &example.AddMintersBurnersLinkConfig{ + ChainSelector: chainSelector, + Minters: []common.Address{timelockAddress}, + Burners: []common.Address{timelockAddress}, + }) + require.NoError(t, err) + + // check timelock balance + isMinter, err := linkState.LinkToken.IsMinter(&bind.CallOpts{Context: ctx}, timelockAddress) + require.NoError(t, err) + require.True(t, isMinter) + isBurner, err := linkState.LinkToken.IsBurner(&bind.CallOpts{Context: ctx}, timelockAddress) + require.NoError(t, err) + require.True(t, isBurner) +} diff --git a/deployment/common/changeset/example/link_transfer_test.go b/deployment/common/changeset/example/link_transfer_test.go index 70e7f5fd8b1..a956634f0c7 100644 --- a/deployment/common/changeset/example/link_transfer_test.go +++ b/deployment/common/changeset/example/link_transfer_test.go @@ -358,7 +358,7 @@ func TestValidate(t *testing.T) { Transfers: map[uint64][]example.TransferConfig{ chainSelector: {{To: common.Address{}, Value: big.NewInt(100)}}}, }, - errorMsg: "'to' address for transfers must be set", + errorMsg: "'to' address for transfers must be set", }, { name: "invalid config: validUntil in the past", diff --git a/deployment/common/changeset/example/mint_link_timelock.go b/deployment/common/changeset/example/mint_link.go similarity index 75% rename from deployment/common/changeset/example/mint_link_timelock.go rename to deployment/common/changeset/example/mint_link.go index dc722da7f75..dc50f8a1a27 100644 --- a/deployment/common/changeset/example/mint_link_timelock.go +++ b/deployment/common/changeset/example/mint_link.go @@ -30,15 +30,7 @@ func MintLink(e deployment.Environment, cfg *MintLinkConfig) (deployment.Changes return deployment.ChangesetOutput{}, err } - tx, err := linkState.LinkToken.GrantMintRole(chain.DeployerKey, chain.DeployerKey.From) - if err != nil { - return deployment.ChangesetOutput{}, err - } - _, err = deployment.ConfirmIfNoError(chain, tx, err) - if err != nil { - return deployment.ChangesetOutput{}, err - } - tx, err = linkState.LinkToken.Mint(chain.DeployerKey, cfg.To, cfg.Amount) + tx, err := linkState.LinkToken.Mint(chain.DeployerKey, cfg.To, cfg.Amount) if err != nil { return deployment.ChangesetOutput{}, err } diff --git a/deployment/common/changeset/example/mint_link_timelock_test.go b/deployment/common/changeset/example/mint_link_test.go similarity index 83% rename from deployment/common/changeset/example/mint_link_timelock_test.go rename to deployment/common/changeset/example/mint_link_test.go index bbeca8e24f6..374c13dbfca 100644 --- a/deployment/common/changeset/example/mint_link_timelock_test.go +++ b/deployment/common/changeset/example/mint_link_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" @@ -15,7 +16,6 @@ import ( "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/deployment/environment/memory" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token" ) // TestMintLinkTimelock tests the MintLink changeset @@ -58,12 +58,20 @@ func TestMintLinkTimelock(t *testing.T) { require.NoError(t, err) linkState, err := changeset.MaybeLoadLinkTokenChainState(chain, addrs) require.NoError(t, err) - linkAddress := linkState.LinkToken.Address() - timelockAddress := mcmsState.Timelock.Address() - linkContract, err := link_token.NewLinkToken(linkAddress, chain.Client) + _, err = changeset.ApplyChangesets(t, env, nil, []changeset.ChangesetApplication{ + { + Changeset: changeset.WrapChangeSet(example.AddMintersBurnersLink), + Config: &example.AddMintersBurnersLinkConfig{ + ChainSelector: chainSelector, + Minters: []common.Address{chain.DeployerKey.From}, + }, + }, + }) require.NoError(t, err) + timelockAddress := mcmsState.Timelock.Address() + // Mint some funds _, err = example.MintLink(env, &example.MintLinkConfig{ ChainSelector: chainSelector, @@ -73,7 +81,7 @@ func TestMintLinkTimelock(t *testing.T) { require.NoError(t, err) // check timelock balance - endBalance, err := linkContract.BalanceOf(&bind.CallOpts{Context: ctx}, timelockAddress) + endBalance, err := linkState.LinkToken.BalanceOf(&bind.CallOpts{Context: ctx}, timelockAddress) require.NoError(t, err) expectedBalance := big.NewInt(7568) require.Equal(t, expectedBalance, endBalance) From 8a25dbc6d28389e861765801267faec820bf423e Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 11 Dec 2024 13:06:31 -0600 Subject: [PATCH 35/41] fix: name TestMintLink on unit test --- deployment/common/changeset/example/mint_link_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment/common/changeset/example/mint_link_test.go b/deployment/common/changeset/example/mint_link_test.go index 374c13dbfca..9865231be46 100644 --- a/deployment/common/changeset/example/mint_link_test.go +++ b/deployment/common/changeset/example/mint_link_test.go @@ -18,8 +18,8 @@ import ( "github.com/smartcontractkit/chainlink/deployment/environment/memory" ) -// TestMintLinkTimelock tests the MintLink changeset -func TestMintLinkTimelock(t *testing.T) { +// TestMintLink tests the MintLink changeset +func TestMintLink(t *testing.T) { t.Parallel() ctx := context.Background() lggr := logger.TestLogger(t) From fd9fab4f8d26d41da5e02e6c201654f253cc149b Mon Sep 17 00:00:00 2001 From: Pablo Estrada <139084212+ecPablo@users.noreply.github.com> Date: Thu, 12 Dec 2024 07:43:12 -0600 Subject: [PATCH 36/41] Update deployment/common/changeset/example/add_mint_burners_link.go Co-authored-by: Graham Goh --- deployment/common/changeset/example/add_mint_burners_link.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/common/changeset/example/add_mint_burners_link.go b/deployment/common/changeset/example/add_mint_burners_link.go index 4181284ef80..704131cfe65 100644 --- a/deployment/common/changeset/example/add_mint_burners_link.go +++ b/deployment/common/changeset/example/add_mint_burners_link.go @@ -49,7 +49,7 @@ func AddMintersBurnersLink(e deployment.Environment, cfg *AddMintersBurnersLinkC } for _, burner := range cfg.Burners { // check if burner is already a burner - isMinter, err := linkState.LinkToken.IsBurner(&bind.CallOpts{Context: e.GetContext()}, burner) + isBurner, err := linkState.LinkToken.IsBurner(&bind.CallOpts{Context: e.GetContext()}, burner) if err != nil { return deployment.ChangesetOutput{}, err } From 4c68ec85211ca46d42c106b669f37239a470f447 Mon Sep 17 00:00:00 2001 From: Pablo Estrada <139084212+ecPablo@users.noreply.github.com> Date: Thu, 12 Dec 2024 07:43:44 -0600 Subject: [PATCH 37/41] Update deployment/common/changeset/example/add_mint_burners_link.go Co-authored-by: Graham Goh --- deployment/common/changeset/example/add_mint_burners_link.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/common/changeset/example/add_mint_burners_link.go b/deployment/common/changeset/example/add_mint_burners_link.go index 704131cfe65..7322f99dd60 100644 --- a/deployment/common/changeset/example/add_mint_burners_link.go +++ b/deployment/common/changeset/example/add_mint_burners_link.go @@ -53,7 +53,7 @@ func AddMintersBurnersLink(e deployment.Environment, cfg *AddMintersBurnersLinkC if err != nil { return deployment.ChangesetOutput{}, err } - if isMinter { + if isBurner { continue } tx, err := linkState.LinkToken.GrantBurnRole(chain.DeployerKey, burner) From 941260257229c11030ebb9509f2e595203f692d3 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 12 Dec 2024 08:07:05 -0600 Subject: [PATCH 38/41] fix: use changeset apply for unit tests environment setup --- .../example/add_mint_burners_link_test.go | 37 ++----------------- .../changeset/example/mint_link_test.go | 36 ++---------------- 2 files changed, 7 insertions(+), 66 deletions(-) diff --git a/deployment/common/changeset/example/add_mint_burners_link_test.go b/deployment/common/changeset/example/add_mint_burners_link_test.go index 600bd09b8d9..4dbfddc0b30 100644 --- a/deployment/common/changeset/example/add_mint_burners_link_test.go +++ b/deployment/common/changeset/example/add_mint_burners_link_test.go @@ -2,54 +2,25 @@ package example_test import ( "context" - "math/big" "testing" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - "go.uber.org/zap/zapcore" - - "github.com/smartcontractkit/chainlink/deployment/common/changeset/example" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/deployment/common/changeset" - "github.com/smartcontractkit/chainlink/deployment/common/types" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/deployment/common/changeset/example" ) // TestAddMintersBurnersLink tests the AddMintersBurnersLink changeset func TestAddMintersBurnersLink(t *testing.T) { t.Parallel() ctx := context.Background() - lggr := logger.TestLogger(t) - cfg := memory.MemoryEnvironmentConfig{ - Nodes: 1, - Chains: 2, - } - env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) + // Deploy Link Token and Timelock contracts and add addresses to environment + env := setupLinkTransferTestEnv(t) + chainSelector := env.AllChainSelectors()[0] chain := env.Chains[chainSelector] - - // Deploy Link Token - resp, err := changeset.DeployLinkToken(env, []uint64{chainSelector}) - require.NoError(t, err) - require.NotNil(t, resp) - require.NoError(t, env.ExistingAddresses.Merge(resp.AddressBook)) - - // Deploy MCMS and Timelock - config := changeset.SingleGroupMCMS(t) - respTimelock, err := changeset.DeployMCMSWithTimelock(env, map[uint64]types.MCMSWithTimelockConfig{ - chainSelector: { - Canceller: config, - Bypasser: config, - Proposer: config, - TimelockMinDelay: big.NewInt(0), - }, - }) - require.NoError(t, env.ExistingAddresses.Merge(respTimelock.AddressBook)) - require.NoError(t, err) - addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) require.NoError(t, err) require.Len(t, addrs, 6) diff --git a/deployment/common/changeset/example/mint_link_test.go b/deployment/common/changeset/example/mint_link_test.go index 9865231be46..1c60c3221de 100644 --- a/deployment/common/changeset/example/mint_link_test.go +++ b/deployment/common/changeset/example/mint_link_test.go @@ -1,55 +1,25 @@ package example_test import ( - "context" "math/big" "testing" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - "go.uber.org/zap/zapcore" - - "github.com/smartcontractkit/chainlink/deployment/common/changeset/example" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/deployment/common/changeset" - "github.com/smartcontractkit/chainlink/deployment/common/types" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/deployment/common/changeset/example" ) // TestMintLink tests the MintLink changeset func TestMintLink(t *testing.T) { t.Parallel() - ctx := context.Background() - lggr := logger.TestLogger(t) - cfg := memory.MemoryEnvironmentConfig{ - Nodes: 1, - Chains: 2, - } - env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) + env := setupLinkTransferTestEnv(t) + ctx := env.GetContext() chainSelector := env.AllChainSelectors()[0] chain := env.Chains[chainSelector] - // Deploy Link Token - resp, err := changeset.DeployLinkToken(env, []uint64{chainSelector}) - require.NoError(t, err) - require.NotNil(t, resp) - require.NoError(t, env.ExistingAddresses.Merge(resp.AddressBook)) - - // Deploy MCMS and Timelock - config := changeset.SingleGroupMCMS(t) - respTimelock, err := changeset.DeployMCMSWithTimelock(env, map[uint64]types.MCMSWithTimelockConfig{ - chainSelector: { - Canceller: config, - Bypasser: config, - Proposer: config, - TimelockMinDelay: big.NewInt(0), - }, - }) - require.NoError(t, env.ExistingAddresses.Merge(respTimelock.AddressBook)) - require.NoError(t, err) - addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) require.NoError(t, err) require.Len(t, addrs, 6) From 6fd93b964a42af7f79ccfbfb2894d2ee64a71566 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 12 Dec 2024 08:27:16 -0600 Subject: [PATCH 39/41] fix: linting errors --- integration-tests/smoke/ccip/ccip_fee_boosting_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go index 9308424d17a..48d9061ec63 100644 --- a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go +++ b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go @@ -9,8 +9,6 @@ import ( "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink-common/pkg/config" - commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" From f0aa7dabd856801a780c0c72af0ab30df775ae90 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 12 Dec 2024 10:13:10 -0600 Subject: [PATCH 40/41] fix: merge conflicts --- .../changeset/example/link_transfer_test.go | 7 ++-- deployment/common/changeset/state.go | 39 ++++++++++++------- .../transfer_to_mcms_with_timelock_test.go | 1 - 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/deployment/common/changeset/example/link_transfer_test.go b/deployment/common/changeset/example/link_transfer_test.go index a956634f0c7..cd84613be00 100644 --- a/deployment/common/changeset/example/link_transfer_test.go +++ b/deployment/common/changeset/example/link_transfer_test.go @@ -11,6 +11,7 @@ import ( chain_selectors "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink/deployment/common/changeset/example" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/stretchr/testify/require" @@ -32,7 +33,7 @@ func setupLinkTransferTestEnv(t *testing.T) deployment.Environment { } env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) chainSelector := env.AllChainSelectors()[0] - config := changeset.SingleGroupMCMS(t) + config := proposalutils.SingleGroupMCMS(t) // Deploy MCMS and Timelock env, err := changeset.ApplyChangesets(t, env, nil, []changeset.ChangesetApplication{ @@ -87,7 +88,7 @@ func TestLinkTransferMCMS(t *testing.T) { _, err = deployment.ConfirmIfNoError(chain, tx, err) require.NoError(t, err) - timelocks := map[uint64]*changeset.TimelockExecutionContracts{ + timelocks := map[uint64]*proposalutils.TimelockExecutionContracts{ chainSelector: { Timelock: mcmsState.Timelock, CallProxy: mcmsState.CallProxy, @@ -162,7 +163,7 @@ func TestLinkTransfer(t *testing.T) { _, err = deployment.ConfirmIfNoError(chain, tx, err) require.NoError(t, err) - timelocks := map[uint64]*changeset.TimelockExecutionContracts{ + timelocks := map[uint64]*proposalutils.TimelockExecutionContracts{ chainSelector: { Timelock: mcmsState.Timelock, CallProxy: mcmsState.CallProxy, diff --git a/deployment/common/changeset/state.go b/deployment/common/changeset/state.go index 7184e5e4d79..0db34abad71 100644 --- a/deployment/common/changeset/state.go +++ b/deployment/common/changeset/state.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common" + owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" @@ -22,17 +23,6 @@ type MCMSWithTimelockState struct { *proposalutils.MCMSWithTimelockContracts } -func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*MCMSWithTimelockState, error) { - contracts, err := proposalutils.MaybeLoadMCMSWithTimelockContracts(chain, addresses) - if err != nil { - return nil, err - } - - return &MCMSWithTimelockState{ - MCMSWithTimelockContracts: contracts, - }, nil -} - func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWithTimelockView, error) { if err := state.Validate(); err != nil { return v1_0.MCMSWithTimelockView{}, err @@ -95,7 +85,9 @@ func MaybeLoadMCMSWithTimelockState(env deployment.Environment, chainSelectors [ // - It only found part of the bundle of contracts // - If found more than one instance of a contract (we expect one bundle in the given addresses) func MaybeLoadMCMSWithTimelockChainState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*MCMSWithTimelockState, error) { - state := MCMSWithTimelockState{} + state := MCMSWithTimelockState{ + MCMSWithTimelockContracts: &proposalutils.MCMSWithTimelockContracts{}, + } // We expect one of each contract on the chain. timelock := deployment.NewTypeAndVersion(types.RBACTimelock, deployment.Version1_0_0) callProxy := deployment.NewTypeAndVersion(types.CallProxy, deployment.Version1_0_0) @@ -160,7 +152,28 @@ func (s LinkTokenState) GenerateLinkView() (v1_0.LinkTokenView, error) { return v1_0.GenerateLinkTokenView(s.LinkToken) } -func MaybeLoadLinkTokenState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*LinkTokenState, error) { +// MaybeLoadLinkTokenState loads the LinkTokenState state for each chain in the given environment. +func MaybeLoadLinkTokenState(env deployment.Environment, chainSelectors []uint64) (map[uint64]*LinkTokenState, error) { + result := map[uint64]*LinkTokenState{} + for _, chainSelector := range chainSelectors { + chain, ok := env.Chains[chainSelector] + if !ok { + return nil, fmt.Errorf("chain %d not found", chainSelector) + } + addressesChain, err := env.ExistingAddresses.AddressesForChain(chainSelector) + if err != nil { + return nil, err + } + state, err := MaybeLoadLinkTokenChainState(chain, addressesChain) + if err != nil { + return nil, err + } + result[chainSelector] = state + } + return result, nil +} + +func MaybeLoadLinkTokenChainState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*LinkTokenState, error) { state := LinkTokenState{} linkToken := deployment.NewTypeAndVersion(types.LinkToken, deployment.Version1_0_0) // Perhaps revisit if we have a use case for multiple. diff --git a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go index 523493cb87d..7ba11596a2d 100644 --- a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go +++ b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go @@ -1,7 +1,6 @@ package changeset import ( - "math/big" "testing" "github.com/ethereum/go-ethereum/common" From 7c2ecf0d39347a53e68447f5239aa4c0510b9e23 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 12 Dec 2024 18:12:42 -0600 Subject: [PATCH 41/41] fix: remove valid unit to reuse util for proposal creation --- .../common/changeset/example/link_transfer.go | 28 ++++++------------- .../changeset/example/link_transfer_test.go | 23 ++------------- 2 files changed, 11 insertions(+), 40 deletions(-) diff --git a/deployment/common/changeset/example/link_transfer.go b/deployment/common/changeset/example/link_transfer.go index 5073d90c235..2e3be48a4d1 100644 --- a/deployment/common/changeset/example/link_transfer.go +++ b/deployment/common/changeset/example/link_transfer.go @@ -29,7 +29,6 @@ type TransferConfig struct { } type MCMSConfig struct { - ValidUntil uint32 // unix time until the proposal will be valid MinDelay time.Duration // delay for timelock worker to execute the transfers. OverrideRoot bool } @@ -113,10 +112,7 @@ func (cfg LinkTransferConfig) Validate(e deployment.Environment) error { if cfg.McmsConfig.MinDelay > MaxTimelockDelay { return errors.New("minDelay must be less than 7 days") } - // Check that validUntil is in the future - if time.Unix(int64(cfg.McmsConfig.ValidUntil), 0).Before(time.Now()) { - return fmt.Errorf("validUntil must be in the future") - } + return nil } @@ -180,7 +176,7 @@ func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment } mcmsPerChain := map[uint64]*owner_helpers.ManyChainMultiSig{} - timelockAddresses := map[mcms.ChainIdentifier]common.Address{} + timelockAddresses := map[uint64]common.Address{} // Initialize state for each chain linkStatePerChain, mcmsStatePerChain, err := initStatePerChain(cfg, e) @@ -196,7 +192,7 @@ func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment mcmsPerChain[uint64(chainID)] = mcmsState.ProposerMcm - timelockAddresses[chainID] = timelockAddress + timelockAddresses[chainSelector] = timelockAddress batch := timelock.BatchChainOperation{ ChainIdentifier: chainID, Batch: []mcms.Operation{}, @@ -221,22 +217,14 @@ func LinkTransfer(e deployment.Environment, cfg *LinkTransferConfig) (deployment allBatches = append(allBatches, batch) } - chainMetadata, err := proposalutils.BuildProposalMetadata(chainSelectors, mcmsPerChain) - if err != nil { - return deployment.ChangesetOutput{}, err - } + if cfg.McmsConfig != nil { - proposal, err := timelock.NewMCMSWithTimelockProposal( - "1", - cfg.McmsConfig.ValidUntil, - []mcms.Signature{}, - cfg.McmsConfig.OverrideRoot, - chainMetadata, + proposal, err := proposalutils.BuildProposalFromBatches( timelockAddresses, - "Value transfer proposal", + mcmsPerChain, allBatches, - timelock.Schedule, - cfg.McmsConfig.MinDelay.String(), + "LINK Value transfer proposal", + cfg.McmsConfig.MinDelay, ) if err != nil { return deployment.ChangesetOutput{}, err diff --git a/deployment/common/changeset/example/link_transfer_test.go b/deployment/common/changeset/example/link_transfer_test.go index cd84613be00..eecfbd37c95 100644 --- a/deployment/common/changeset/example/link_transfer_test.go +++ b/deployment/common/changeset/example/link_transfer_test.go @@ -111,7 +111,6 @@ func TestLinkTransferMCMS(t *testing.T) { }, }, McmsConfig: &example.MCMSConfig{ - ValidUntil: 4131638958, MinDelay: 0, OverrideRoot: true, }, @@ -237,8 +236,7 @@ func TestValidate(t *testing.T) { chainSelector: {{To: mcmsState.Timelock.Address(), Value: big.NewInt(100)}}}, From: chain.DeployerKey.From, McmsConfig: &example.MCMSConfig{ - ValidUntil: uint32(time.Now().Add(1 * time.Hour).Unix()), - MinDelay: time.Hour, + MinDelay: time.Hour, }, }, }, @@ -262,8 +260,7 @@ func TestValidate(t *testing.T) { }, From: mcmsState.Timelock.Address(), McmsConfig: &example.MCMSConfig{ - ValidUntil: uint32(time.Now().Add(1 * time.Hour).Unix()), - MinDelay: time.Hour, + MinDelay: time.Hour, }, }, errorMsg: "sender does not have enough funds for transfers for chain selector 909606746561742123, required: 1850, available: 0", @@ -347,8 +344,7 @@ func TestValidate(t *testing.T) { chainSelector: {{To: mcmsState.Timelock.Address(), Value: big.NewInt(100)}}}, From: chain.DeployerKey.From, McmsConfig: &example.MCMSConfig{ - ValidUntil: uint32(time.Now().Add(1 * time.Hour).Unix()), - MinDelay: time.Hour * 24 * 10, + MinDelay: time.Hour * 24 * 10, }, }, errorMsg: "minDelay must be less than 7 days", @@ -361,19 +357,6 @@ func TestValidate(t *testing.T) { }, errorMsg: "'to' address for transfers must be set", }, - { - name: "invalid config: validUntil in the past", - cfg: example.LinkTransferConfig{ - Transfers: map[uint64][]example.TransferConfig{ - chainSelector: {{To: mcmsState.Timelock.Address(), Value: big.NewInt(100)}}}, - From: chain.DeployerKey.From, - McmsConfig: &example.MCMSConfig{ - ValidUntil: uint32(time.Now().Add(-1 * time.Hour).Unix()), - MinDelay: time.Hour, - }, - }, - errorMsg: "validUntil must be in the future", - }, } for _, tt := range tests {