github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/txs/executor/import_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package executor
     5  
     6  import (
     7  	"math/rand"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/ava-labs/avalanchego/chains/atomic"
    14  	"github.com/ava-labs/avalanchego/database/prefixdb"
    15  	"github.com/ava-labs/avalanchego/ids"
    16  	"github.com/ava-labs/avalanchego/upgrade/upgradetest"
    17  	"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
    18  	"github.com/ava-labs/avalanchego/vms/components/avax"
    19  	"github.com/ava-labs/avalanchego/vms/platformvm/state"
    20  	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
    21  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    22  	"github.com/ava-labs/avalanchego/wallet/chain/p/builder"
    23  )
    24  
    25  var fundedSharedMemoryCalls byte
    26  
    27  func TestNewImportTx(t *testing.T) {
    28  	env := newEnvironment(t, upgradetest.ApricotPhase5)
    29  
    30  	type test struct {
    31  		description   string
    32  		sourceChainID ids.ID
    33  		sharedMemory  atomic.SharedMemory
    34  		timestamp     time.Time
    35  		expectedErr   error
    36  	}
    37  
    38  	sourceKey, err := secp256k1.NewPrivateKey()
    39  	require.NoError(t, err)
    40  
    41  	customAssetID := ids.GenerateTestID()
    42  	// generate a constant random source generator.
    43  	randSrc := rand.NewSource(0)
    44  	tests := []test{
    45  		{
    46  			description:   "can't pay fee",
    47  			sourceChainID: env.ctx.XChainID,
    48  			sharedMemory: fundedSharedMemory(
    49  				t,
    50  				env,
    51  				sourceKey,
    52  				env.ctx.XChainID,
    53  				map[ids.ID]uint64{
    54  					env.ctx.AVAXAssetID: env.config.StaticFeeConfig.TxFee - 1,
    55  				},
    56  				randSrc,
    57  			),
    58  			expectedErr: builder.ErrInsufficientFunds,
    59  		},
    60  		{
    61  			description:   "can barely pay fee",
    62  			sourceChainID: env.ctx.XChainID,
    63  			sharedMemory: fundedSharedMemory(
    64  				t,
    65  				env,
    66  				sourceKey,
    67  				env.ctx.XChainID,
    68  				map[ids.ID]uint64{
    69  					env.ctx.AVAXAssetID: env.config.StaticFeeConfig.TxFee,
    70  				},
    71  				randSrc,
    72  			),
    73  			expectedErr: nil,
    74  		},
    75  		{
    76  			description:   "attempting to import from C-chain",
    77  			sourceChainID: env.ctx.CChainID,
    78  			sharedMemory: fundedSharedMemory(
    79  				t,
    80  				env,
    81  				sourceKey,
    82  				env.ctx.CChainID,
    83  				map[ids.ID]uint64{
    84  					env.ctx.AVAXAssetID: env.config.StaticFeeConfig.TxFee,
    85  				},
    86  				randSrc,
    87  			),
    88  			timestamp:   env.config.UpgradeConfig.ApricotPhase5Time,
    89  			expectedErr: nil,
    90  		},
    91  		{
    92  			description:   "attempting to import non-avax from X-chain",
    93  			sourceChainID: env.ctx.XChainID,
    94  			sharedMemory: fundedSharedMemory(
    95  				t,
    96  				env,
    97  				sourceKey,
    98  				env.ctx.XChainID,
    99  				map[ids.ID]uint64{
   100  					env.ctx.AVAXAssetID: env.config.StaticFeeConfig.TxFee,
   101  					customAssetID:       1,
   102  				},
   103  				randSrc,
   104  			),
   105  			timestamp:   env.config.UpgradeConfig.ApricotPhase5Time,
   106  			expectedErr: nil,
   107  		},
   108  	}
   109  
   110  	to := &secp256k1fx.OutputOwners{
   111  		Threshold: 1,
   112  		Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
   113  	}
   114  	for _, tt := range tests {
   115  		t.Run(tt.description, func(t *testing.T) {
   116  			require := require.New(t)
   117  
   118  			env.msm.SharedMemory = tt.sharedMemory
   119  
   120  			wallet := newWallet(t, env, walletConfig{
   121  				keys:     []*secp256k1.PrivateKey{sourceKey},
   122  				chainIDs: []ids.ID{tt.sourceChainID},
   123  			})
   124  
   125  			tx, err := wallet.IssueImportTx(
   126  				tt.sourceChainID,
   127  				to,
   128  			)
   129  			require.ErrorIs(err, tt.expectedErr)
   130  			if tt.expectedErr != nil {
   131  				return
   132  			}
   133  
   134  			unsignedTx := tx.Unsigned.(*txs.ImportTx)
   135  			require.NotEmpty(unsignedTx.ImportedInputs)
   136  			numInputs := len(unsignedTx.Ins) + len(unsignedTx.ImportedInputs)
   137  			require.Equal(len(tx.Creds), numInputs, "should have the same number of credentials as inputs")
   138  
   139  			totalIn := uint64(0)
   140  			for _, in := range unsignedTx.Ins {
   141  				totalIn += in.Input().Amount()
   142  			}
   143  			for _, in := range unsignedTx.ImportedInputs {
   144  				totalIn += in.Input().Amount()
   145  			}
   146  			totalOut := uint64(0)
   147  			for _, out := range unsignedTx.Outs {
   148  				totalOut += out.Out.Amount()
   149  			}
   150  
   151  			require.Equal(env.config.StaticFeeConfig.TxFee, totalIn-totalOut)
   152  
   153  			stateDiff, err := state.NewDiff(lastAcceptedID, env)
   154  			require.NoError(err)
   155  
   156  			stateDiff.SetTimestamp(tt.timestamp)
   157  
   158  			feeCalculator := state.PickFeeCalculator(env.config, stateDiff)
   159  			verifier := StandardTxExecutor{
   160  				Backend:       &env.backend,
   161  				FeeCalculator: feeCalculator,
   162  				State:         stateDiff,
   163  				Tx:            tx,
   164  			}
   165  			require.NoError(tx.Unsigned.Visit(&verifier))
   166  		})
   167  	}
   168  }
   169  
   170  // Returns a shared memory where GetDatabase returns a database
   171  // where [recipientKey] has a balance of [amt]
   172  func fundedSharedMemory(
   173  	t *testing.T,
   174  	env *environment,
   175  	sourceKey *secp256k1.PrivateKey,
   176  	peerChain ids.ID,
   177  	assets map[ids.ID]uint64,
   178  	randSrc rand.Source,
   179  ) atomic.SharedMemory {
   180  	fundedSharedMemoryCalls++
   181  	m := atomic.NewMemory(prefixdb.New([]byte{fundedSharedMemoryCalls}, env.baseDB))
   182  
   183  	sm := m.NewSharedMemory(env.ctx.ChainID)
   184  	peerSharedMemory := m.NewSharedMemory(peerChain)
   185  
   186  	for assetID, amt := range assets {
   187  		utxo := &avax.UTXO{
   188  			UTXOID: avax.UTXOID{
   189  				TxID:        ids.GenerateTestID(),
   190  				OutputIndex: uint32(randSrc.Int63()),
   191  			},
   192  			Asset: avax.Asset{ID: assetID},
   193  			Out: &secp256k1fx.TransferOutput{
   194  				Amt: amt,
   195  				OutputOwners: secp256k1fx.OutputOwners{
   196  					Locktime:  0,
   197  					Addrs:     []ids.ShortID{sourceKey.Address()},
   198  					Threshold: 1,
   199  				},
   200  			},
   201  		}
   202  		utxoBytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo)
   203  		require.NoError(t, err)
   204  
   205  		inputID := utxo.InputID()
   206  		require.NoError(t, peerSharedMemory.Apply(map[ids.ID]*atomic.Requests{
   207  			env.ctx.ChainID: {
   208  				PutRequests: []*atomic.Element{
   209  					{
   210  						Key:   inputID[:],
   211  						Value: utxoBytes,
   212  						Traits: [][]byte{
   213  							sourceKey.Address().Bytes(),
   214  						},
   215  					},
   216  				},
   217  			},
   218  		}))
   219  	}
   220  
   221  	return sm
   222  }