Skip to content

token expansion v2#1930

Merged
tt-cll merged 20 commits intomainfrom
tt/tokens2
Apr 6, 2026
Merged

token expansion v2#1930
tt-cll merged 20 commits intomainfrom
tt/tokens2

Conversation

@tt-cll
Copy link
Copy Markdown
Collaborator

@tt-cll tt-cll commented Apr 2, 2026

No description provided.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR advances “Token Expansion v2” by wiring EVM token adapters into the TokenExpansion flow, adding support for an additional-CCV threshold parameter, and migrating/removing legacy “deploy token + pool” sequences/changesets in v1.7.0 in favor of TokenExpansion-driven deployments.

Changes:

  • Add ThresholdForAdditionalCCVs to token-pool deployment input and pass it through TokenExpansion into EVM 2.0.0+ pool deployments.
  • Implement missing EVM token adapter methods for v1.6.1 and v1.7.0 (2.0.0 pools), plus update v1.7.0 tests to deploy via TokenExpansion.
  • Remove legacy v1.7.0 deploy-token-and-pool sequences/changesets and adjust devenv Go module metadata accordingly.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
devenv/go.mod Removes replace-block; keeps gogo/protobuf replace and updates indirect deps.
devenv/go.sum Updates dependency checksums after module graph changes.
deployment/tokens/token_expansion.go Adds threshold field and ref-merging simplification for token/pool deployment.
chains/evm/deployment/v1_6_1/adapters/tokens.go Implements DeployToken/Verify + DeployTokenPoolForToken wrapper and role-grant logic.
ccv/chains/evm/deployment/v1_7_0/sequences/tokens/test_helpers_test.go New helper to deploy token+pool via TokenExpansion for tests.
ccv/chains/evm/deployment/v1_7_0/sequences/tokens/deploy_token_and_pool.go Removes legacy deploy-token-and-pool sequence (now replaced by TokenExpansion in tests).
ccv/chains/evm/deployment/v1_7_0/sequences/tokens/deploy_token_and_pool_test.go Removes legacy tests for removed sequence.
ccv/chains/evm/deployment/v1_7_0/sequences/tokens/configure_token_pool_test.go Migrates to TokenExpansion-based deployment helper.
ccv/chains/evm/deployment/v1_7_0/sequences/tokens/configure_token_pool_for_remote_chain_test.go Migrates to TokenExpansion-based deployment helper and datastore lookups.
ccv/chains/evm/deployment/v1_7_0/sequences/tokens/configure_token_for_transfers_test.go Migrates to TokenExpansion-based deployment helper and datastore lookups.
ccv/chains/evm/deployment/v1_7_0/changesets/deploy_token_and_pool.go Removes legacy changeset.
ccv/chains/evm/deployment/v1_7_0/changesets/deploy_token_and_pool_test.go Removes legacy changeset tests.
ccv/chains/evm/deployment/v1_7_0/adapters/tokens.go Implements 2.0.0 token adapter deploy/pool deploy, manual registration, rate limits, and authority updates.
ccv/chains/evm/deployment/v1_7_0/adapters/tokens_test.go Updates adapter tests to rely on init-registered adapter and adds TokenExpansion E2E test.
ccv/chains/evm/deployment/v1_7_0/adapters/init.go Registers the v1.7.0 EVM TokenAdapter in the global registry at init.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ccv/chains/evm/deployment/v1_7_0/sequences/tokens/test_helpers_test.go Outdated
Comment thread chains/evm/deployment/v1_6_1/adapters/tokens.go Outdated
Comment thread ccv/chains/evm/deployment/v1_7_0/adapters/tokens.go
Comment thread ccv/chains/evm/deployment/v1_7_0/adapters/tokens.go Outdated
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm not mistaken, the current v1.6.0 adapter is capable of dealing with v1.6.1, so is it possible to replace all the duplicated code with something like this?

var _ tokens.TokenAdapter = &TokenAdapter{}

// TokenAdapter is the adapter for EVM tokens using 1.6.1 token pools.
type TokenAdapter struct {
	v1_6 v1_6_0_seq.EVMAdapter
}

