Skip to content

Commit

Permalink
feat(deployment): add link token transfer changeset
Browse files Browse the repository at this point in the history
Added a new changeset in shared package which transfer the link token.

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

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

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

"github.com/smartcontractkit/chainlink/deployment"
)

var _ deployment.ChangeSet[TransferLinkTokenBatchConfig] = TransferLinkTokenBatch

type TokenTransferer interface {
Transfer(opts *bind.TransactOpts, to common.Address, amount *big.Int) (*gethtypes.Transaction, error)
}

type TransferLinkTokenConfig struct {
To common.Address
Amount *big.Int
Transferer TokenTransferer
}

type TransferLinkTokenBatchConfig struct {
Transfers map[uint64]TransferLinkTokenConfig
}

func (cfg TransferLinkTokenBatchConfig) Validate() error {
for k, c := range cfg.Transfers {
if err := deployment.IsValidChainSelector(k); err != nil {
return err
}
if c.To == (common.Address{}) {
return errors.New("to address must be set")
}
if c.Amount == nil || c.Amount.Sign() == -1 {
return errors.New("amount must be set and positive")
}
if c.Transferer == nil {
return errors.New("transferer must be set")
}
}

return nil
}

// TransferLinkTokenBatch transfers link token to the to address from the transferer in a batch.
func TransferLinkTokenBatch(e deployment.Environment, config TransferLinkTokenBatchConfig) (deployment.ChangesetOutput, error) {
if err := config.Validate(); err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("invalid transfer link token config: %v", err)
}

for chainSelector, transferConfig := range config.Transfers {
chain, ok := e.Chains[chainSelector]
if !ok {
return deployment.ChangesetOutput{}, fmt.Errorf("chain not found in environment")
}
tx, err := transferConfig.Transferer.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)
}
}
return deployment.ChangesetOutput{}, nil
}
179 changes: 179 additions & 0 deletions deployment/common/changeset/transfer_link_token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package changeset_test

import (
"math/big"
"testing"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
gethtypes "github.com/ethereum/go-ethereum/core/types"
chainselectors "github.com/smartcontractkit/chain-selectors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"

"github.com/smartcontractkit/chainlink-common/pkg/logger"

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

func TestTransferLinkToken_Validate(t *testing.T) {
t.Parallel()

tests := []struct {
name string
config changeset.TransferLinkTokenBatchConfig
wantErr bool
wantErrMsg string
}{
{
name: "valid config",
config: changeset.TransferLinkTokenBatchConfig{
Transfers: map[uint64]changeset.TransferLinkTokenConfig{
chainselectors.ETHEREUM_TESTNET_SEPOLIA.Selector: {
To: common.HexToAddress("0x1"),
Amount: big.NewInt(1),
Transferer: mockTokenTransferer{},
},
},
},
wantErr: false,
},
{
name: "missing to address",
config: changeset.TransferLinkTokenBatchConfig{
Transfers: map[uint64]changeset.TransferLinkTokenConfig{
chainselectors.ETHEREUM_TESTNET_SEPOLIA.Selector: {
Amount: big.NewInt(1),
Transferer: mockTokenTransferer{},
},
},
},
wantErr: true,
wantErrMsg: "to address must be set",
},
{
name: "missing amount",
config: changeset.TransferLinkTokenBatchConfig{
Transfers: map[uint64]changeset.TransferLinkTokenConfig{
chainselectors.ETHEREUM_TESTNET_SEPOLIA.Selector: {
To: common.HexToAddress("0x1"),
Transferer: mockTokenTransferer{},
},
},
},
wantErr: true,
wantErrMsg: "amount must be set and positive",
},
{
name: "missing transferer",
config: changeset.TransferLinkTokenBatchConfig{
Transfers: map[uint64]changeset.TransferLinkTokenConfig{
chainselectors.ETHEREUM_TESTNET_SEPOLIA.Selector: {
To: common.HexToAddress("0x1"),
Amount: big.NewInt(1),
Transferer: nil,
},
},
},
wantErr: true,
wantErrMsg: "transferer must be set",
},
{
name: "invalid chain selector",
config: changeset.TransferLinkTokenBatchConfig{
Transfers: map[uint64]changeset.TransferLinkTokenConfig{
0: {
To: common.HexToAddress("0x1"),
Amount: big.NewInt(1),
Transferer: mockTokenTransferer{},
},
},
},
wantErr: true,
wantErrMsg: "chain selector must be set",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()

err := test.config.Validate()
if test.wantErr {
if test.wantErrMsg != "" {
assert.Contains(t, err.Error(), test.wantErrMsg)
}
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}

func TestTransferLinkToken(t *testing.T) {
t.Parallel()

lggr := logger.Test(t)
cfg := memory.MemoryEnvironmentConfig{
Nodes: 1,
Chains: 1,
}
env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg)
chainSelectorId := env.AllChainSelectors()[0]

chain := env.Chains[chainSelectorId]
deployer := chain.DeployerKey
tokenContract, err := deployment.DeployContract(lggr, chain, env.ExistingAddresses,
func(chain deployment.Chain) deployment.ContractDeploy[*link_token.LinkToken] {
tokenAddress, tx, token, err2 := link_token.DeployLinkToken(
deployer,
chain.Client,
)
return deployment.ContractDeploy[*link_token.LinkToken]{
tokenAddress, token, tx, deployment.NewTypeAndVersion(types.LinkToken, deployment.Version1_0_0), err2,
}
})
require.NoError(t, err)

tx, err := tokenContract.Contract.GrantMintRole(deployer, deployer.From)
require.NoError(t, err)
_, err = chain.Confirm(tx)

tx, err = tokenContract.Contract.Mint(deployer, deployer.From, big.NewInt(100))
require.NoError(t, err)
_, err = chain.Confirm(tx)
require.NoError(t, err)

receiver := common.HexToAddress("0x1")
_, err = changeset.TransferLinkTokenBatch(env, changeset.TransferLinkTokenBatchConfig{
Transfers: map[uint64]changeset.TransferLinkTokenConfig{
chainSelectorId: {
To: receiver,
Amount: big.NewInt(30),
Transferer: tokenContract.Contract,
},
},
})
require.NoError(t, err)

balance, err := tokenContract.Contract.BalanceOf(nil, deployer.From)
require.NoError(t, err)
require.Equal(t, big.NewInt(70), balance)

balance, err = tokenContract.Contract.BalanceOf(nil, receiver)
require.NoError(t, err)
require.Equal(t, big.NewInt(30), balance)
}

type mockTokenTransferer struct{}

func (m mockTokenTransferer) Transfer(opts *bind.TransactOpts,
to common.Address, amount *big.Int) (*gethtypes.Transaction, error) {
return nil, nil
}

0 comments on commit fe15cc5

Please sign in to comment.