Skip to content

Commit

Permalink
Add changesets for self-serve token pool deployments (#15877)
Browse files Browse the repository at this point in the history
* Add changeset

* Add first working test

* Make fns private

* Add more use case tests

* Unit tests for validations and deployment fns

* Linting

* More linting

* goimports formatting

* Test fixes

* Refactor into 3 changesets

* Finish token pool configuration tests

* Align with test package conventions

* Linting

* goimports

* More tests & goimports

* chain.Name to chain.String

* Update tests

* Goimports

* Use confirm if no error

* Comments

* Remove addresses as inputs

* Deployer group integration & improvements

* Estimate gas limit

* Comments

* Remove unstable test

* Move fn to helpers

* Fix deployer group

* Buildfix

* Sync ccip rmn test with develop

* goimports

* Clean up token admin registry changeset

* More comments

* Revert deployer group, add opCount offset capability

* goimports

* Split out token admin reg changesets

* Revert propose.go

* Comments

* Fix build

* Lint, remove forceDeployment

* goimports
  • Loading branch information
kylesmartin authored Jan 24, 2025
1 parent 4f646f9 commit de9e993
Show file tree
Hide file tree
Showing 19 changed files with 3,308 additions and 32 deletions.
59 changes: 59 additions & 0 deletions deployment/ccip/changeset/cs_accept_admin_role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package changeset

import (
"fmt"

"github.com/ethereum/go-ethereum/common"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/token_admin_registry"
)

var _ deployment.ChangeSet[TokenAdminRegistryChangesetConfig] = AcceptAdminRoleChangeset

func validateAcceptAdminRole(
config token_admin_registry.TokenAdminRegistryTokenConfig,
sender common.Address,
externalAdmin common.Address,
symbol TokenSymbol,
chain deployment.Chain,
) error {
// We must be the pending administrator
if config.PendingAdministrator != sender {
return fmt.Errorf("unable to accept admin role for %s token on %s: %s is not the pending administrator (%s)", symbol, chain, sender, config.PendingAdministrator)
}
return nil
}

// AcceptAdminRoleChangeset accepts admin rights for tokens on the token admin registry.
func AcceptAdminRoleChangeset(env deployment.Environment, c TokenAdminRegistryChangesetConfig) (deployment.ChangesetOutput, error) {
if err := c.Validate(env, false, validateAcceptAdminRole); err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("invalid TokenAdminRegistryChangesetConfig: %w", err)
}
state, err := LoadOnchainState(env)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err)
}
deployerGroup := NewDeployerGroup(env, state, c.MCMS)

for chainSelector, tokenSymbolToPoolInfo := range c.Pools {
chain := env.Chains[chainSelector]
chainState := state.Chains[chainSelector]
opts, err := deployerGroup.GetDeployer(chainSelector)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to get deployer for %s", chain)
}
for symbol, poolInfo := range tokenSymbolToPoolInfo {
_, tokenAddress, err := poolInfo.GetPoolAndTokenAddress(env.GetContext(), symbol, chain, chainState)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to get state of %s token on chain %s: %w", symbol, chain, err)
}
_, err = chainState.TokenAdminRegistry.AcceptAdminRole(opts, tokenAddress)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to create acceptAdminRole transaction for %s on %s registry: %w", symbol, chain, err)
}
}
}

return deployerGroup.Enact("accept admin role for tokens on token admin registries")
}
207 changes: 207 additions & 0 deletions deployment/ccip/changeset/cs_accept_admin_role_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package changeset_test

import (
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset"
"github.com/smartcontractkit/chainlink/v2/core/logger"
)

func TestAcceptAdminRoleChangeset_Validations(t *testing.T) {
t.Parallel()

e, selectorA, _, tokens, timelockContracts := testhelpers.SetupTwoChainEnvironmentWithTokens(t, logger.TestLogger(t), true)

e = testhelpers.DeployTestTokenPools(t, e, map[uint64]changeset.DeployTokenPoolInput{
selectorA: {
Type: changeset.BurnMintTokenPool,
TokenAddress: tokens[selectorA].Address,
LocalTokenDecimals: testhelpers.LocalTokenDecimals,
},
}, true)

mcmsConfig := &changeset.MCMSConfig{
MinDelay: 0 * time.Second,
}

tests := []struct {
Config changeset.TokenAdminRegistryChangesetConfig
ErrStr string
Msg string
}{
{
Msg: "Chain selector is invalid",
Config: changeset.TokenAdminRegistryChangesetConfig{
Pools: map[uint64]map[changeset.TokenSymbol]changeset.TokenPoolInfo{
0: map[changeset.TokenSymbol]changeset.TokenPoolInfo{},
},
},
ErrStr: "failed to validate chain selector 0",
},
{
Msg: "Chain selector doesn't exist in environment",
Config: changeset.TokenAdminRegistryChangesetConfig{
Pools: map[uint64]map[changeset.TokenSymbol]changeset.TokenPoolInfo{
5009297550715157269: map[changeset.TokenSymbol]changeset.TokenPoolInfo{},
},
},
ErrStr: "does not exist in environment",
},
{
Msg: "Invalid pool type",
Config: changeset.TokenAdminRegistryChangesetConfig{
MCMS: mcmsConfig,
Pools: map[uint64]map[changeset.TokenSymbol]changeset.TokenPoolInfo{
selectorA: map[changeset.TokenSymbol]changeset.TokenPoolInfo{
testhelpers.TestTokenSymbol: {
Type: "InvalidType",
Version: deployment.Version1_5_1,
},
},
},
},
ErrStr: "InvalidType is not a known token pool type",
},
{
Msg: "Invalid pool version",
Config: changeset.TokenAdminRegistryChangesetConfig{
MCMS: mcmsConfig,
Pools: map[uint64]map[changeset.TokenSymbol]changeset.TokenPoolInfo{
selectorA: map[changeset.TokenSymbol]changeset.TokenPoolInfo{
testhelpers.TestTokenSymbol: {
Type: changeset.BurnMintTokenPool,
Version: deployment.Version1_0_0,
},
},
},
},
ErrStr: "1.0.0 is not a known token pool version",
},
{
Msg: "Not pending admin",
Config: changeset.TokenAdminRegistryChangesetConfig{
MCMS: mcmsConfig,
Pools: map[uint64]map[changeset.TokenSymbol]changeset.TokenPoolInfo{
selectorA: map[changeset.TokenSymbol]changeset.TokenPoolInfo{
testhelpers.TestTokenSymbol: {
Type: changeset.BurnMintTokenPool,
Version: deployment.Version1_5_1,
},
},
},
},
ErrStr: "is not the pending administrator",
},
}

for _, test := range tests {
t.Run(test.Msg, func(t *testing.T) {
_, err := commonchangeset.ApplyChangesets(t, e, timelockContracts, []commonchangeset.ChangesetApplication{
{
Changeset: commonchangeset.WrapChangeSet(changeset.AcceptAdminRoleChangeset),
Config: test.Config,
},
})
require.Error(t, err)
require.ErrorContains(t, err, test.ErrStr)
})
}
}

