Skip to content

Commit

Permalink
Added data feeds deployment changesets (#16323)
Browse files Browse the repository at this point in the history
* add data feeds deployment changesets

* fix lint issues pt1

* fix lint issues pt2

* fix major lint issues

* fix goimports

* replace if/else with switch in state

* replace mcms with mcmsv2

* create BuildMCMProposal func

* add acceptOwnership changeset

* lint

* add tests for mcms, accept_ownership changeset

* fix lint issues

* use changesetv2

* add test cases for mcms

* fix lint issues

* fix cache_deploy test

* add mcms support to confirm/propose aggregator changesets. buildproposal batch

* fix lint issues

* remove custom wrapper for legacy changeset

* add more complex changesets

* fix lint

* minor changes

* fix lint

* fix typo

* update buildproposal env
  • Loading branch information
karen-stepanyan authored Feb 27, 2025
1 parent 88a2f8f commit 3e5ec23
Show file tree
Hide file tree
Showing 40 changed files with 3,171 additions and 0 deletions.
56 changes: 56 additions & 0 deletions deployment/data-feeds/changeset/accept_ownership.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package changeset

import (
"errors"
"fmt"

mcmslib "github.com/smartcontractkit/mcms"

"github.com/smartcontractkit/chainlink/deployment"
commonChangesets "github.com/smartcontractkit/chainlink/deployment/common/changeset"
"github.com/smartcontractkit/chainlink/deployment/data-feeds/changeset/types"
)

// AcceptOwnershipChangeset is a changeset that will create an MCM proposal to accept the ownership of a contract.
// Returns an MSM proposal to accept the ownership of a contract. Doesn't return a new addressbook.
// Once proposal is executed, new owned contract can be imported into the addressbook.
var AcceptOwnershipChangeset = deployment.CreateChangeSet(acceptOwnershipLogic, acceptOwnershipPrecondition)

func acceptOwnershipLogic(env deployment.Environment, c types.AcceptOwnershipConfig) (deployment.ChangesetOutput, error) {
chain := env.Chains[c.ChainSelector]

_, contract, err := commonChangesets.LoadOwnableContract(c.ContractAddress, chain.Client)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to load the contract %w", err)
}

tx, err := contract.AcceptOwnership(deployment.SimTransactOpts())
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to create accept transfer ownership tx %w", err)
}

proposal, err := BuildMCMProposals(env, "accept ownership to timelock", c.ChainSelector, []ProposalData{
{
contract: c.ContractAddress.Hex(),
tx: tx,
},
}, c.McmsConfig.MinDelay)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal: %w", err)
}

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

func acceptOwnershipPrecondition(env deployment.Environment, c types.AcceptOwnershipConfig) error {
_, ok := env.Chains[c.ChainSelector]
if !ok {
return fmt.Errorf("chain not found in env %d", c.ChainSelector)
}

if c.McmsConfig == nil {
return errors.New("mcms config is required")
}

return ValidateMCMSAddresses(env.ExistingAddresses, c.ChainSelector)
}
65 changes: 65 additions & 0 deletions deployment/data-feeds/changeset/accept_ownership_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package changeset

import (
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"

"github.com/smartcontractkit/chainlink/deployment/data-feeds/changeset/types"

"github.com/smartcontractkit/chainlink/deployment"
commonChangesets "github.com/smartcontractkit/chainlink/deployment/common/changeset"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
commonTypes "github.com/smartcontractkit/chainlink/deployment/common/types"

"github.com/smartcontractkit/chainlink-common/pkg/logger"

"github.com/smartcontractkit/chainlink/deployment/environment/memory"
)

func TestAcceptOwnership(t *testing.T) {
t.Parallel()
lggr := logger.Test(t)
cfg := memory.MemoryEnvironmentConfig{
Nodes: 1,
Chains: 1,
}
env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg)

chainSelector := env.AllChainSelectors()[0]
chain := env.Chains[chainSelector]

newEnv, err := commonChangesets.Apply(t, env, nil,
commonChangesets.Configure(
deployment.CreateLegacyChangeSet(commonChangesets.DeployMCMSWithTimelockV2),
map[uint64]commonTypes.MCMSWithTimelockConfigV2{
chainSelector: proposalutils.SingleGroupTimelockConfigV2(t),
},
),
)
require.NoError(t, err)

timeLockAddress, err := deployment.SearchAddressBook(newEnv.ExistingAddresses, chainSelector, "RBACTimelock")
require.NoError(t, err)

cache, _ := DeployCache(chain, []string{})
tx, _ := cache.Contract.TransferOwnership(chain.DeployerKey, common.HexToAddress(timeLockAddress))
_, err = chain.Confirm(tx)
require.NoError(t, err)

