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 }