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