func TestAcceptAdminRoleChangeset_Execution(t *testing.T) {
for _, mcmsConfig := range []*changeset.MCMSConfig{nil, &changeset.MCMSConfig{MinDelay: 0 * time.Second}} {
msg := "Accept admin role with MCMS"
if mcmsConfig == nil {
msg = "Accept admin role without MCMS"
}

t.Run(msg, func(t *testing.T) {
e, selectorA, selectorB, tokens, timelockContracts := testhelpers.SetupTwoChainEnvironmentWithTokens(t, logger.TestLogger(t), mcmsConfig != nil)

e = testhelpers.DeployTestTokenPools(t, e, map[uint64]changeset.DeployTokenPoolInput{
selectorA: {
Type: changeset.BurnMintTokenPool,
TokenAddress: tokens[selectorA].Address,
LocalTokenDecimals: testhelpers.LocalTokenDecimals,
},
selectorB: {
Type: changeset.BurnMintTokenPool,
TokenAddress: tokens[selectorB].Address,
LocalTokenDecimals: testhelpers.LocalTokenDecimals,
},
}, mcmsConfig != nil)

state, err := changeset.LoadOnchainState(e)
require.NoError(t, err)

registryOnA := state.Chains[selectorA].TokenAdminRegistry
registryOnB := state.Chains[selectorB].TokenAdminRegistry

e, err = commonchangeset.ApplyChangesets(t, e, timelockContracts, []commonchangeset.ChangesetApplication{
{
Changeset: commonchangeset.WrapChangeSet(changeset.ProposeAdminRoleChangeset),
Config: changeset.TokenAdminRegistryChangesetConfig{
MCMS: mcmsConfig,
Pools: map[uint64]map[changeset.TokenSymbol]changeset.TokenPoolInfo{
selectorA: map[changeset.TokenSymbol]changeset.TokenPoolInfo{
testhelpers.TestTokenSymbol: {
Type: changeset.BurnMintTokenPool,
Version: deployment.Version1_5_1,
},
},
selectorB: map[changeset.TokenSymbol]changeset.TokenPoolInfo{
testhelpers.TestTokenSymbol: {
Type: changeset.BurnMintTokenPool,
Version: deployment.Version1_5_1,
},
},
},
},
},
{
Changeset: commonchangeset.WrapChangeSet(changeset.AcceptAdminRoleChangeset),
Config: changeset.TokenAdminRegistryChangesetConfig{
MCMS: mcmsConfig,
Pools: map[uint64]map[changeset.TokenSymbol]changeset.TokenPoolInfo{
selectorA: map[changeset.TokenSymbol]changeset.TokenPoolInfo{
testhelpers.TestTokenSymbol: {
Type: changeset.BurnMintTokenPool,
Version: deployment.Version1_5_1,
},
},
selectorB: map[changeset.TokenSymbol]changeset.TokenPoolInfo{
testhelpers.TestTokenSymbol: {
Type: changeset.BurnMintTokenPool,
Version: deployment.Version1_5_1,
},
},
},
},
},
})
require.NoError(t, err)

configOnA, err := registryOnA.GetTokenConfig(nil, tokens[selectorA].Address)
require.NoError(t, err)
if mcmsConfig != nil {
require.Equal(t, state.Chains[selectorA].Timelock.Address(), configOnA.Administrator)
} else {
require.Equal(t, e.Chains[selectorA].DeployerKey.From, configOnA.Administrator)
}

configOnB, err := registryOnB.GetTokenConfig(nil, tokens[selectorB].Address)
require.NoError(t, err)
if mcmsConfig != nil {
require.Equal(t, state.Chains[selectorB].Timelock.Address(), configOnB.Administrator)
} else {
require.Equal(t, e.Chains[selectorB].DeployerKey.From, configOnB.Administrator)
}
})
}
}
Loading

0 comments on commit de9e993

Please sign in to comment.