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 })