Skip to content

Commit

Permalink
feat(deployment): add link token transfer changeset
Browse files Browse the repository at this point in the history
Added 2 new changesets in shared package which transfer the link token, the other one returns a MCMS proposal.

JIRA: https://smartcontract-it.atlassian.net/browse/DPA-1314
  • Loading branch information
graham-chainlink committed Dec 4, 2024
1 parent 07bd33c commit fb4e1e0
Show file tree
Hide file tree
Showing 2 changed files with 448 additions and 0 deletions.
184 changes: 184 additions & 0 deletions deployment/common/changeset/transfer_link_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package changeset

import (
"errors"
"fmt"
"math/big"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
"github.com/smartcontractkit/chainlink/deployment/common/types"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/link_token"
)

var _ deployment.ChangeSet[TransferLinkTokenConfig] = TransferLinkToken
var _ deployment.ChangeSet[TransferLinkTokenProposalConfig] = TransferLinkTokenProposal

type Transfer struct {
To common.Address
Amount *big.Int
}

type TransferLinkTokenConfig struct {
Transfers map[uint64]Transfer
}

func (c TransferLinkTokenConfig) Validate() error {
for k, v := range c.Transfers {
if err := deployment.IsValidChainSelector(k); err != nil {
return err
}

if v.To == (common.Address{}) {
return errors.New("to address must be set")
}
if v.Amount == nil || v.Amount.Sign() == -1 {
return errors.New("amount must be set and positive")
}
}
return nil
}

// TransferLinkToken transfers link token to the to address on the chain identified by the chainSelector.
func TransferLinkToken(e deployment.Environment, config TransferLinkTokenConfig) (deployment.ChangesetOutput, error) {
if err := config.Validate(); err != nil {
return deployment.ChangesetOutput{}, err
}

for chainSelector, transferConfig := range config.Transfers {
addresses, err := e.ExistingAddresses.AddressesForChain(chainSelector)
if err != nil {
return deployment.ChangesetOutput{}, err
}

for address, typeversion := range addresses {
if typeversion.Type == types.LinkToken && typeversion.Version == deployment.Version1_0_0 {
chain, ok := e.Chains[chainSelector]
if !ok {
return deployment.ChangesetOutput{}, fmt.Errorf("chain not found in environment")
}
contract, err := link_token.NewLinkToken(common.HexToAddress(address), chain.Client)
if err != nil {
return deployment.ChangesetOutput{}, err
}

tx, err := contract.Transfer(chain.DeployerKey, transferConfig.To, transferConfig.Amount)
if _, err = deployment.ConfirmIfNoError(chain, tx, err); err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm transfer link token to %s: %v", transferConfig.To, err)
}
e.Logger.Infow("Transferred LINK",
"to", transferConfig.To,
"amount", transferConfig.Amount,
"txHash", tx.Hash().Hex(),
"chainSelector", chainSelector)

break
}
}
}
return deployment.ChangesetOutput{}, nil
}

type TransferLinkTokenProposalConfig struct {
Transfers map[uint64]Transfer

// OwnersPerChain is a mapping from chain selector to the owner contract address on that chain.
OwnersPerChain map[uint64]common.Address
// ProposerMCMSes is a mapping from chain selector to the proposer MCMS contract on that chain.
ProposerMCMSes map[uint64]*gethwrappers.ManyChainMultiSig
// MinDelay is the minimum amount of time that must pass before the proposal
// can be executed onchain.
// This is typically set to 3 hours but can be set to 0 for immediate execution (useful for tests).
MinDelay time.Duration
}

func (c TransferLinkTokenProposalConfig) Validate() error {
for k, v := range c.Transfers {
if err := deployment.IsValidChainSelector(k); err != nil {
return err
}

if v.To == (common.Address{}) {
return errors.New("to address must be set")
}
if v.Amount == nil || v.Amount.Sign() == -1 {
return errors.New("amount must be set and positive")
}
if _, ok := c.OwnersPerChain[k]; !ok {
return fmt.Errorf("missing owner for chain %d", k)
}
if _, ok := c.ProposerMCMSes[k]; !ok {
return fmt.Errorf("missing proposer MCMS for chain %d", k)
}
}
return nil
}

// TransferLinkTokenProposal transfers link token to the to address on the chain identified by the chainSelector with proposal.
func TransferLinkTokenProposal(e deployment.Environment, config TransferLinkTokenProposalConfig) (deployment.ChangesetOutput, error) {
if err := config.Validate(); err != nil {
return deployment.ChangesetOutput{}, err
}

var batches []timelock.BatchChainOperation
for chainSelector, transferConfig := range config.Transfers {
addresses, err := e.ExistingAddresses.AddressesForChain(chainSelector)
if err != nil {
return deployment.ChangesetOutput{}, err
}

for address, typeversion := range addresses {
if typeversion.Type == types.LinkToken && typeversion.Version == deployment.Version1_0_0 {
chain, ok := e.Chains[chainSelector]
if !ok {
return deployment.ChangesetOutput{}, fmt.Errorf("chain not found in environment")
}
contract, err := link_token.NewLinkToken(common.HexToAddress(address), chain.Client)
if err != nil {
return deployment.ChangesetOutput{}, err
}

tx, err := contract.Transfer(deployment.SimTransactOpts(), transferConfig.To, transferConfig.Amount)
e.Logger.Infow("Setting up proposal to transfer LINK",
"to", transferConfig.To,
"amount", transferConfig.Amount,
"txHash", tx.Hash().Hex(),
"chainSelector", chainSelector)

batches = append(batches, timelock.BatchChainOperation{
ChainIdentifier: mcms.ChainIdentifier(chainSelector),
Batch: []mcms.Operation{{
To: contract.Address(),
Data: tx.Data(),
Value: big.NewInt(0),
}},
})
break
}
}
}

if len(batches) == 0 {
return deployment.ChangesetOutput{}, errors.New("no link token contract found")
}

proposal, err := proposalutils.BuildProposalFromBatches(
config.OwnersPerChain,
config.ProposerMCMSes,
batches,
"Transfer LINK",
config.MinDelay,
)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal from batch: %w, batches: %+v", err, batches)
}
return deployment.ChangesetOutput{
Proposals: []timelock.MCMSWithTimelockProposal{*proposal},
}, nil
}
Loading

0 comments on commit fb4e1e0

Please sign in to comment.