func (t *TokenAdapter) ConfigureTokenForTransfersSequence() *cldf_ops.Sequence[tokens.ConfigureTokenForTransfersInput, sequences.OnChainOutput, cldf_chain.BlockChains] {
	return t.v1_6.ConfigureTokenForTransfersSequence()
}

func (t *TokenAdapter) AddressRefToBytes(ref datastore.AddressRef) ([]byte, error) {
	return t.v1_6.AddressRefToBytes(ref)
}

func (t *TokenAdapter) DeriveTokenAddress(e deployment.Environment, chainSelector uint64, poolRef datastore.AddressRef) ([]byte, error) {
	return t.v1_6.DeriveTokenAddress(e, chainSelector, poolRef)
}

func (t *TokenAdapter) DeployToken() *cldf_ops.Sequence[tokens.DeployTokenInput, sequences.OnChainOutput, cldf_chain.BlockChains] {
	return t.v1_6.DeployToken()
}

func (t *TokenAdapter) DeployTokenVerify(e deployment.Environment, input tokens.DeployTokenInput) error {
	return t.v1_6.DeployTokenVerify(e, input)
}

//...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good call

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as v1.6.1 adapter - would it be worth it / make sense to inject the v1.6.0 adapter and reuse some of its methods to reduce duplication? It looks like there's an opportunity to re-use these:

  • DeployToken <- the seq for this is in the v1.6 folder, but seems like it should be moved to the v1.0 folder
  • DeployTokenVerify
  • AddressRefToBytes
  • DeriveTokenAddress
  • DeriveTokenDecimals
  • ManualRegistration
  • UpdateAuthorities

Or maybe it makes sense to approach this a different way? During the develop > main merge, token expansion was modified such that it loads adapters based on the token pool version rather than the chain adapter version:

func ResolveAdapter(registry *TokenAdapterRegistry, selector uint64, tokenPoolVersion *semver.Version) (TokenAdapter, string, error) {
family, err := chain_selectors.GetSelectorFamily(selector)
if err != nil {
return nil, "", fmt.Errorf("failed to get chain family for remote chain selector %d: %w", selector, err)
}
version := deploy_utils.StripPatchVersion(tokenPoolVersion)
if family == chain_selectors.FamilyEVM && version.String() == deploy_utils.Version_1_5_0.String() {
version = deploy_utils.Version_1_6_0 // NOTE: for EVM, the v1.6.0 adapter currently handles v1.5.x pools
}
adapter, ok := registry.GetTokenAdapter(family, version)
if !ok {
return nil, "", fmt.Errorf("no token adapter registered for chain family '%s' and version '%s'", family, version.String())
}
return adapter, family, nil
}

So maybe each adapter should purely be responsible for managing pool bindings for a single version? I think that would remove the need for a switch clause on the version from the v1.6.0 adapter and make things a bit more straightforward unless I'm missing something 👀

