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 }