_, err = commonChangesets.Apply(t, newEnv, nil,
commonChangesets.Configure(
AcceptOwnershipChangeset,
types.AcceptOwnershipConfig{
ChainSelector: chainSelector,
ContractAddress: cache.Contract.Address(),
McmsConfig: &types.MCMSConfig{
MinDelay: 1,
},
},
),
)
require.NoError(t, err)
}
69 changes: 69 additions & 0 deletions deployment/data-feeds/changeset/confirm_aggregator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package changeset

import (
"fmt"

mcmslib "github.com/smartcontractkit/mcms"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/data-feeds/changeset/types"
proxy "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/data-feeds/generated/aggregator_proxy"
)

// ConfirmAggregatorChangeset is a changeset that confirms a proposed aggregator on deployed AggregatorProxy contract
// This changeset may return a timelock proposal if the MCMS config is provided, otherwise it will execute the transaction with the deployer key.
var ConfirmAggregatorChangeset = deployment.CreateChangeSet(confirmAggregatorLogic, confirmAggregatorPrecondition)

func confirmAggregatorLogic(env deployment.Environment, c types.ProposeConfirmAggregatorConfig) (deployment.ChangesetOutput, error) {
chain := env.Chains[c.ChainSelector]

aggregatorProxy, err := proxy.NewAggregatorProxy(c.ProxyAddress, chain.Client)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to load AggregatorProxy: %w", err)
}

txOpt := chain.DeployerKey
if c.McmsConfig != nil {
txOpt = deployment.SimTransactOpts()
}

tx, err := aggregatorProxy.ConfirmAggregator(txOpt, c.NewAggregatorAddress)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to execute ConfirmAggregator: %w", err)
}

if c.McmsConfig != nil {
proposal, err := BuildMCMProposals(env, "proposal to confirm a new aggregator", c.ChainSelector, []ProposalData{
{
contract: aggregatorProxy.Address().Hex(),
tx: tx,
},
}, c.McmsConfig.MinDelay)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal: %w", err)
}
return deployment.ChangesetOutput{MCMSTimelockProposals: []mcmslib.TimelockProposal{*proposal}}, nil
}

_, err = chain.Confirm(tx)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm transaction: %s, %w", tx.Hash().String(), err)
}

return deployment.ChangesetOutput{}, nil
}

func confirmAggregatorPrecondition(env deployment.Environment, c types.ProposeConfirmAggregatorConfig) error {
_, ok := env.Chains[c.ChainSelector]
if !ok {
return fmt.Errorf("chain not found in env %d", c.ChainSelector)
}

if c.McmsConfig != nil {
if err := ValidateMCMSAddresses(env.ExistingAddresses, c.ChainSelector); err != nil {
return err
}
}

return nil
}
118 changes: 118 additions & 0 deletions deployment/data-feeds/changeset/confirm_aggregator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package changeset_test

import (
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
commonTypes "github.com/smartcontractkit/chainlink/deployment/common/types"

commonChangesets "github.com/smartcontractkit/chainlink/deployment/common/changeset"

"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink/deployment/data-feeds/changeset"
"github.com/smartcontractkit/chainlink/deployment/data-feeds/changeset/types"
"github.com/smartcontractkit/chainlink/deployment/environment/memory"
)

func TestConfirmAggregator(t *testing.T) {
t.Parallel()
lggr := logger.Test(t)
cfg := memory.MemoryEnvironmentConfig{
Nodes: 1,
Chains: 1,
}
env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg)

chainSelector := env.AllChainSelectors()[0]

// without MCMS
newEnv, err := commonChangesets.Apply(t, env, nil,
// Deploy cache and aggregator proxy
commonChangesets.Configure(
changeset.DeployCacheChangeset,
types.DeployConfig{
ChainsToDeploy: []uint64{chainSelector},
Labels: []string{"data-feeds"},
},
),
commonChangesets.Configure(
changeset.DeployAggregatorProxyChangeset,
types.DeployAggregatorProxyConfig{
ChainsToDeploy: []uint64{chainSelector},
AccessController: []common.Address{common.HexToAddress("0x")},
},
),
)
require.NoError(t, err)

proxyAddress, err := deployment.SearchAddressBook(newEnv.ExistingAddresses, chainSelector, "AggregatorProxy")
require.NoError(t, err)

