github.com/MetalBlockchain/metalgo@v1.11.9/wallet/chain/x/builder_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 x 5 6 import ( 7 "testing" 8 9 "github.com/stretchr/testify/require" 10 11 "github.com/MetalBlockchain/metalgo/ids" 12 "github.com/MetalBlockchain/metalgo/utils/constants" 13 "github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1" 14 "github.com/MetalBlockchain/metalgo/utils/set" 15 "github.com/MetalBlockchain/metalgo/utils/units" 16 "github.com/MetalBlockchain/metalgo/vms/components/avax" 17 "github.com/MetalBlockchain/metalgo/vms/components/verify" 18 "github.com/MetalBlockchain/metalgo/vms/nftfx" 19 "github.com/MetalBlockchain/metalgo/vms/propertyfx" 20 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 21 "github.com/MetalBlockchain/metalgo/wallet/chain/x/builder" 22 "github.com/MetalBlockchain/metalgo/wallet/subnet/primary/common" 23 ) 24 25 var ( 26 testKeys = secp256k1.TestKeys() 27 28 // We hard-code [avaxAssetID] and [subnetAssetID] to make 29 // ordering of UTXOs generated by [testUTXOsList] is reproducible 30 avaxAssetID = ids.Empty.Prefix(1789) 31 xChainID = ids.Empty.Prefix(2021) 32 nftAssetID = ids.Empty.Prefix(2022) 33 propertyAssetID = ids.Empty.Prefix(2023) 34 35 testContext = &builder.Context{ 36 NetworkID: constants.UnitTestID, 37 BlockchainID: xChainID, 38 AVAXAssetID: avaxAssetID, 39 BaseTxFee: units.MicroAvax, 40 CreateAssetTxFee: 99 * units.MilliAvax, 41 } 42 ) 43 44 // These tests create and sign a tx, then verify that utxos included 45 // in the tx are exactly necessary to pay fees for it 46 47 func TestBaseTx(t *testing.T) { 48 var ( 49 require = require.New(t) 50 51 // backend 52 utxosKey = testKeys[1] 53 utxos = makeTestUTXOs(utxosKey) 54 genericBackend = common.NewDeterministicChainUTXOs( 55 require, 56 map[ids.ID][]*avax.UTXO{ 57 xChainID: utxos, 58 }, 59 ) 60 backend = NewBackend(testContext, genericBackend) 61 62 // builder 63 utxoAddr = utxosKey.Address() 64 builder = builder.New(set.Of(utxoAddr), testContext, backend) 65 66 // data to build the transaction 67 outputsToMove = []*avax.TransferableOutput{{ 68 Asset: avax.Asset{ID: avaxAssetID}, 69 Out: &secp256k1fx.TransferOutput{ 70 Amt: 7 * units.Avax, 71 OutputOwners: secp256k1fx.OutputOwners{ 72 Threshold: 1, 73 Addrs: []ids.ShortID{utxoAddr}, 74 }, 75 }, 76 }} 77 ) 78 79 utx, err := builder.NewBaseTx( 80 outputsToMove, 81 ) 82 require.NoError(err) 83 84 // check UTXOs selection and fee financing 85 ins := utx.Ins 86 outs := utx.Outs 87 require.Len(ins, 2) 88 require.Len(outs, 2) 89 90 expectedConsumed := testContext.BaseTxFee 91 consumed := ins[0].In.Amount() + ins[1].In.Amount() - outs[0].Out.Amount() - outs[1].Out.Amount() 92 require.Equal(expectedConsumed, consumed) 93 require.Equal(outputsToMove[0], outs[1]) 94 } 95 96 func TestCreateAssetTx(t *testing.T) { 97 require := require.New(t) 98 99 var ( 100 // backend 101 utxosKey = testKeys[1] 102 utxos = makeTestUTXOs(utxosKey) 103 genericBackend = common.NewDeterministicChainUTXOs( 104 require, 105 map[ids.ID][]*avax.UTXO{ 106 xChainID: utxos, 107 }, 108 ) 109 backend = NewBackend(testContext, genericBackend) 110 111 // builder 112 utxoAddr = utxosKey.Address() 113 builder = builder.New(set.Of(utxoAddr), testContext, backend) 114 115 // data to build the transaction 116 assetName = "Team Rocket" 117 symbol = "TR" 118 denomination uint8 = 0 119 initialState = map[uint32][]verify.State{ 120 0: { 121 &secp256k1fx.MintOutput{ 122 OutputOwners: secp256k1fx.OutputOwners{ 123 Threshold: 1, 124 Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, 125 }, 126 }, &secp256k1fx.MintOutput{ 127 OutputOwners: secp256k1fx.OutputOwners{ 128 Threshold: 1, 129 Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, 130 }, 131 }, 132 }, 133 1: { 134 &nftfx.MintOutput{ 135 GroupID: 1, 136 OutputOwners: secp256k1fx.OutputOwners{ 137 Threshold: 1, 138 Addrs: []ids.ShortID{testKeys[1].PublicKey().Address()}, 139 }, 140 }, 141 &nftfx.MintOutput{ 142 GroupID: 2, 143 OutputOwners: secp256k1fx.OutputOwners{ 144 Threshold: 1, 145 Addrs: []ids.ShortID{testKeys[1].PublicKey().Address()}, 146 }, 147 }, 148 }, 149 2: { 150 &propertyfx.MintOutput{ 151 OutputOwners: secp256k1fx.OutputOwners{ 152 Threshold: 1, 153 Addrs: []ids.ShortID{testKeys[2].PublicKey().Address()}, 154 }, 155 }, 156 &propertyfx.MintOutput{ 157 OutputOwners: secp256k1fx.OutputOwners{ 158 Threshold: 1, 159 Addrs: []ids.ShortID{testKeys[2].PublicKey().Address()}, 160 }, 161 }, 162 }, 163 } 164 ) 165 166 utx, err := builder.NewCreateAssetTx( 167 assetName, 168 symbol, 169 denomination, 170 initialState, 171 ) 172 require.NoError(err) 173 174 // check UTXOs selection and fee financing 175 ins := utx.Ins 176 outs := utx.Outs 177 require.Len(ins, 2) 178 require.Len(outs, 1) 179 180 expectedConsumed := testContext.CreateAssetTxFee 181 consumed := ins[0].In.Amount() + ins[1].In.Amount() - outs[0].Out.Amount() 182 require.Equal(expectedConsumed, consumed) 183 } 184 185 func TestMintNFTOperation(t *testing.T) { 186 require := require.New(t) 187 188 var ( 189 // backend 190 utxosKey = testKeys[1] 191 utxos = makeTestUTXOs(utxosKey) 192 genericBackend = common.NewDeterministicChainUTXOs( 193 require, 194 map[ids.ID][]*avax.UTXO{ 195 xChainID: utxos, 196 }, 197 ) 198 backend = NewBackend(testContext, genericBackend) 199 200 // builder 201 utxoAddr = utxosKey.Address() 202 builder = builder.New(set.Of(utxoAddr), testContext, backend) 203 204 // data to build the transaction 205 payload = []byte{'h', 'e', 'l', 'l', 'o'} 206 NFTOwner = &secp256k1fx.OutputOwners{ 207 Threshold: 1, 208 Addrs: []ids.ShortID{utxoAddr}, 209 } 210 ) 211 212 utx, err := builder.NewOperationTxMintNFT( 213 nftAssetID, 214 payload, 215 []*secp256k1fx.OutputOwners{NFTOwner}, 216 ) 217 require.NoError(err) 218 219 // check UTXOs selection and fee financing 220 ins := utx.Ins 221 outs := utx.Outs 222 require.Len(ins, 1) 223 require.Len(outs, 1) 224 225 expectedConsumed := testContext.BaseTxFee 226 consumed := ins[0].In.Amount() - outs[0].Out.Amount() 227 require.Equal(expectedConsumed, consumed) 228 } 229 230 func TestMintFTOperation(t *testing.T) { 231 require := require.New(t) 232 233 var ( 234 // backend 235 utxosKey = testKeys[1] 236 utxos = makeTestUTXOs(utxosKey) 237 genericBackend = common.NewDeterministicChainUTXOs( 238 require, 239 map[ids.ID][]*avax.UTXO{ 240 xChainID: utxos, 241 }, 242 ) 243 backend = NewBackend(testContext, genericBackend) 244 245 // builder 246 utxoAddr = utxosKey.Address() 247 builder = builder.New(set.Of(utxoAddr), testContext, backend) 248 249 // data to build the transaction 250 outputs = map[ids.ID]*secp256k1fx.TransferOutput{ 251 nftAssetID: { 252 Amt: 1, 253 OutputOwners: secp256k1fx.OutputOwners{ 254 Threshold: 1, 255 Addrs: []ids.ShortID{utxoAddr}, 256 }, 257 }, 258 } 259 ) 260 261 utx, err := builder.NewOperationTxMintFT( 262 outputs, 263 ) 264 require.NoError(err) 265 266 // check UTXOs selection and fee financing 267 ins := utx.Ins 268 outs := utx.Outs 269 require.Len(ins, 1) 270 require.Len(outs, 1) 271 272 expectedConsumed := testContext.BaseTxFee 273 consumed := ins[0].In.Amount() - outs[0].Out.Amount() 274 require.Equal(expectedConsumed, consumed) 275 } 276 277 func TestMintPropertyOperation(t *testing.T) { 278 require := require.New(t) 279 280 var ( 281 // backend 282 utxosKey = testKeys[1] 283 utxos = makeTestUTXOs(utxosKey) 284 genericBackend = common.NewDeterministicChainUTXOs( 285 require, 286 map[ids.ID][]*avax.UTXO{ 287 xChainID: utxos, 288 }, 289 ) 290 backend = NewBackend(testContext, genericBackend) 291 292 // builder 293 utxoAddr = utxosKey.Address() 294 builder = builder.New(set.Of(utxoAddr), testContext, backend) 295 296 // data to build the transaction 297 propertyOwner = &secp256k1fx.OutputOwners{ 298 Threshold: 1, 299 Addrs: []ids.ShortID{utxoAddr}, 300 } 301 ) 302 303 utx, err := builder.NewOperationTxMintProperty( 304 propertyAssetID, 305 propertyOwner, 306 ) 307 require.NoError(err) 308 309 // check UTXOs selection and fee financing 310 ins := utx.Ins 311 outs := utx.Outs 312 require.Len(ins, 1) 313 require.Len(outs, 1) 314 315 expectedConsumed := testContext.BaseTxFee 316 consumed := ins[0].In.Amount() - outs[0].Out.Amount() 317 require.Equal(expectedConsumed, consumed) 318 } 319 320 func TestBurnPropertyOperation(t *testing.T) { 321 require := require.New(t) 322 323 var ( 324 // backend 325 utxosKey = testKeys[1] 326 utxos = makeTestUTXOs(utxosKey) 327 genericBackend = common.NewDeterministicChainUTXOs( 328 require, 329 map[ids.ID][]*avax.UTXO{ 330 xChainID: utxos, 331 }, 332 ) 333 backend = NewBackend(testContext, genericBackend) 334 335 // builder 336 utxoAddr = utxosKey.Address() 337 builder = builder.New(set.Of(utxoAddr), testContext, backend) 338 ) 339 340 utx, err := builder.NewOperationTxBurnProperty( 341 propertyAssetID, 342 ) 343 require.NoError(err) 344 345 // check UTXOs selection and fee financing 346 ins := utx.Ins 347 outs := utx.Outs 348 require.Len(ins, 1) 349 require.Len(outs, 1) 350 351 expectedConsumed := testContext.BaseTxFee 352 consumed := ins[0].In.Amount() - outs[0].Out.Amount() 353 require.Equal(expectedConsumed, consumed) 354 } 355 356 func TestImportTx(t *testing.T) { 357 var ( 358 require = require.New(t) 359 360 // backend 361 utxosKey = testKeys[1] 362 utxos = makeTestUTXOs(utxosKey) 363 sourceChainID = ids.GenerateTestID() 364 importedUTXOs = utxos[:1] 365 genericBackend = common.NewDeterministicChainUTXOs( 366 require, 367 map[ids.ID][]*avax.UTXO{ 368 xChainID: utxos, 369 sourceChainID: importedUTXOs, 370 }, 371 ) 372 373 backend = NewBackend(testContext, genericBackend) 374 375 // builder 376 utxoAddr = utxosKey.Address() 377 builder = builder.New(set.Of(utxoAddr), testContext, backend) 378 379 // data to build the transaction 380 importKey = testKeys[0] 381 importTo = &secp256k1fx.OutputOwners{ 382 Threshold: 1, 383 Addrs: []ids.ShortID{ 384 importKey.Address(), 385 }, 386 } 387 ) 388 389 utx, err := builder.NewImportTx( 390 sourceChainID, 391 importTo, 392 ) 393 require.NoError(err) 394 395 // check UTXOs selection and fee financing 396 ins := utx.Ins 397 outs := utx.Outs 398 importedIns := utx.ImportedIns 399 require.Empty(ins) 400 require.Len(importedIns, 1) 401 require.Len(outs, 1) 402 403 expectedConsumed := testContext.BaseTxFee 404 consumed := importedIns[0].In.Amount() - outs[0].Out.Amount() 405 require.Equal(expectedConsumed, consumed) 406 } 407 408 func TestExportTx(t *testing.T) { 409 var ( 410 require = require.New(t) 411 412 // backend 413 utxosKey = testKeys[1] 414 utxos = makeTestUTXOs(utxosKey) 415 genericBackend = common.NewDeterministicChainUTXOs( 416 require, 417 map[ids.ID][]*avax.UTXO{ 418 xChainID: utxos, 419 }, 420 ) 421 backend = NewBackend(testContext, genericBackend) 422 423 // builder 424 utxoAddr = utxosKey.Address() 425 builder = builder.New(set.Of(utxoAddr), testContext, backend) 426 427 // data to build the transaction 428 subnetID = ids.GenerateTestID() 429 exportedOutputs = []*avax.TransferableOutput{{ 430 Asset: avax.Asset{ID: avaxAssetID}, 431 Out: &secp256k1fx.TransferOutput{ 432 Amt: 7 * units.Avax, 433 OutputOwners: secp256k1fx.OutputOwners{ 434 Threshold: 1, 435 Addrs: []ids.ShortID{utxoAddr}, 436 }, 437 }, 438 }} 439 ) 440 441 utx, err := builder.NewExportTx( 442 subnetID, 443 exportedOutputs, 444 ) 445 require.NoError(err) 446 447 // check UTXOs selection and fee financing 448 ins := utx.Ins 449 outs := utx.Outs 450 require.Len(ins, 2) 451 require.Len(outs, 1) 452 453 expectedConsumed := testContext.BaseTxFee + exportedOutputs[0].Out.Amount() 454 consumed := ins[0].In.Amount() + ins[1].In.Amount() - outs[0].Out.Amount() 455 require.Equal(expectedConsumed, consumed) 456 require.Equal(utx.ExportedOuts, exportedOutputs) 457 } 458 459 func makeTestUTXOs(utxosKey *secp256k1.PrivateKey) []*avax.UTXO { 460 // Note: we avoid ids.GenerateTestNodeID here to make sure that UTXO IDs won't change 461 // run by run. This simplifies checking what utxos are included in the built txs. 462 const utxosOffset uint64 = 2024 463 464 return []*avax.UTXO{ // currently, the wallet scans UTXOs in the order provided here 465 { // a small UTXO first, which should not be enough to pay fees 466 UTXOID: avax.UTXOID{ 467 TxID: ids.Empty.Prefix(utxosOffset), 468 OutputIndex: uint32(utxosOffset), 469 }, 470 Asset: avax.Asset{ID: avaxAssetID}, 471 Out: &secp256k1fx.TransferOutput{ 472 Amt: 2 * units.MilliAvax, 473 OutputOwners: secp256k1fx.OutputOwners{ 474 Locktime: 0, 475 Addrs: []ids.ShortID{utxosKey.PublicKey().Address()}, 476 Threshold: 1, 477 }, 478 }, 479 }, 480 { 481 UTXOID: avax.UTXOID{ 482 TxID: ids.Empty.Prefix(utxosOffset + 2), 483 OutputIndex: uint32(utxosOffset + 2), 484 }, 485 Asset: avax.Asset{ID: nftAssetID}, 486 Out: &nftfx.MintOutput{ 487 GroupID: 1, 488 OutputOwners: secp256k1fx.OutputOwners{ 489 Threshold: 1, 490 Addrs: []ids.ShortID{utxosKey.PublicKey().Address()}, 491 }, 492 }, 493 }, 494 { 495 UTXOID: avax.UTXOID{ 496 TxID: ids.Empty.Prefix(utxosOffset + 3), 497 OutputIndex: uint32(utxosOffset + 3), 498 }, 499 Asset: avax.Asset{ID: nftAssetID}, 500 Out: &secp256k1fx.MintOutput{ 501 OutputOwners: secp256k1fx.OutputOwners{ 502 Threshold: 1, 503 Addrs: []ids.ShortID{utxosKey.PublicKey().Address()}, 504 }, 505 }, 506 }, 507 { 508 UTXOID: avax.UTXOID{ 509 TxID: ids.Empty.Prefix(utxosOffset + 4), 510 OutputIndex: uint32(utxosOffset + 4), 511 }, 512 Asset: avax.Asset{ID: propertyAssetID}, 513 Out: &propertyfx.MintOutput{ 514 OutputOwners: secp256k1fx.OutputOwners{ 515 Locktime: 0, 516 Addrs: []ids.ShortID{utxosKey.PublicKey().Address()}, 517 Threshold: 1, 518 }, 519 }, 520 }, 521 { 522 UTXOID: avax.UTXOID{ 523 TxID: ids.Empty.Prefix(utxosOffset + 5), 524 OutputIndex: uint32(utxosOffset + 5), 525 }, 526 Asset: avax.Asset{ID: propertyAssetID}, 527 Out: &propertyfx.OwnedOutput{ 528 OutputOwners: secp256k1fx.OutputOwners{ 529 Locktime: 0, 530 Addrs: []ids.ShortID{utxosKey.PublicKey().Address()}, 531 Threshold: 1, 532 }, 533 }, 534 }, 535 { // a large UTXO last, which should be enough to pay any fee by itself 536 UTXOID: avax.UTXOID{ 537 TxID: ids.Empty.Prefix(utxosOffset + 6), 538 OutputIndex: uint32(utxosOffset + 6), 539 }, 540 Asset: avax.Asset{ID: avaxAssetID}, 541 Out: &secp256k1fx.TransferOutput{ 542 Amt: 9 * units.Avax, 543 OutputOwners: secp256k1fx.OutputOwners{ 544 Locktime: 0, 545 Addrs: []ids.ShortID{utxosKey.PublicKey().Address()}, 546 Threshold: 1, 547 }, 548 }, 549 }, 550 } 551 }