github.com/MetalBlockchain/metalgo@v1.11.9/tests/e2e/c/interchain_workflow.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package c
     5  
     6  import (
     7  	"math/big"
     8  
     9  	"github.com/MetalBlockchain/coreth/core/types"
    10  	"github.com/MetalBlockchain/coreth/plugin/evm"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/MetalBlockchain/metalgo/ids"
    14  	"github.com/MetalBlockchain/metalgo/tests/fixture/e2e"
    15  	"github.com/MetalBlockchain/metalgo/utils/constants"
    16  	"github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1"
    17  	"github.com/MetalBlockchain/metalgo/utils/set"
    18  	"github.com/MetalBlockchain/metalgo/utils/units"
    19  	"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
    20  	"github.com/MetalBlockchain/metalgo/wallet/subnet/primary/common"
    21  
    22  	ginkgo "github.com/onsi/ginkgo/v2"
    23  )
    24  
    25  var _ = e2e.DescribeCChain("[Interchain Workflow]", func() {
    26  	require := require.New(ginkgo.GinkgoT())
    27  
    28  	const txAmount = 10 * units.Avax // Arbitrary amount to send and transfer
    29  
    30  	ginkgo.It("should ensure that funds can be transferred from the C-Chain to the X-Chain and the P-Chain", func() {
    31  		ginkgo.By("initializing a new eth client")
    32  		// Select a random node URI to use for both the eth client and
    33  		// the wallet to avoid having to verify that all nodes are at
    34  		// the same height before initializing the wallet.
    35  		nodeURI := e2e.Env.GetRandomNodeURI()
    36  		ethClient := e2e.NewEthClient(nodeURI)
    37  
    38  		ginkgo.By("allocating a pre-funded key to send from and a recipient key to deliver to")
    39  		senderKey := e2e.Env.AllocatePreFundedKey()
    40  		senderEthAddress := evm.GetEthAddress(senderKey)
    41  		recipientKey, err := secp256k1.NewPrivateKey()
    42  		require.NoError(err)
    43  		recipientEthAddress := evm.GetEthAddress(recipientKey)
    44  
    45  		ginkgo.By("sending funds from one address to another on the C-Chain", func() {
    46  			// Create transaction
    47  			acceptedNonce, err := ethClient.AcceptedNonceAt(e2e.DefaultContext(), senderEthAddress)
    48  			require.NoError(err)
    49  			gasPrice := e2e.SuggestGasPrice(ethClient)
    50  			tx := types.NewTransaction(
    51  				acceptedNonce,
    52  				recipientEthAddress,
    53  				big.NewInt(int64(txAmount)),
    54  				e2e.DefaultGasLimit,
    55  				gasPrice,
    56  				nil,
    57  			)
    58  
    59  			// Sign transaction
    60  			cChainID, err := ethClient.ChainID(e2e.DefaultContext())
    61  			require.NoError(err)
    62  			signer := types.NewEIP155Signer(cChainID)
    63  			signedTx, err := types.SignTx(tx, signer, senderKey.ToECDSA())
    64  			require.NoError(err)
    65  
    66  			_ = e2e.SendEthTransaction(ethClient, signedTx)
    67  
    68  			ginkgo.By("waiting for the C-Chain recipient address to have received the sent funds")
    69  			e2e.Eventually(func() bool {
    70  				balance, err := ethClient.BalanceAt(e2e.DefaultContext(), recipientEthAddress, nil)
    71  				require.NoError(err)
    72  				return balance.Cmp(big.NewInt(0)) > 0
    73  			}, e2e.DefaultTimeout, e2e.DefaultPollingInterval, "failed to see funds delivered before timeout")
    74  		})
    75  
    76  		// Wallet must be initialized after sending funds on the
    77  		// C-Chain with the same node URI to ensure wallet state
    78  		// matches on-chain state.
    79  		ginkgo.By("initializing a keychain and associated wallet")
    80  		keychain := secp256k1fx.NewKeychain(senderKey, recipientKey)
    81  		baseWallet := e2e.NewWallet(keychain, nodeURI)
    82  		xWallet := baseWallet.X()
    83  		cWallet := baseWallet.C()
    84  		pWallet := baseWallet.P()
    85  
    86  		ginkgo.By("defining common configuration")
    87  		xBuilder := xWallet.Builder()
    88  		xContext := xBuilder.Context()
    89  		cBuilder := cWallet.Builder()
    90  		cContext := cBuilder.Context()
    91  		avaxAssetID := xContext.AVAXAssetID
    92  		// Use the same owner for import funds to X-Chain and P-Chain
    93  		recipientOwner := secp256k1fx.OutputOwners{
    94  			Threshold: 1,
    95  			Addrs: []ids.ShortID{
    96  				recipientKey.Address(),
    97  			},
    98  		}
    99  		// Use the same outputs for both X-Chain and P-Chain exports
   100  		exportOutputs := []*secp256k1fx.TransferOutput{
   101  			{
   102  				Amt: txAmount,
   103  				OutputOwners: secp256k1fx.OutputOwners{
   104  					Threshold: 1,
   105  					Addrs: []ids.ShortID{
   106  						keychain.Keys[0].Address(),
   107  					},
   108  				},
   109  			},
   110  		}
   111  
   112  		ginkgo.By("exporting AVAX from the C-Chain to the X-Chain", func() {
   113  			_, err := cWallet.IssueExportTx(
   114  				xContext.BlockchainID,
   115  				exportOutputs,
   116  				e2e.WithDefaultContext(),
   117  				e2e.WithSuggestedGasPrice(ethClient),
   118  			)
   119  			require.NoError(err)
   120  		})
   121  
   122  		ginkgo.By("importing AVAX from the C-Chain to the X-Chain", func() {
   123  			_, err := xWallet.IssueImportTx(
   124  				cContext.BlockchainID,
   125  				&recipientOwner,
   126  				e2e.WithDefaultContext(),
   127  			)
   128  			require.NoError(err)
   129  		})
   130  
   131  		ginkgo.By("checking that the recipient address has received imported funds on the X-Chain", func() {
   132  			balances, err := xWallet.Builder().GetFTBalance(common.WithCustomAddresses(set.Of(
   133  				recipientKey.Address(),
   134  			)))
   135  			require.NoError(err)
   136  			require.Positive(balances[avaxAssetID])
   137  		})
   138  
   139  		ginkgo.By("exporting AVAX from the C-Chain to the P-Chain", func() {
   140  			_, err := cWallet.IssueExportTx(
   141  				constants.PlatformChainID,
   142  				exportOutputs,
   143  				e2e.WithDefaultContext(),
   144  				e2e.WithSuggestedGasPrice(ethClient),
   145  			)
   146  			require.NoError(err)
   147  		})
   148  
   149  		ginkgo.By("importing AVAX from the C-Chain to the P-Chain", func() {
   150  			_, err = pWallet.IssueImportTx(
   151  				cContext.BlockchainID,
   152  				&recipientOwner,
   153  				e2e.WithDefaultContext(),
   154  			)
   155  			require.NoError(err)
   156  		})
   157  
   158  		ginkgo.By("checking that the recipient address has received imported funds on the P-Chain", func() {
   159  			balances, err := pWallet.Builder().GetBalance(common.WithCustomAddresses(set.Of(
   160  				recipientKey.Address(),
   161  			)))
   162  			require.NoError(err)
   163  			require.Positive(balances[avaxAssetID])
   164  		})
   165  
   166  		e2e.CheckBootstrapIsPossible(e2e.Env.GetNetwork())
   167  	})
   168  })