@tt-cll tt-cll enabled auto-merge April 6, 2026 11:14
DeployTokenPoolInput: &tokenscore.DeployTokenPoolInput{
PoolType: string(burn_mint_token_pool.ContractType),
TokenPoolQualifier: "TEST",
ThresholdForAdditionalCCVs: thresholdAmountForAdditionalCCVs.String(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Should this also be called ThresholdAmount?

Comment thread deployment/tokens/token_expansion.go Outdated
// ThresholdForAdditionalCCVs is the transfer amount (in base units, as a decimal string)
// above which additional CCVs are required. Applicable to EVM 2.0.0+ token pools.
// If empty or "0", no threshold is set.
ThresholdForAdditionalCCVs string `yaml:"thresholdForAdditionalCCVs,omitempty" json:"thresholdForAdditionalCCVs,omitempty"`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if it's worth moving all the version specific field under a property

    type V2Fields struct {
{
	    // ThresholdForAdditionalCCVs is the transfer amount (in base units, as a decimal string)
	    // above which additional CCVs are required. Applicable to EVM 2.0.0+ token pools.
	    // If empty or "0", no threshold is set.
	    ThresholdForAdditionalCCVs string `yaml:"thresholdForAdditionalCCVs,omitempty" json:"thresholdForAdditionalCCVs,omitempty"`
	}
}

    v1_6Only *V1_6Fields
    v2Only *V2Fields

This would make it more explicit from the input and could allow us to enforce only one of the field group is set at once?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will handle in a followup

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

Metric tt/tokens2 main
Coverage 70.1% 69.8%

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

return poolOwner, rlAdmin, nil
}

func (a *EVMAdapter) FindLatestAddressRef(ds datastore.DataStore, ref datastore.AddressRef) (common.Address, error) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking: might be worth it to extract some of this logic to datastore_utils 👀

// 1. if it is already present in the TokenRef, then skip the datastore altogether and simply use the given address
// 2. if the token address is not present, then use whatever fields are defined in TokenRef to lookup the address in the DS
// 3. if step #2 produces multiple tokens or an error, then attempt to resolve the token address from the TokenPoolRef
// 4. if we still can't derive it from the TokenPoolRef, then give up
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it okay if we retain some of these comments in the v1.0.0 adapter?

Some of them could help clarify the code/logic a bit more

// Helper methods //
////////////////////

func (a *EVMAdapter) GetTokenAdminRegistryAddress(ds datastore.DataStore, selector uint64) (common.Address, error) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems duplicated with the new helper introduced in the v1.0.0 adapter - any chance we can remove this and re-use some more things?

Comment on lines +18 to 24
// AddressRefToBytes converts a hex-encoded address to its EVM byte representation.
func (a *EVMAdapter) AddressRefToBytes(ref datastore.AddressRef) ([]byte, error) {
if !common.IsHexAddress(ref.Address) {
return nil, fmt.Errorf("address %q is not a valid hex address", ref.Address)
}

return common.HexToAddress(ref.Address).Bytes(), nil
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe v1.0.0 offers an impl for AddressRefToBytes - can this be removed?

Comment on lines -112 to -114
func StripPatchVersion(version *semver.Version) *semver.Version {
return semver.New(version.Major(), version.Minor(), 0, version.Prerelease(), version.Metadata())
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it okay if we keep this around? Wanna use it to refactor the fees adapter:

lookupVersion := semver.MustParse(fmt.Sprintf("%d.%d.0", v.Major(), v.Minor()))

@tt-cll tt-cll added this pull request to the merge queue Apr 6, 2026
Merged via the queue into main with commit 0ec22f0 Apr 6, 2026
57 checks passed
@tt-cll tt-cll deleted the tt/tokens2 branch April 6, 2026 18:25
return &TokenAdapter{
EVMPoolAdapter: evm1_0_0.EVMPoolAdapter{
Ops: &poolOpsV151{},
DeployTokenPoolSeq: v1_6_0_seq.DeployTokenPool,
Copy link
Copy Markdown
Collaborator

@chris-de-leon-cll chris-de-leon-cll Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking: for later we may want to refactor DeployTokenPool so that v1.5.1 has its own sequence and the v1.6.0 sequence is only responsible for its own bindings

Comment on lines +47 to +48
v1_6_0_scenarios = semver.MustParse("1.6.0")
v1_5_1_scenarios = semver.MustParse("1.5.1")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking (and unrelated): might be worth it to start using the predefined constants moving forward

var (
Version_1_0_0 = semver.MustParse("1.0.0")
Version_1_5_0 = semver.MustParse("1.5.0")
Version_1_5_1 = semver.MustParse("1.5.1")
Version_1_6_0 = semver.MustParse("1.6.0")
Version_1_6_1 = semver.MustParse("1.6.1")
Version_2_0_0 = semver.MustParse("2.0.0")
)

deployTokenPoolInput.TokenPoolVersion = input.TokenPoolVersion
deployTokenPoolInput.ExistingDataStore = e.DataStore
deployTokenPoolInput.ChainSelector = selector
deployTokenPoolReport, err := cldf_ops.ExecuteSequence(e.OperationsBundle, tokenPoolAdapter.DeployTokenPoolForToken(), e.BlockChains, deployTokenPoolInput)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On this line, we are using the tokenPoolAdapter variable which is tied to the ChainAdapterVersion - instead the adapter should be fetched based on the TokenPoolVersion right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants