Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions chains/evm/deployment/tokens/tokenimpl/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package tokenimpl

import (
"fmt"
"math/big"

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

"github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/operations/burn_mint_erc20"
"github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/operations/erc20"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/evm"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/evm/operations/contract"
cldf_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations"
bnm_erc20_bindings "github.com/smartcontractkit/chainlink-evm/gethwrappers/shared/generated/initial/burn_mint_erc20"
)

func revokeDefaultAdminRoleBurnMintERC20(b cldf_ops.Bundle, chain evm.Chain, token, user common.Address) ([]contract.WriteOutput, error) {
tokenContract, err := bnm_erc20_bindings.NewBurnMintERC20(token, chain.Client)
if err != nil {
return nil, fmt.Errorf("failed to instantiate BurnMintERC20 contract: %w", err)
}
role, err := tokenContract.DEFAULTADMINROLE(&bind.CallOpts{Context: b.GetContext()})
if err != nil {
return nil, fmt.Errorf("failed to get default admin role constant: %w", err)
}
report, err := cldf_ops.ExecuteOperation(b, burn_mint_erc20.RevokeAdminRole, chain, contract.FunctionInput[burn_mint_erc20.RoleAssignment]{
ChainSelector: chain.Selector,
Address: token,
Args: burn_mint_erc20.RoleAssignment{
Role: role,
To: user,
},
})
if err != nil {
return nil, fmt.Errorf("failed to revoke default admin role: %w", err)
}
return []contract.WriteOutput{report.Output}, nil
}

func grantDefaultAdminRoleBurnMintERC20(b cldf_ops.Bundle, chain evm.Chain, token, user common.Address) ([]contract.WriteOutput, error) {
tokenContract, err := bnm_erc20_bindings.NewBurnMintERC20(token, chain.Client)
if err != nil {
return nil, fmt.Errorf("failed to instantiate BurnMintERC20 contract: %w", err)
}
role, err := tokenContract.DEFAULTADMINROLE(&bind.CallOpts{Context: b.GetContext()})
if err != nil {
return nil, fmt.Errorf("failed to get default admin role constant: %w", err)
}
report, err := cldf_ops.ExecuteOperation(b, burn_mint_erc20.GrantAdminRole, chain, contract.FunctionInput[burn_mint_erc20.RoleAssignment]{
ChainSelector: chain.Selector,
Address: token,
Args: burn_mint_erc20.RoleAssignment{
Role: role,
To: user,
},
})
if err != nil {
return nil, fmt.Errorf("failed to grant default admin role: %w", err)
}
return []contract.WriteOutput{report.Output}, nil
}

func grantMintAndBurnRolesBurnMintERC20(b cldf_ops.Bundle, chain evm.Chain, token, pool common.Address) ([]contract.WriteOutput, error) {
report, err := cldf_ops.ExecuteOperation(b, burn_mint_erc20.GrantMintAndBurnRoles, chain, contract.FunctionInput[common.Address]{
ChainSelector: chain.Selector,
Address: token,
Args: pool,
})
if err != nil {
return nil, fmt.Errorf("failed to grant mint and burn roles: %w", err)
}
return []contract.WriteOutput{report.Output}, nil
}

func setCCIPAdminBurnMintERC20(b cldf_ops.Bundle, chain evm.Chain, token, ccipAdmin common.Address) ([]contract.WriteOutput, error) {
report, err := cldf_ops.ExecuteOperation(b, burn_mint_erc20.SetCCIPAdmin, chain, contract.FunctionInput[string]{
ChainSelector: chain.Selector,
Address: token,
Args: ccipAdmin.Hex(),
})
if err != nil {
return nil, fmt.Errorf("failed to set CCIP admin: %w", err)
}
return []contract.WriteOutput{report.Output}, nil
}

func transferTokensERC20(b cldf_ops.Bundle, chain evm.Chain, token, to common.Address, scaledAmount *big.Int) ([]contract.WriteOutput, error) {
report, err := cldf_ops.ExecuteOperation(b, erc20.Transfer, chain, contract.FunctionInput[erc20.TransferArgs]{
ChainSelector: chain.Selector,
Address: token,
Args: erc20.TransferArgs{
Amount: scaledAmount,
Receiver: to,
},
})
if err != nil {
return nil, fmt.Errorf("failed to transfer ERC20 tokens: %w", err)
}
return []contract.WriteOutput{report.Output}, nil
}
78 changes: 78 additions & 0 deletions chains/evm/deployment/tokens/tokenimpl/impl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package tokenimpl

import (
"math/big"

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

tokensapi "github.com/smartcontractkit/chainlink-ccip/deployment/tokens"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/evm"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/evm/operations/contract"
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
"github.com/smartcontractkit/chainlink-deployments-framework/deployment"
cldf_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations"
)

// CapabilitySet reports the optional flow steps a token contract type
// participates in. The orchestrating sequence reads these flags to
// decide whether to invoke the corresponding step.
type CapabilitySet struct {
// ParticipatesInPoolRoleGrant is true when the token requires token-side
// role grants for the pool to operate; GrantPoolRoles must emit those writes.
ParticipatesInPoolRoleGrant bool

// SupportsAdminRole is true when the token exposes a manageable admin or
// default-admin role; GrantAdminRole and RevokeAdminRole must implement it.
SupportsAdminRole bool

// SupportsCCIPAdmin is true when the token has a token-level CCIP admin;
// SetCCIPAdmin must emit the write that updates it.
SupportsCCIPAdmin bool

// SupportsPreMint is true when the token can mint during deployment and
// transfer those tokens afterward to the configured recipient.
SupportsPreMint bool
}

// Token encapsulates everything specific to one EVM token contract type.
type Token interface {
// ContractType returns the deployment.ContractType used as the registry key.
ContractType() deployment.ContractType

// Capabilities returns the static feature flags for this token type.
Capabilities() CapabilitySet

// RevokeAdminRole revokes the default-admin or contract-specific admin
// role from user. Callers should consult SupportsAdminRole first.
RevokeAdminRole(b cldf_ops.Bundle, chain evm.Chain, token, user common.Address) ([]contract.WriteOutput, error)

// GrantAdminRole grants the default-admin or contract-specific
// admin role to user. Returns an error for token types whose
// Capabilities.SupportsAdminRole is false; callers should consult
// that flag first.
GrantAdminRole(b cldf_ops.Bundle, chain evm.Chain, token, user common.Address) ([]contract.WriteOutput, error)

// GrantPoolRoles emits the writes that authorize a freshly-deployed pool
// to mint/burn (or its TIP-20 issuer-role equivalent) against this token.
// proposalExecutor is the MCMS timelock (or zero when unused); BurnMintERC677
// uses it for PrepareGrantMintAndBurnRoles. Other token types ignore it.
// Returns an error for token types that don't participate in pool role
// granting; ParticipatesInPoolRoleGrant is the authoritative flag, callers
// should consult it first.
GrantPoolRoles(b cldf_ops.Bundle, chain evm.Chain, token, pool, proposalExecutor common.Address) ([]contract.WriteOutput, error)

// SetCCIPAdmin sets the token-level CCIP admin where the token contract
// supports one. Callers should consult SupportsCCIPAdmin first.
SetCCIPAdmin(b cldf_ops.Bundle, chain evm.Chain, token, admin common.Address) ([]contract.WriteOutput, error)

// Transfer emits the writes that transfer already-scaled token units from
// the deployer to to, typically for post-deploy pre-mint distribution.
Transfer(b cldf_ops.Bundle, chain evm.Chain, token, to common.Address, scaledAmount *big.Int) ([]contract.WriteOutput, error)

// Deploy performs the token contract deployment, returning the
// resulting datastore reference and any token-side write outputs
// produced during deployment. Implementations may call lower-level
// deployment operations or helpers, but batching is handled by the
// outer token deployment sequence.
Deploy(b cldf_ops.Bundle, chain evm.Chain, in tokensapi.DeployTokenInput) (datastore.AddressRef, []contract.WriteOutput, error)
}
35 changes: 35 additions & 0 deletions chains/evm/deployment/tokens/tokenimpl/lookup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package tokenimpl

import (
bnmERC20 "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/operations/burn_mint_erc20"
dripV1_0_0 "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/operations/burn_mint_erc20_with_drip"
"github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/operations/erc20"
"github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/operations/tip20"
dripV1_5_0 "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_5_0/operations/burn_mint_erc20_with_drip"
"github.com/smartcontractkit/chainlink-ccip/deployment/utils"
"github.com/smartcontractkit/chainlink-deployments-framework/deployment"
)

var tokenImpls = map[deployment.ContractType]Token{
dripV1_5_0.ContractType: tokenBurnMintERC20WithDripV1_5_0{},
dripV1_0_0.ContractType: tokenBurnMintERC20WithDripV1_0_0{},
utils.ERC677TokenHelper: tokenBurnMintERC677{},
utils.BurnMintToken: tokenBurnMintERC677{},
bnmERC20.ContractType: tokenBurnMintERC20{},
erc20.ContractType: tokenERC20{},
tip20.ContractType: tokenTIP20{},
}
Comment thread
chris-de-leon-cll marked this conversation as resolved.

// Get returns the token implementation for an EVM token contract type.
func Get(ct deployment.ContractType) (Token, bool) {
s, ok := tokenImpls[ct]
return s, ok
}

// Capabilities returns the capability set for an EVM token contract type, or the zero value if the token implementation does not exist.
func Capabilities(ct deployment.ContractType) CapabilitySet {
if s, ok := Get(ct); ok {
return s.Capabilities()
}
return CapabilitySet{}
}
86 changes: 86 additions & 0 deletions chains/evm/deployment/tokens/tokenimpl/token_burn_mint_erc20.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package tokenimpl

import (
"fmt"
"math/big"

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

"github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/operations/burn_mint_erc20"
tokensapi "github.com/smartcontractkit/chainlink-ccip/deployment/tokens"
"github.com/smartcontractkit/chainlink-ccip/deployment/utils"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/evm"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/evm/operations/contract"
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
"github.com/smartcontractkit/chainlink-deployments-framework/deployment"
"github.com/smartcontractkit/chainlink-deployments-framework/operations"
)

type tokenBurnMintERC20 struct{}

func (tokenBurnMintERC20) ContractType() deployment.ContractType {
return burn_mint_erc20.ContractType
}

func (tokenBurnMintERC20) Capabilities() CapabilitySet {
return CapabilitySet{
ParticipatesInPoolRoleGrant: true,
SupportsAdminRole: true,
SupportsCCIPAdmin: true,
SupportsPreMint: true,
}
}

func (tokenBurnMintERC20) RevokeAdminRole(b operations.Bundle, chain evm.Chain, token, user common.Address) ([]contract.WriteOutput, error) {
return revokeDefaultAdminRoleBurnMintERC20(b, chain, token, user)
}

func (tokenBurnMintERC20) GrantAdminRole(b operations.Bundle, chain evm.Chain, token, externalAdmin common.Address) ([]contract.WriteOutput, error) {
return grantDefaultAdminRoleBurnMintERC20(b, chain, token, externalAdmin)
}

func (tokenBurnMintERC20) GrantPoolRoles(b operations.Bundle, chain evm.Chain, token, pool, _ common.Address) ([]contract.WriteOutput, error) {
return grantMintAndBurnRolesBurnMintERC20(b, chain, token, pool)
}

func (tokenBurnMintERC20) SetCCIPAdmin(b operations.Bundle, chain evm.Chain, token, ccipAdmin common.Address) ([]contract.WriteOutput, error) {
return setCCIPAdminBurnMintERC20(b, chain, token, ccipAdmin)
}

func (tokenBurnMintERC20) Transfer(b operations.Bundle, chain evm.Chain, token, to common.Address, scaledAmount *big.Int) ([]contract.WriteOutput, error) {
// NOTE: BnM ERC20 tokens inherit from a standard ERC20 implementation, so we can use the same transfer helper function as the plain ERC20 token.
return transferTokensERC20(b, chain, token, to, scaledAmount)
}