newEnv, err = commonChangesets.Apply(t, newEnv, nil,
// Propose and confirm new Aggregator
commonChangesets.Configure(
changeset.ProposeAggregatorChangeset,
types.ProposeConfirmAggregatorConfig{
ChainSelector: chainSelector,
ProxyAddress: common.HexToAddress(proxyAddress),
NewAggregatorAddress: common.HexToAddress("0x123"),
},
),
commonChangesets.Configure(
changeset.ConfirmAggregatorChangeset,
types.ProposeConfirmAggregatorConfig{
ChainSelector: chainSelector,
ProxyAddress: common.HexToAddress(proxyAddress),
NewAggregatorAddress: common.HexToAddress("0x123"),
},
),
commonChangesets.Configure(
deployment.CreateLegacyChangeSet(commonChangesets.DeployMCMSWithTimelockV2),
map[uint64]commonTypes.MCMSWithTimelockConfigV2{
chainSelector: proposalutils.SingleGroupTimelockConfigV2(t),
},
),
)
require.NoError(t, err)

// with MCMS
newEnv, err = commonChangesets.Apply(t, newEnv, nil,
// propose new Aggregator
commonChangesets.Configure(
changeset.ProposeAggregatorChangeset,
types.ProposeConfirmAggregatorConfig{
ChainSelector: chainSelector,
ProxyAddress: common.HexToAddress(proxyAddress),
NewAggregatorAddress: common.HexToAddress("0x124"),
},
),
// transfer proxy ownership to timelock
commonChangesets.Configure(
deployment.CreateLegacyChangeSet(commonChangesets.TransferToMCMSWithTimelockV2),
commonChangesets.TransferToMCMSWithTimelockConfig{
ContractsByChain: map[uint64][]common.Address{
chainSelector: {common.HexToAddress(proxyAddress)},
},
MinDelay: 0,
},
),
// confirm from timelock
commonChangesets.Configure(
changeset.ConfirmAggregatorChangeset,
types.ProposeConfirmAggregatorConfig{
ChainSelector: chainSelector,
ProxyAddress: common.HexToAddress(proxyAddress),
NewAggregatorAddress: common.HexToAddress("0x124"),
McmsConfig: &types.MCMSConfig{
MinDelay: 0,
},
},
),
)
require.NoError(t, err)
}
78 changes: 78 additions & 0 deletions deployment/data-feeds/changeset/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package changeset

import (
"fmt"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/data-feeds/changeset/types"
proxy "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/data-feeds/generated/aggregator_proxy"
cache "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/data-feeds/generated/data_feeds_cache"
)

func DeployCache(chain deployment.Chain, labels []string) (*types.DeployCacheResponse, error) {
cacheAddr, tx, cacheContract, err := cache.DeployDataFeedsCache(chain.DeployerKey, chain.Client)
if err != nil {
return nil, fmt.Errorf("failed to deploy DataFeedsCache: %w", err)
}

_, err = chain.Confirm(tx)
if err != nil {
return nil, fmt.Errorf("failed to confirm DataFeedsCache: %w", err)
}

tvStr, err := cacheContract.TypeAndVersion(&bind.CallOpts{})
if err != nil {
return nil, fmt.Errorf("failed to get type and version: %w", err)
}

tv, err := deployment.TypeAndVersionFromString(tvStr)
if err != nil {
return nil, fmt.Errorf("failed to parse type and version from %s: %w", tvStr, err)
}

for _, label := range labels {
tv.Labels.Add(label)
}

resp := &types.DeployCacheResponse{
Address: cacheAddr,
Tx: tx.Hash(),
Tv: tv,
Contract: cacheContract,
}
return resp, nil
}

func DeployAggregatorProxy(chain deployment.Chain, aggregator common.Address, accessController common.Address, labels []string) (*types.DeployProxyResponse, error) {
proxyAddr, tx, proxyContract, err := proxy.DeployAggregatorProxy(chain.DeployerKey, chain.Client, aggregator, accessController)
if err != nil {
return nil, fmt.Errorf("failed to deploy AggregatorProxy: %w", err)
}

_, err = chain.Confirm(tx)
if err != nil {
return nil, fmt.Errorf("failed to confirm AggregatorProxy: %w", err)
}

// AggregatorProxy contract doesn't implement typeAndVersion interface, so we have to set it manually
tvStr := "AggregatorProxy 1.0.0"
tv, err := deployment.TypeAndVersionFromString(tvStr)
if err != nil {
return nil, fmt.Errorf("failed to parse type and version from %s: %w", tvStr, err)
}

for _, label := range labels {
tv.Labels.Add(label)
}

resp := &types.DeployProxyResponse{
Address: proxyAddr,
Tx: tx.Hash(),
Tv: tv,
Contract: proxyContract,
}
return resp, nil
}
Loading

0 comments on commit 3e5ec23

Please sign in to comment.