-
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.
feat(deployment): add link token transfer changeset
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
1 parent
07bd33c
commit fb4e1e0
Showing
2 changed files
with
448 additions
and
0 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,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 | ||
} |
Oops, something went wrong.