func (tokenBurnMintERC20) Deploy(b operations.Bundle, chain evm.Chain, in tokensapi.DeployTokenInput) (datastore.AddressRef, []contract.WriteOutput, error) {
maxSupply := big.NewInt(0)
if in.Supply != nil {
maxSupply = tokensapi.ScaleTokenAmount(new(big.Int).SetUint64(*in.Supply), in.Decimals)
}

preMint := big.NewInt(0)
if in.PreMint != nil {
preMint = tokensapi.ScaleTokenAmount(new(big.Int).SetUint64(*in.PreMint), in.Decimals)
}

ref, err := contract.MaybeDeployContract(b, burn_mint_erc20.Deploy, chain,
contract.DeployInput[burn_mint_erc20.ConstructorArgs]{
TypeAndVersion: deployment.NewTypeAndVersion(burn_mint_erc20.ContractType, *utils.Version_1_0_0),
ChainSelector: chain.Selector,
Qualifier: &in.Symbol,
Args: burn_mint_erc20.ConstructorArgs{
Name: in.Name,
Symbol: in.Symbol,
Decimals: in.Decimals,
MaxSupply: maxSupply,
PreMint: preMint,
},
},
nil,
)
if err != nil {
return datastore.AddressRef{}, nil, fmt.Errorf("failed to deploy BurnMintERC20 token: %w", err)
}

return ref, nil, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package tokenimpl

import (
"fmt"
"math/big"

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

"github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/operations/burn_mint_erc20_with_drip"
tokensapi "github.com/smartcontractkit/chainlink-ccip/deployment/tokens"
"github.com/smartcontractkit/chainlink-ccip/deployment/utils"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/evm"
"github.com/smartcontractkit/chainlink-deployments-framework/chain/evm/operations/contract"
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
"github.com/smartcontractkit/chainlink-deployments-framework/deployment"
"github.com/smartcontractkit/chainlink-deployments-framework/operations"
)

// Deprecated: BurnMintERC20WithDripToken has no actual drip functionality - it
// is retained only for compatibility with existing tests and should be removed
// in a future cleanup.
type tokenBurnMintERC20WithDripV1_0_0 struct{}

func (tokenBurnMintERC20WithDripV1_0_0) ContractType() deployment.ContractType {
return burn_mint_erc20_with_drip.ContractType
}

func (tokenBurnMintERC20WithDripV1_0_0) Capabilities() CapabilitySet {
return CapabilitySet{
ParticipatesInPoolRoleGrant: true,
SupportsAdminRole: true,
SupportsCCIPAdmin: true,
SupportsPreMint: true,
}
}

func (tokenBurnMintERC20WithDripV1_0_0) RevokeAdminRole(b operations.Bundle, chain evm.Chain, token, externalAdmin common.Address) ([]contract.WriteOutput, error) {
return revokeDefaultAdminRoleBurnMintERC20(b, chain, token, externalAdmin)
}

func (tokenBurnMintERC20WithDripV1_0_0) GrantAdminRole(b operations.Bundle, chain evm.Chain, token, externalAdmin common.Address) ([]contract.WriteOutput, error) {
return grantDefaultAdminRoleBurnMintERC20(b, chain, token, externalAdmin)
}

func (tokenBurnMintERC20WithDripV1_0_0) GrantPoolRoles(b operations.Bundle, chain evm.Chain, token, pool, _ common.Address) ([]contract.WriteOutput, error) {
return grantMintAndBurnRolesBurnMintERC20(b, chain, token, pool)
}

func (tokenBurnMintERC20WithDripV1_0_0) SetCCIPAdmin(b operations.Bundle, chain evm.Chain, token, ccipAdmin common.Address) ([]contract.WriteOutput, error) {
return setCCIPAdminBurnMintERC20(b, chain, token, ccipAdmin)
}

func (tokenBurnMintERC20WithDripV1_0_0) Transfer(b operations.Bundle, chain evm.Chain, token, to common.Address, scaledAmount *big.Int) ([]contract.WriteOutput, error) {
// NOTE: BnM ERC20 drip tokens inherit from a standard ERC20 implementation, so we can use the same transfer helper function as the plain ERC20 token.
return transferTokensERC20(b, chain, token, to, scaledAmount)
}

func (tokenBurnMintERC20WithDripV1_0_0) Deploy(b operations.Bundle, chain evm.Chain, in tokensapi.DeployTokenInput) (datastore.AddressRef, []contract.WriteOutput, error) {
maxSupply := big.NewInt(0)
if in.Supply != nil {
maxSupply = tokensapi.ScaleTokenAmount(new(big.Int).SetUint64(*in.Supply), in.Decimals)
}

preMint := big.NewInt(0)
if in.PreMint != nil {
preMint = tokensapi.ScaleTokenAmount(new(big.Int).SetUint64(*in.PreMint), in.Decimals)
}

ref, err := contract.MaybeDeployContract(b, burn_mint_erc20_with_drip.Deploy, chain,
contract.DeployInput[burn_mint_erc20_with_drip.ConstructorArgs]{
TypeAndVersion: deployment.NewTypeAndVersion(burn_mint_erc20_with_drip.ContractType, *utils.Version_1_0_0),
ChainSelector: chain.Selector,
Qualifier: &in.Symbol,
Args: burn_mint_erc20_with_drip.ConstructorArgs{
Name: in.Name,
Symbol: in.Symbol,
Decimals: in.Decimals,
MaxSupply: maxSupply,
PreMint: preMint,
},
},
nil,
)
if err != nil {
return datastore.AddressRef{}, nil, fmt.Errorf("failed to deploy BurnMintERC20WithDripToken token: %w", err)
}

return ref, nil, nil
}
Loading
Loading