-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add changesets for self-serve token pool deployments (#15877)
* 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
1 parent
4f646f9
commit de9e993
Showing
19 changed files
with
3,308 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.