github.com/ava-labs/avalanchego@v1.11.11/wallet/chain/p/builder/builder.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package builder 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "time" 11 12 "github.com/ava-labs/avalanchego/ids" 13 "github.com/ava-labs/avalanchego/utils" 14 "github.com/ava-labs/avalanchego/utils/constants" 15 "github.com/ava-labs/avalanchego/utils/math" 16 "github.com/ava-labs/avalanchego/utils/set" 17 "github.com/ava-labs/avalanchego/vms/components/avax" 18 "github.com/ava-labs/avalanchego/vms/components/gas" 19 "github.com/ava-labs/avalanchego/vms/components/verify" 20 "github.com/ava-labs/avalanchego/vms/platformvm/fx" 21 "github.com/ava-labs/avalanchego/vms/platformvm/signer" 22 "github.com/ava-labs/avalanchego/vms/platformvm/stakeable" 23 "github.com/ava-labs/avalanchego/vms/platformvm/txs" 24 "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" 25 "github.com/ava-labs/avalanchego/vms/secp256k1fx" 26 "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" 27 ) 28 29 var ( 30 ErrNoChangeAddress = errors.New("no possible change address") 31 ErrUnknownOutputType = errors.New("unknown output type") 32 ErrUnknownOwnerType = errors.New("unknown owner type") 33 ErrInsufficientAuthorization = errors.New("insufficient authorization") 34 ErrInsufficientFunds = errors.New("insufficient funds") 35 36 _ Builder = (*builder)(nil) 37 ) 38 39 // Builder provides a convenient interface for building unsigned P-chain 40 // transactions. 41 type Builder interface { 42 // Context returns the configuration of the chain that this builder uses to 43 // create transactions. 44 Context() *Context 45 46 // GetBalance calculates the amount of each asset that this builder has 47 // control over. 48 GetBalance( 49 options ...common.Option, 50 ) (map[ids.ID]uint64, error) 51 52 // GetImportableBalance calculates the amount of each asset that this 53 // builder could import from the provided chain. 54 // 55 // - [chainID] specifies the chain the funds are from. 56 GetImportableBalance( 57 chainID ids.ID, 58 options ...common.Option, 59 ) (map[ids.ID]uint64, error) 60 61 // NewBaseTx creates a new simple value transfer. 62 // 63 // - [outputs] specifies all the recipients and amounts that should be sent 64 // from this transaction. 65 NewBaseTx( 66 outputs []*avax.TransferableOutput, 67 options ...common.Option, 68 ) (*txs.BaseTx, error) 69 70 // NewAddValidatorTx creates a new validator of the primary network. 71 // 72 // - [vdr] specifies all the details of the validation period such as the 73 // startTime, endTime, stake weight, and nodeID. 74 // - [rewardsOwner] specifies the owner of all the rewards this validator 75 // may accrue during its validation period. 76 // - [shares] specifies the fraction (out of 1,000,000) that this validator 77 // will take from delegation rewards. If 1,000,000 is provided, 100% of 78 // the delegation reward will be sent to the validator's [rewardsOwner]. 79 NewAddValidatorTx( 80 vdr *txs.Validator, 81 rewardsOwner *secp256k1fx.OutputOwners, 82 shares uint32, 83 options ...common.Option, 84 ) (*txs.AddValidatorTx, error) 85 86 // NewAddSubnetValidatorTx creates a new validator of a subnet. 87 // 88 // - [vdr] specifies all the details of the validation period such as the 89 // startTime, endTime, sampling weight, nodeID, and subnetID. 90 NewAddSubnetValidatorTx( 91 vdr *txs.SubnetValidator, 92 options ...common.Option, 93 ) (*txs.AddSubnetValidatorTx, error) 94 95 // NewRemoveSubnetValidatorTx removes [nodeID] from the validator 96 // set [subnetID]. 97 NewRemoveSubnetValidatorTx( 98 nodeID ids.NodeID, 99 subnetID ids.ID, 100 options ...common.Option, 101 ) (*txs.RemoveSubnetValidatorTx, error) 102 103 // NewAddDelegatorTx creates a new delegator to a validator on the primary 104 // network. 105 // 106 // - [vdr] specifies all the details of the delegation period such as the 107 // startTime, endTime, stake weight, and validator's nodeID. 108 // - [rewardsOwner] specifies the owner of all the rewards this delegator 109 // may accrue at the end of its delegation period. 110 NewAddDelegatorTx( 111 vdr *txs.Validator, 112 rewardsOwner *secp256k1fx.OutputOwners, 113 options ...common.Option, 114 ) (*txs.AddDelegatorTx, error) 115 116 // NewCreateChainTx creates a new chain in the named subnet. 117 // 118 // - [subnetID] specifies the subnet to launch the chain in. 119 // - [genesis] specifies the initial state of the new chain. 120 // - [vmID] specifies the vm that the new chain will run. 121 // - [fxIDs] specifies all the feature extensions that the vm should be 122 // running with. 123 // - [chainName] specifies a human readable name for the chain. 124 NewCreateChainTx( 125 subnetID ids.ID, 126 genesis []byte, 127 vmID ids.ID, 128 fxIDs []ids.ID, 129 chainName string, 130 options ...common.Option, 131 ) (*txs.CreateChainTx, error) 132 133 // NewCreateSubnetTx creates a new subnet with the specified owner. 134 // 135 // - [owner] specifies who has the ability to create new chains and add new 136 // validators to the subnet. 137 NewCreateSubnetTx( 138 owner *secp256k1fx.OutputOwners, 139 options ...common.Option, 140 ) (*txs.CreateSubnetTx, error) 141 142 // NewTransferSubnetOwnershipTx changes the owner of the named subnet. 143 // 144 // - [subnetID] specifies the subnet to be modified 145 // - [owner] specifies who has the ability to create new chains and add new 146 // validators to the subnet. 147 NewTransferSubnetOwnershipTx( 148 subnetID ids.ID, 149 owner *secp256k1fx.OutputOwners, 150 options ...common.Option, 151 ) (*txs.TransferSubnetOwnershipTx, error) 152 153 // NewImportTx creates an import transaction that attempts to consume all 154 // the available UTXOs and import the funds to [to]. 155 // 156 // - [chainID] specifies the chain to be importing funds from. 157 // - [to] specifies where to send the imported funds to. 158 NewImportTx( 159 chainID ids.ID, 160 to *secp256k1fx.OutputOwners, 161 options ...common.Option, 162 ) (*txs.ImportTx, error) 163 164 // NewExportTx creates an export transaction that attempts to send all the 165 // provided [outputs] to the requested [chainID]. 166 // 167 // - [chainID] specifies the chain to be exporting the funds to. 168 // - [outputs] specifies the outputs to send to the [chainID]. 169 NewExportTx( 170 chainID ids.ID, 171 outputs []*avax.TransferableOutput, 172 options ...common.Option, 173 ) (*txs.ExportTx, error) 174 175 // NewTransformSubnetTx creates a transform subnet transaction that attempts 176 // to convert the provided [subnetID] from a permissioned subnet to a 177 // permissionless subnet. This transaction will convert 178 // [maxSupply] - [initialSupply] of [assetID] to staking rewards. 179 // 180 // - [subnetID] specifies the subnet to transform. 181 // - [assetID] specifies the asset to use to reward stakers on the subnet. 182 // - [initialSupply] is the amount of [assetID] that will be in circulation 183 // after this transaction is accepted. 184 // - [maxSupply] is the maximum total amount of [assetID] that should ever 185 // exist. 186 // - [minConsumptionRate] is the rate that a staker will receive rewards 187 // if they stake with a duration of 0. 188 // - [maxConsumptionRate] is the maximum rate that staking rewards should be 189 // consumed from the reward pool per year. 190 // - [minValidatorStake] is the minimum amount of funds required to become a 191 // validator. 192 // - [maxValidatorStake] is the maximum amount of funds a single validator 193 // can be allocated, including delegated funds. 194 // - [minStakeDuration] is the minimum number of seconds a staker can stake 195 // for. 196 // - [maxStakeDuration] is the maximum number of seconds a staker can stake 197 // for. 198 // - [minValidatorStake] is the minimum amount of funds required to become a 199 // delegator. 200 // - [maxValidatorWeightFactor] is the factor which calculates the maximum 201 // amount of delegation a validator can receive. A value of 1 effectively 202 // disables delegation. 203 // - [uptimeRequirement] is the minimum percentage a validator must be 204 // online and responsive to receive a reward. 205 NewTransformSubnetTx( 206 subnetID ids.ID, 207 assetID ids.ID, 208 initialSupply uint64, 209 maxSupply uint64, 210 minConsumptionRate uint64, 211 maxConsumptionRate uint64, 212 minValidatorStake uint64, 213 maxValidatorStake uint64, 214 minStakeDuration time.Duration, 215 maxStakeDuration time.Duration, 216 minDelegationFee uint32, 217 minDelegatorStake uint64, 218 maxValidatorWeightFactor byte, 219 uptimeRequirement uint32, 220 options ...common.Option, 221 ) (*txs.TransformSubnetTx, error) 222 223 // NewAddPermissionlessValidatorTx creates a new validator of the specified 224 // subnet. 225 // 226 // - [vdr] specifies all the details of the validation period such as the 227 // subnetID, startTime, endTime, stake weight, and nodeID. 228 // - [signer] if the subnetID is the primary network, this is the BLS key 229 // for this validator. Otherwise, this value should be the empty signer. 230 // - [assetID] specifies the asset to stake. 231 // - [validationRewardsOwner] specifies the owner of all the rewards this 232 // validator earns for its validation period. 233 // - [delegationRewardsOwner] specifies the owner of all the rewards this 234 // validator earns for delegations during its validation period. 235 // - [shares] specifies the fraction (out of 1,000,000) that this validator 236 // will take from delegation rewards. If 1,000,000 is provided, 100% of 237 // the delegation reward will be sent to the validator's [rewardsOwner]. 238 NewAddPermissionlessValidatorTx( 239 vdr *txs.SubnetValidator, 240 signer signer.Signer, 241 assetID ids.ID, 242 validationRewardsOwner *secp256k1fx.OutputOwners, 243 delegationRewardsOwner *secp256k1fx.OutputOwners, 244 shares uint32, 245 options ...common.Option, 246 ) (*txs.AddPermissionlessValidatorTx, error) 247 248 // NewAddPermissionlessDelegatorTx creates a new delegator of the specified 249 // subnet on the specified nodeID. 250 // 251 // - [vdr] specifies all the details of the delegation period such as the 252 // subnetID, startTime, endTime, stake weight, and nodeID. 253 // - [assetID] specifies the asset to stake. 254 // - [rewardsOwner] specifies the owner of all the rewards this delegator 255 // earns during its delegation period. 256 NewAddPermissionlessDelegatorTx( 257 vdr *txs.SubnetValidator, 258 assetID ids.ID, 259 rewardsOwner *secp256k1fx.OutputOwners, 260 options ...common.Option, 261 ) (*txs.AddPermissionlessDelegatorTx, error) 262 } 263 264 type Backend interface { 265 UTXOs(ctx context.Context, sourceChainID ids.ID) ([]*avax.UTXO, error) 266 GetSubnetOwner(ctx context.Context, subnetID ids.ID) (fx.Owner, error) 267 } 268 269 type builder struct { 270 addrs set.Set[ids.ShortID] 271 context *Context 272 backend Backend 273 } 274 275 // New returns a new transaction builder. 276 // 277 // - [addrs] is the set of addresses that the builder assumes can be used when 278 // signing the transactions in the future. 279 // - [context] provides the chain's configuration. 280 // - [backend] provides the chain's state. 281 func New( 282 addrs set.Set[ids.ShortID], 283 context *Context, 284 backend Backend, 285 ) Builder { 286 return &builder{ 287 addrs: addrs, 288 context: context, 289 backend: backend, 290 } 291 } 292 293 func (b *builder) Context() *Context { 294 return b.context 295 } 296 297 func (b *builder) GetBalance( 298 options ...common.Option, 299 ) (map[ids.ID]uint64, error) { 300 ops := common.NewOptions(options) 301 return b.getBalance(constants.PlatformChainID, ops) 302 } 303 304 func (b *builder) GetImportableBalance( 305 chainID ids.ID, 306 options ...common.Option, 307 ) (map[ids.ID]uint64, error) { 308 ops := common.NewOptions(options) 309 return b.getBalance(chainID, ops) 310 } 311 312 func (b *builder) NewBaseTx( 313 outputs []*avax.TransferableOutput, 314 options ...common.Option, 315 ) (*txs.BaseTx, error) { 316 toBurn := map[ids.ID]uint64{ 317 b.context.AVAXAssetID: b.context.StaticFeeConfig.TxFee, 318 } 319 for _, out := range outputs { 320 assetID := out.AssetID() 321 amountToBurn, err := math.Add(toBurn[assetID], out.Out.Amount()) 322 if err != nil { 323 return nil, err 324 } 325 toBurn[assetID] = amountToBurn 326 } 327 toStake := map[ids.ID]uint64{} 328 329 ops := common.NewOptions(options) 330 memo := ops.Memo() 331 memoComplexity := gas.Dimensions{ 332 gas.Bandwidth: uint64(len(memo)), 333 } 334 outputComplexity, err := fee.OutputComplexity(outputs...) 335 if err != nil { 336 return nil, err 337 } 338 complexity, err := fee.IntrinsicBaseTxComplexities.Add( 339 &memoComplexity, 340 &outputComplexity, 341 ) 342 if err != nil { 343 return nil, err 344 } 345 346 inputs, changeOutputs, _, err := b.spend( 347 toBurn, 348 toStake, 349 0, 350 complexity, 351 nil, 352 ops, 353 ) 354 if err != nil { 355 return nil, err 356 } 357 outputs = append(outputs, changeOutputs...) 358 avax.SortTransferableOutputs(outputs, txs.Codec) // sort the outputs 359 360 tx := &txs.BaseTx{BaseTx: avax.BaseTx{ 361 NetworkID: b.context.NetworkID, 362 BlockchainID: constants.PlatformChainID, 363 Ins: inputs, 364 Outs: outputs, 365 Memo: memo, 366 }} 367 return tx, b.initCtx(tx) 368 } 369 370 func (b *builder) NewAddValidatorTx( 371 vdr *txs.Validator, 372 rewardsOwner *secp256k1fx.OutputOwners, 373 shares uint32, 374 options ...common.Option, 375 ) (*txs.AddValidatorTx, error) { 376 avaxAssetID := b.context.AVAXAssetID 377 toBurn := map[ids.ID]uint64{ 378 avaxAssetID: b.context.StaticFeeConfig.AddPrimaryNetworkValidatorFee, 379 } 380 toStake := map[ids.ID]uint64{ 381 avaxAssetID: vdr.Wght, 382 } 383 ops := common.NewOptions(options) 384 inputs, baseOutputs, stakeOutputs, err := b.spend( 385 toBurn, 386 toStake, 387 0, 388 gas.Dimensions{}, 389 nil, 390 ops, 391 ) 392 if err != nil { 393 return nil, err 394 } 395 396 utils.Sort(rewardsOwner.Addrs) 397 tx := &txs.AddValidatorTx{ 398 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 399 NetworkID: b.context.NetworkID, 400 BlockchainID: constants.PlatformChainID, 401 Ins: inputs, 402 Outs: baseOutputs, 403 Memo: ops.Memo(), 404 }}, 405 Validator: *vdr, 406 StakeOuts: stakeOutputs, 407 RewardsOwner: rewardsOwner, 408 DelegationShares: shares, 409 } 410 return tx, b.initCtx(tx) 411 } 412 413 func (b *builder) NewAddSubnetValidatorTx( 414 vdr *txs.SubnetValidator, 415 options ...common.Option, 416 ) (*txs.AddSubnetValidatorTx, error) { 417 toBurn := map[ids.ID]uint64{ 418 b.context.AVAXAssetID: b.context.StaticFeeConfig.AddSubnetValidatorFee, 419 } 420 toStake := map[ids.ID]uint64{} 421 422 ops := common.NewOptions(options) 423 subnetAuth, err := b.authorizeSubnet(vdr.Subnet, ops) 424 if err != nil { 425 return nil, err 426 } 427 428 memo := ops.Memo() 429 memoComplexity := gas.Dimensions{ 430 gas.Bandwidth: uint64(len(memo)), 431 } 432 authComplexity, err := fee.AuthComplexity(subnetAuth) 433 if err != nil { 434 return nil, err 435 } 436 complexity, err := fee.IntrinsicAddSubnetValidatorTxComplexities.Add( 437 &memoComplexity, 438 &authComplexity, 439 ) 440 if err != nil { 441 return nil, err 442 } 443 444 inputs, outputs, _, err := b.spend( 445 toBurn, 446 toStake, 447 0, 448 complexity, 449 nil, 450 ops, 451 ) 452 if err != nil { 453 return nil, err 454 } 455 456 tx := &txs.AddSubnetValidatorTx{ 457 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 458 NetworkID: b.context.NetworkID, 459 BlockchainID: constants.PlatformChainID, 460 Ins: inputs, 461 Outs: outputs, 462 Memo: memo, 463 }}, 464 SubnetValidator: *vdr, 465 SubnetAuth: subnetAuth, 466 } 467 return tx, b.initCtx(tx) 468 } 469 470 func (b *builder) NewRemoveSubnetValidatorTx( 471 nodeID ids.NodeID, 472 subnetID ids.ID, 473 options ...common.Option, 474 ) (*txs.RemoveSubnetValidatorTx, error) { 475 toBurn := map[ids.ID]uint64{ 476 b.context.AVAXAssetID: b.context.StaticFeeConfig.TxFee, 477 } 478 toStake := map[ids.ID]uint64{} 479 480 ops := common.NewOptions(options) 481 subnetAuth, err := b.authorizeSubnet(subnetID, ops) 482 if err != nil { 483 return nil, err 484 } 485 486 memo := ops.Memo() 487 memoComplexity := gas.Dimensions{ 488 gas.Bandwidth: uint64(len(memo)), 489 } 490 authComplexity, err := fee.AuthComplexity(subnetAuth) 491 if err != nil { 492 return nil, err 493 } 494 complexity, err := fee.IntrinsicRemoveSubnetValidatorTxComplexities.Add( 495 &memoComplexity, 496 &authComplexity, 497 ) 498 if err != nil { 499 return nil, err 500 } 501 502 inputs, outputs, _, err := b.spend( 503 toBurn, 504 toStake, 505 0, 506 complexity, 507 nil, 508 ops, 509 ) 510 if err != nil { 511 return nil, err 512 } 513 514 tx := &txs.RemoveSubnetValidatorTx{ 515 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 516 NetworkID: b.context.NetworkID, 517 BlockchainID: constants.PlatformChainID, 518 Ins: inputs, 519 Outs: outputs, 520 Memo: ops.Memo(), 521 }}, 522 Subnet: subnetID, 523 NodeID: nodeID, 524 SubnetAuth: subnetAuth, 525 } 526 return tx, b.initCtx(tx) 527 } 528 529 func (b *builder) NewAddDelegatorTx( 530 vdr *txs.Validator, 531 rewardsOwner *secp256k1fx.OutputOwners, 532 options ...common.Option, 533 ) (*txs.AddDelegatorTx, error) { 534 avaxAssetID := b.context.AVAXAssetID 535 toBurn := map[ids.ID]uint64{ 536 avaxAssetID: b.context.StaticFeeConfig.AddPrimaryNetworkDelegatorFee, 537 } 538 toStake := map[ids.ID]uint64{ 539 avaxAssetID: vdr.Wght, 540 } 541 ops := common.NewOptions(options) 542 inputs, baseOutputs, stakeOutputs, err := b.spend( 543 toBurn, 544 toStake, 545 0, 546 gas.Dimensions{}, 547 nil, 548 ops, 549 ) 550 if err != nil { 551 return nil, err 552 } 553 554 utils.Sort(rewardsOwner.Addrs) 555 tx := &txs.AddDelegatorTx{ 556 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 557 NetworkID: b.context.NetworkID, 558 BlockchainID: constants.PlatformChainID, 559 Ins: inputs, 560 Outs: baseOutputs, 561 Memo: ops.Memo(), 562 }}, 563 Validator: *vdr, 564 StakeOuts: stakeOutputs, 565 DelegationRewardsOwner: rewardsOwner, 566 } 567 return tx, b.initCtx(tx) 568 } 569 570 func (b *builder) NewCreateChainTx( 571 subnetID ids.ID, 572 genesis []byte, 573 vmID ids.ID, 574 fxIDs []ids.ID, 575 chainName string, 576 options ...common.Option, 577 ) (*txs.CreateChainTx, error) { 578 toBurn := map[ids.ID]uint64{ 579 b.context.AVAXAssetID: b.context.StaticFeeConfig.CreateBlockchainTxFee, 580 } 581 toStake := map[ids.ID]uint64{} 582 583 ops := common.NewOptions(options) 584 subnetAuth, err := b.authorizeSubnet(subnetID, ops) 585 if err != nil { 586 return nil, err 587 } 588 589 memo := ops.Memo() 590 bandwidth, err := math.Mul(uint64(len(fxIDs)), ids.IDLen) 591 if err != nil { 592 return nil, err 593 } 594 bandwidth, err = math.Add(bandwidth, uint64(len(chainName))) 595 if err != nil { 596 return nil, err 597 } 598 bandwidth, err = math.Add(bandwidth, uint64(len(genesis))) 599 if err != nil { 600 return nil, err 601 } 602 bandwidth, err = math.Add(bandwidth, uint64(len(memo))) 603 if err != nil { 604 return nil, err 605 } 606 dynamicComplexity := gas.Dimensions{ 607 gas.Bandwidth: bandwidth, 608 } 609 authComplexity, err := fee.AuthComplexity(subnetAuth) 610 if err != nil { 611 return nil, err 612 } 613 complexity, err := fee.IntrinsicCreateChainTxComplexities.Add( 614 &dynamicComplexity, 615 &authComplexity, 616 ) 617 if err != nil { 618 return nil, err 619 } 620 621 inputs, outputs, _, err := b.spend( 622 toBurn, 623 toStake, 624 0, 625 complexity, 626 nil, 627 ops, 628 ) 629 if err != nil { 630 return nil, err 631 } 632 633 utils.Sort(fxIDs) 634 tx := &txs.CreateChainTx{ 635 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 636 NetworkID: b.context.NetworkID, 637 BlockchainID: constants.PlatformChainID, 638 Ins: inputs, 639 Outs: outputs, 640 Memo: memo, 641 }}, 642 SubnetID: subnetID, 643 ChainName: chainName, 644 VMID: vmID, 645 FxIDs: fxIDs, 646 GenesisData: genesis, 647 SubnetAuth: subnetAuth, 648 } 649 return tx, b.initCtx(tx) 650 } 651 652 func (b *builder) NewCreateSubnetTx( 653 owner *secp256k1fx.OutputOwners, 654 options ...common.Option, 655 ) (*txs.CreateSubnetTx, error) { 656 toBurn := map[ids.ID]uint64{ 657 b.context.AVAXAssetID: b.context.StaticFeeConfig.CreateSubnetTxFee, 658 } 659 toStake := map[ids.ID]uint64{} 660 661 ops := common.NewOptions(options) 662 memo := ops.Memo() 663 memoComplexity := gas.Dimensions{ 664 gas.Bandwidth: uint64(len(memo)), 665 } 666 ownerComplexity, err := fee.OwnerComplexity(owner) 667 if err != nil { 668 return nil, err 669 } 670 complexity, err := fee.IntrinsicCreateSubnetTxComplexities.Add( 671 &memoComplexity, 672 &ownerComplexity, 673 ) 674 if err != nil { 675 return nil, err 676 } 677 678 inputs, outputs, _, err := b.spend( 679 toBurn, 680 toStake, 681 0, 682 complexity, 683 nil, 684 ops, 685 ) 686 if err != nil { 687 return nil, err 688 } 689 690 utils.Sort(owner.Addrs) 691 tx := &txs.CreateSubnetTx{ 692 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 693 NetworkID: b.context.NetworkID, 694 BlockchainID: constants.PlatformChainID, 695 Ins: inputs, 696 Outs: outputs, 697 Memo: memo, 698 }}, 699 Owner: owner, 700 } 701 return tx, b.initCtx(tx) 702 } 703 704 func (b *builder) NewTransferSubnetOwnershipTx( 705 subnetID ids.ID, 706 owner *secp256k1fx.OutputOwners, 707 options ...common.Option, 708 ) (*txs.TransferSubnetOwnershipTx, error) { 709 toBurn := map[ids.ID]uint64{ 710 b.context.AVAXAssetID: b.context.StaticFeeConfig.TxFee, 711 } 712 toStake := map[ids.ID]uint64{} 713 714 ops := common.NewOptions(options) 715 subnetAuth, err := b.authorizeSubnet(subnetID, ops) 716 if err != nil { 717 return nil, err 718 } 719 720 memo := ops.Memo() 721 memoComplexity := gas.Dimensions{ 722 gas.Bandwidth: uint64(len(memo)), 723 } 724 authComplexity, err := fee.AuthComplexity(subnetAuth) 725 if err != nil { 726 return nil, err 727 } 728 ownerComplexity, err := fee.OwnerComplexity(owner) 729 if err != nil { 730 return nil, err 731 } 732 complexity, err := fee.IntrinsicTransferSubnetOwnershipTxComplexities.Add( 733 &memoComplexity, 734 &authComplexity, 735 &ownerComplexity, 736 ) 737 if err != nil { 738 return nil, err 739 } 740 741 inputs, outputs, _, err := b.spend( 742 toBurn, 743 toStake, 744 0, 745 complexity, 746 nil, 747 ops, 748 ) 749 if err != nil { 750 return nil, err 751 } 752 753 utils.Sort(owner.Addrs) 754 tx := &txs.TransferSubnetOwnershipTx{ 755 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 756 NetworkID: b.context.NetworkID, 757 BlockchainID: constants.PlatformChainID, 758 Ins: inputs, 759 Outs: outputs, 760 Memo: memo, 761 }}, 762 Subnet: subnetID, 763 Owner: owner, 764 SubnetAuth: subnetAuth, 765 } 766 return tx, b.initCtx(tx) 767 } 768 769 func (b *builder) NewImportTx( 770 sourceChainID ids.ID, 771 to *secp256k1fx.OutputOwners, 772 options ...common.Option, 773 ) (*txs.ImportTx, error) { 774 ops := common.NewOptions(options) 775 utxos, err := b.backend.UTXOs(ops.Context(), sourceChainID) 776 if err != nil { 777 return nil, err 778 } 779 780 var ( 781 addrs = ops.Addresses(b.addrs) 782 minIssuanceTime = ops.MinIssuanceTime() 783 avaxAssetID = b.context.AVAXAssetID 784 txFee = b.context.StaticFeeConfig.TxFee 785 786 importedInputs = make([]*avax.TransferableInput, 0, len(utxos)) 787 importedAmounts = make(map[ids.ID]uint64) 788 ) 789 // Iterate over the unlocked UTXOs 790 for _, utxo := range utxos { 791 out, ok := utxo.Out.(*secp256k1fx.TransferOutput) 792 if !ok { 793 continue 794 } 795 796 inputSigIndices, ok := common.MatchOwners(&out.OutputOwners, addrs, minIssuanceTime) 797 if !ok { 798 // We couldn't spend this UTXO, so we skip to the next one 799 continue 800 } 801 802 importedInputs = append(importedInputs, &avax.TransferableInput{ 803 UTXOID: utxo.UTXOID, 804 Asset: utxo.Asset, 805 In: &secp256k1fx.TransferInput{ 806 Amt: out.Amt, 807 Input: secp256k1fx.Input{ 808 SigIndices: inputSigIndices, 809 }, 810 }, 811 }) 812 813 assetID := utxo.AssetID() 814 newImportedAmount, err := math.Add(importedAmounts[assetID], out.Amt) 815 if err != nil { 816 return nil, err 817 } 818 importedAmounts[assetID] = newImportedAmount 819 } 820 utils.Sort(importedInputs) // sort imported inputs 821 822 if len(importedInputs) == 0 { 823 return nil, fmt.Errorf( 824 "%w: no UTXOs available to import", 825 ErrInsufficientFunds, 826 ) 827 } 828 829 outputs := make([]*avax.TransferableOutput, 0, len(importedAmounts)) 830 for assetID, amount := range importedAmounts { 831 if assetID == avaxAssetID { 832 continue 833 } 834 835 outputs = append(outputs, &avax.TransferableOutput{ 836 Asset: avax.Asset{ID: assetID}, 837 Out: &secp256k1fx.TransferOutput{ 838 Amt: amount, 839 OutputOwners: *to, 840 }, 841 }) 842 } 843 844 memo := ops.Memo() 845 memoComplexity := gas.Dimensions{ 846 gas.Bandwidth: uint64(len(memo)), 847 } 848 inputComplexity, err := fee.InputComplexity(importedInputs...) 849 if err != nil { 850 return nil, err 851 } 852 outputComplexity, err := fee.OutputComplexity(outputs...) 853 if err != nil { 854 return nil, err 855 } 856 complexity, err := fee.IntrinsicImportTxComplexities.Add( 857 &memoComplexity, 858 &inputComplexity, 859 &outputComplexity, 860 ) 861 if err != nil { 862 return nil, err 863 } 864 865 var ( 866 toBurn = map[ids.ID]uint64{} 867 toStake = map[ids.ID]uint64{} 868 excessAVAX uint64 869 ) 870 if importedAVAX := importedAmounts[avaxAssetID]; importedAVAX < txFee { 871 toBurn[avaxAssetID] = txFee - importedAVAX 872 } else { 873 excessAVAX = importedAVAX - txFee 874 } 875 876 inputs, changeOutputs, _, err := b.spend( 877 toBurn, 878 toStake, 879 excessAVAX, 880 complexity, 881 to, 882 ops, 883 ) 884 if err != nil { 885 return nil, fmt.Errorf("couldn't generate tx inputs/outputs: %w", err) 886 } 887 outputs = append(outputs, changeOutputs...) 888 889 avax.SortTransferableOutputs(outputs, txs.Codec) // sort imported outputs 890 tx := &txs.ImportTx{ 891 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 892 NetworkID: b.context.NetworkID, 893 BlockchainID: constants.PlatformChainID, 894 Ins: inputs, 895 Outs: outputs, 896 Memo: memo, 897 }}, 898 SourceChain: sourceChainID, 899 ImportedInputs: importedInputs, 900 } 901 return tx, b.initCtx(tx) 902 } 903 904 func (b *builder) NewExportTx( 905 chainID ids.ID, 906 outputs []*avax.TransferableOutput, 907 options ...common.Option, 908 ) (*txs.ExportTx, error) { 909 toBurn := map[ids.ID]uint64{ 910 b.context.AVAXAssetID: b.context.StaticFeeConfig.TxFee, 911 } 912 for _, out := range outputs { 913 assetID := out.AssetID() 914 amountToBurn, err := math.Add(toBurn[assetID], out.Out.Amount()) 915 if err != nil { 916 return nil, err 917 } 918 toBurn[assetID] = amountToBurn 919 } 920 921 toStake := map[ids.ID]uint64{} 922 ops := common.NewOptions(options) 923 memo := ops.Memo() 924 memoComplexity := gas.Dimensions{ 925 gas.Bandwidth: uint64(len(memo)), 926 } 927 outputComplexity, err := fee.OutputComplexity(outputs...) 928 if err != nil { 929 return nil, err 930 } 931 complexity, err := fee.IntrinsicExportTxComplexities.Add( 932 &memoComplexity, 933 &outputComplexity, 934 ) 935 if err != nil { 936 return nil, err 937 } 938 939 inputs, changeOutputs, _, err := b.spend( 940 toBurn, 941 toStake, 942 0, 943 complexity, 944 nil, 945 ops, 946 ) 947 if err != nil { 948 return nil, err 949 } 950 951 avax.SortTransferableOutputs(outputs, txs.Codec) // sort exported outputs 952 tx := &txs.ExportTx{ 953 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 954 NetworkID: b.context.NetworkID, 955 BlockchainID: constants.PlatformChainID, 956 Ins: inputs, 957 Outs: changeOutputs, 958 Memo: memo, 959 }}, 960 DestinationChain: chainID, 961 ExportedOutputs: outputs, 962 } 963 return tx, b.initCtx(tx) 964 } 965 966 func (b *builder) NewTransformSubnetTx( 967 subnetID ids.ID, 968 assetID ids.ID, 969 initialSupply uint64, 970 maxSupply uint64, 971 minConsumptionRate uint64, 972 maxConsumptionRate uint64, 973 minValidatorStake uint64, 974 maxValidatorStake uint64, 975 minStakeDuration time.Duration, 976 maxStakeDuration time.Duration, 977 minDelegationFee uint32, 978 minDelegatorStake uint64, 979 maxValidatorWeightFactor byte, 980 uptimeRequirement uint32, 981 options ...common.Option, 982 ) (*txs.TransformSubnetTx, error) { 983 toBurn := map[ids.ID]uint64{ 984 b.context.AVAXAssetID: b.context.StaticFeeConfig.TransformSubnetTxFee, 985 assetID: maxSupply - initialSupply, 986 } 987 toStake := map[ids.ID]uint64{} 988 989 ops := common.NewOptions(options) 990 subnetAuth, err := b.authorizeSubnet(subnetID, ops) 991 if err != nil { 992 return nil, err 993 } 994 995 inputs, outputs, _, err := b.spend( 996 toBurn, 997 toStake, 998 0, 999 gas.Dimensions{}, 1000 nil, 1001 ops, 1002 ) 1003 if err != nil { 1004 return nil, err 1005 } 1006 1007 tx := &txs.TransformSubnetTx{ 1008 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 1009 NetworkID: b.context.NetworkID, 1010 BlockchainID: constants.PlatformChainID, 1011 Ins: inputs, 1012 Outs: outputs, 1013 Memo: ops.Memo(), 1014 }}, 1015 Subnet: subnetID, 1016 AssetID: assetID, 1017 InitialSupply: initialSupply, 1018 MaximumSupply: maxSupply, 1019 MinConsumptionRate: minConsumptionRate, 1020 MaxConsumptionRate: maxConsumptionRate, 1021 MinValidatorStake: minValidatorStake, 1022 MaxValidatorStake: maxValidatorStake, 1023 MinStakeDuration: uint32(minStakeDuration / time.Second), 1024 MaxStakeDuration: uint32(maxStakeDuration / time.Second), 1025 MinDelegationFee: minDelegationFee, 1026 MinDelegatorStake: minDelegatorStake, 1027 MaxValidatorWeightFactor: maxValidatorWeightFactor, 1028 UptimeRequirement: uptimeRequirement, 1029 SubnetAuth: subnetAuth, 1030 } 1031 return tx, b.initCtx(tx) 1032 } 1033 1034 func (b *builder) NewAddPermissionlessValidatorTx( 1035 vdr *txs.SubnetValidator, 1036 signer signer.Signer, 1037 assetID ids.ID, 1038 validationRewardsOwner *secp256k1fx.OutputOwners, 1039 delegationRewardsOwner *secp256k1fx.OutputOwners, 1040 shares uint32, 1041 options ...common.Option, 1042 ) (*txs.AddPermissionlessValidatorTx, error) { 1043 avaxAssetID := b.context.AVAXAssetID 1044 toBurn := map[ids.ID]uint64{} 1045 if vdr.Subnet == constants.PrimaryNetworkID { 1046 toBurn[avaxAssetID] = b.context.StaticFeeConfig.AddPrimaryNetworkValidatorFee 1047 } else { 1048 toBurn[avaxAssetID] = b.context.StaticFeeConfig.AddSubnetValidatorFee 1049 } 1050 toStake := map[ids.ID]uint64{ 1051 assetID: vdr.Wght, 1052 } 1053 1054 ops := common.NewOptions(options) 1055 memo := ops.Memo() 1056 memoComplexity := gas.Dimensions{ 1057 gas.Bandwidth: uint64(len(memo)), 1058 } 1059 signerComplexity, err := fee.SignerComplexity(signer) 1060 if err != nil { 1061 return nil, err 1062 } 1063 validatorOwnerComplexity, err := fee.OwnerComplexity(validationRewardsOwner) 1064 if err != nil { 1065 return nil, err 1066 } 1067 delegatorOwnerComplexity, err := fee.OwnerComplexity(delegationRewardsOwner) 1068 if err != nil { 1069 return nil, err 1070 } 1071 complexity, err := fee.IntrinsicAddPermissionlessValidatorTxComplexities.Add( 1072 &memoComplexity, 1073 &signerComplexity, 1074 &validatorOwnerComplexity, 1075 &delegatorOwnerComplexity, 1076 ) 1077 if err != nil { 1078 return nil, err 1079 } 1080 1081 inputs, baseOutputs, stakeOutputs, err := b.spend( 1082 toBurn, 1083 toStake, 1084 0, 1085 complexity, 1086 nil, 1087 ops, 1088 ) 1089 if err != nil { 1090 return nil, err 1091 } 1092 1093 utils.Sort(validationRewardsOwner.Addrs) 1094 utils.Sort(delegationRewardsOwner.Addrs) 1095 tx := &txs.AddPermissionlessValidatorTx{ 1096 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 1097 NetworkID: b.context.NetworkID, 1098 BlockchainID: constants.PlatformChainID, 1099 Ins: inputs, 1100 Outs: baseOutputs, 1101 Memo: memo, 1102 }}, 1103 Validator: vdr.Validator, 1104 Subnet: vdr.Subnet, 1105 Signer: signer, 1106 StakeOuts: stakeOutputs, 1107 ValidatorRewardsOwner: validationRewardsOwner, 1108 DelegatorRewardsOwner: delegationRewardsOwner, 1109 DelegationShares: shares, 1110 } 1111 return tx, b.initCtx(tx) 1112 } 1113 1114 func (b *builder) NewAddPermissionlessDelegatorTx( 1115 vdr *txs.SubnetValidator, 1116 assetID ids.ID, 1117 rewardsOwner *secp256k1fx.OutputOwners, 1118 options ...common.Option, 1119 ) (*txs.AddPermissionlessDelegatorTx, error) { 1120 avaxAssetID := b.context.AVAXAssetID 1121 toBurn := map[ids.ID]uint64{} 1122 if vdr.Subnet == constants.PrimaryNetworkID { 1123 toBurn[avaxAssetID] = b.context.StaticFeeConfig.AddPrimaryNetworkDelegatorFee 1124 } else { 1125 toBurn[avaxAssetID] = b.context.StaticFeeConfig.AddSubnetDelegatorFee 1126 } 1127 toStake := map[ids.ID]uint64{ 1128 assetID: vdr.Wght, 1129 } 1130 1131 ops := common.NewOptions(options) 1132 memo := ops.Memo() 1133 memoComplexity := gas.Dimensions{ 1134 gas.Bandwidth: uint64(len(memo)), 1135 } 1136 ownerComplexity, err := fee.OwnerComplexity(rewardsOwner) 1137 if err != nil { 1138 return nil, err 1139 } 1140 complexity, err := fee.IntrinsicAddPermissionlessDelegatorTxComplexities.Add( 1141 &memoComplexity, 1142 &ownerComplexity, 1143 ) 1144 if err != nil { 1145 return nil, err 1146 } 1147 1148 inputs, baseOutputs, stakeOutputs, err := b.spend( 1149 toBurn, 1150 toStake, 1151 0, 1152 complexity, 1153 nil, 1154 ops, 1155 ) 1156 if err != nil { 1157 return nil, err 1158 } 1159 1160 utils.Sort(rewardsOwner.Addrs) 1161 tx := &txs.AddPermissionlessDelegatorTx{ 1162 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 1163 NetworkID: b.context.NetworkID, 1164 BlockchainID: constants.PlatformChainID, 1165 Ins: inputs, 1166 Outs: baseOutputs, 1167 Memo: memo, 1168 }}, 1169 Validator: vdr.Validator, 1170 Subnet: vdr.Subnet, 1171 StakeOuts: stakeOutputs, 1172 DelegationRewardsOwner: rewardsOwner, 1173 } 1174 return tx, b.initCtx(tx) 1175 } 1176 1177 func (b *builder) getBalance( 1178 chainID ids.ID, 1179 options *common.Options, 1180 ) ( 1181 balance map[ids.ID]uint64, 1182 err error, 1183 ) { 1184 utxos, err := b.backend.UTXOs(options.Context(), chainID) 1185 if err != nil { 1186 return nil, err 1187 } 1188 1189 addrs := options.Addresses(b.addrs) 1190 minIssuanceTime := options.MinIssuanceTime() 1191 balance = make(map[ids.ID]uint64) 1192 1193 // Iterate over the UTXOs 1194 for _, utxo := range utxos { 1195 outIntf := utxo.Out 1196 if lockedOut, ok := outIntf.(*stakeable.LockOut); ok { 1197 if !options.AllowStakeableLocked() && lockedOut.Locktime > minIssuanceTime { 1198 // This output is currently locked, so this output can't be 1199 // burned. 1200 continue 1201 } 1202 outIntf = lockedOut.TransferableOut 1203 } 1204 1205 out, ok := outIntf.(*secp256k1fx.TransferOutput) 1206 if !ok { 1207 return nil, ErrUnknownOutputType 1208 } 1209 1210 _, ok = common.MatchOwners(&out.OutputOwners, addrs, minIssuanceTime) 1211 if !ok { 1212 // We couldn't spend this UTXO, so we skip to the next one 1213 continue 1214 } 1215 1216 assetID := utxo.AssetID() 1217 balance[assetID], err = math.Add(balance[assetID], out.Amt) 1218 if err != nil { 1219 return nil, err 1220 } 1221 } 1222 return balance, nil 1223 } 1224 1225 // spend takes in the requested burn amounts and the requested stake amounts. 1226 // 1227 // - [toBurn] maps assetID to the amount of the asset to spend without 1228 // producing an output. This is typically used for fees. However, it can 1229 // also be used to consume some of an asset that will be produced in 1230 // separate outputs, such as ExportedOutputs. Only unlocked UTXOs are able 1231 // to be burned here. 1232 // - [toStake] maps assetID to the amount of the asset to spend and place into 1233 // the staked outputs. First locked UTXOs are attempted to be used for these 1234 // funds, and then unlocked UTXOs will be attempted to be used. There is no 1235 // preferential ordering on the unlock times. 1236 // - [excessAVAX] contains the amount of extra AVAX that spend can produce in 1237 // the change outputs in addition to the consumed and not burned AVAX. 1238 // - [complexity] contains the currently accrued transaction complexity that 1239 // will be used to calculate the required fees to be burned. 1240 // - [ownerOverride] optionally specifies the output owners to use for the 1241 // unlocked AVAX change output if no additional AVAX was needed to be 1242 // burned. If this value is nil, the default change owner is used. 1243 func (b *builder) spend( 1244 toBurn map[ids.ID]uint64, 1245 toStake map[ids.ID]uint64, 1246 excessAVAX uint64, 1247 complexity gas.Dimensions, 1248 ownerOverride *secp256k1fx.OutputOwners, 1249 options *common.Options, 1250 ) ( 1251 inputs []*avax.TransferableInput, 1252 changeOutputs []*avax.TransferableOutput, 1253 stakeOutputs []*avax.TransferableOutput, 1254 err error, 1255 ) { 1256 utxos, err := b.backend.UTXOs(options.Context(), constants.PlatformChainID) 1257 if err != nil { 1258 return nil, nil, nil, err 1259 } 1260 1261 addrs := options.Addresses(b.addrs) 1262 minIssuanceTime := options.MinIssuanceTime() 1263 1264 addr, ok := addrs.Peek() 1265 if !ok { 1266 return nil, nil, nil, ErrNoChangeAddress 1267 } 1268 changeOwner := options.ChangeOwner(&secp256k1fx.OutputOwners{ 1269 Threshold: 1, 1270 Addrs: []ids.ShortID{addr}, 1271 }) 1272 if ownerOverride == nil { 1273 ownerOverride = changeOwner 1274 } 1275 1276 s := spendHelper{ 1277 weights: b.context.ComplexityWeights, 1278 gasPrice: b.context.GasPrice, 1279 1280 toBurn: toBurn, 1281 toStake: toStake, 1282 complexity: complexity, 1283 1284 // Initialize the return values with empty slices to preserve backward 1285 // compatibility of the json representation of transactions with no 1286 // inputs or outputs. 1287 inputs: make([]*avax.TransferableInput, 0), 1288 changeOutputs: make([]*avax.TransferableOutput, 0), 1289 stakeOutputs: make([]*avax.TransferableOutput, 0), 1290 } 1291 1292 utxosByLocktime := splitByLocktime(utxos, minIssuanceTime) 1293 for _, utxo := range utxosByLocktime.locked { 1294 assetID := utxo.AssetID() 1295 if !s.shouldConsumeLockedAsset(assetID) { 1296 continue 1297 } 1298 1299 out, locktime, err := unwrapOutput(utxo.Out) 1300 if err != nil { 1301 return nil, nil, nil, err 1302 } 1303 1304 inputSigIndices, ok := common.MatchOwners(&out.OutputOwners, addrs, minIssuanceTime) 1305 if !ok { 1306 // We couldn't spend this UTXO, so we skip to the next one 1307 continue 1308 } 1309 1310 err = s.addInput(&avax.TransferableInput{ 1311 UTXOID: utxo.UTXOID, 1312 Asset: utxo.Asset, 1313 In: &stakeable.LockIn{ 1314 Locktime: locktime, 1315 TransferableIn: &secp256k1fx.TransferInput{ 1316 Amt: out.Amt, 1317 Input: secp256k1fx.Input{ 1318 SigIndices: inputSigIndices, 1319 }, 1320 }, 1321 }, 1322 }) 1323 if err != nil { 1324 return nil, nil, nil, err 1325 } 1326 1327 excess := s.consumeLockedAsset(assetID, out.Amt) 1328 err = s.addStakedOutput(&avax.TransferableOutput{ 1329 Asset: utxo.Asset, 1330 Out: &stakeable.LockOut{ 1331 Locktime: locktime, 1332 TransferableOut: &secp256k1fx.TransferOutput{ 1333 Amt: out.Amt - excess, 1334 OutputOwners: out.OutputOwners, 1335 }, 1336 }, 1337 }) 1338 if err != nil { 1339 return nil, nil, nil, err 1340 } 1341 1342 if excess == 0 { 1343 continue 1344 } 1345 1346 // This input had extra value, so some of it must be returned 1347 err = s.addChangeOutput(&avax.TransferableOutput{ 1348 Asset: utxo.Asset, 1349 Out: &stakeable.LockOut{ 1350 Locktime: locktime, 1351 TransferableOut: &secp256k1fx.TransferOutput{ 1352 Amt: excess, 1353 OutputOwners: out.OutputOwners, 1354 }, 1355 }, 1356 }) 1357 if err != nil { 1358 return nil, nil, nil, err 1359 } 1360 } 1361 1362 // Add all the remaining stake amounts assuming unlocked UTXOs. 1363 for assetID, amount := range s.toStake { 1364 if amount == 0 { 1365 continue 1366 } 1367 1368 err = s.addStakedOutput(&avax.TransferableOutput{ 1369 Asset: avax.Asset{ 1370 ID: assetID, 1371 }, 1372 Out: &secp256k1fx.TransferOutput{ 1373 Amt: amount, 1374 OutputOwners: *changeOwner, 1375 }, 1376 }) 1377 if err != nil { 1378 return nil, nil, nil, err 1379 } 1380 } 1381 1382 // AVAX is handled last to account for fees. 1383 utxosByAVAXAssetID := splitByAssetID(utxosByLocktime.unlocked, b.context.AVAXAssetID) 1384 for _, utxo := range utxosByAVAXAssetID.other { 1385 assetID := utxo.AssetID() 1386 if !s.shouldConsumeAsset(assetID) { 1387 continue 1388 } 1389 1390 out, _, err := unwrapOutput(utxo.Out) 1391 if err != nil { 1392 return nil, nil, nil, err 1393 } 1394 1395 inputSigIndices, ok := common.MatchOwners(&out.OutputOwners, addrs, minIssuanceTime) 1396 if !ok { 1397 // We couldn't spend this UTXO, so we skip to the next one 1398 continue 1399 } 1400 1401 err = s.addInput(&avax.TransferableInput{ 1402 UTXOID: utxo.UTXOID, 1403 Asset: utxo.Asset, 1404 In: &secp256k1fx.TransferInput{ 1405 Amt: out.Amt, 1406 Input: secp256k1fx.Input{ 1407 SigIndices: inputSigIndices, 1408 }, 1409 }, 1410 }) 1411 if err != nil { 1412 return nil, nil, nil, err 1413 } 1414 1415 excess := s.consumeAsset(assetID, out.Amt) 1416 if excess == 0 { 1417 continue 1418 } 1419 1420 // This input had extra value, so some of it must be returned 1421 err = s.addChangeOutput(&avax.TransferableOutput{ 1422 Asset: utxo.Asset, 1423 Out: &secp256k1fx.TransferOutput{ 1424 Amt: excess, 1425 OutputOwners: *changeOwner, 1426 }, 1427 }) 1428 if err != nil { 1429 return nil, nil, nil, err 1430 } 1431 } 1432 1433 for _, utxo := range utxosByAVAXAssetID.requested { 1434 requiredFee, err := s.calculateFee() 1435 if err != nil { 1436 return nil, nil, nil, err 1437 } 1438 1439 // If we don't need to burn or stake additional AVAX and we have 1440 // consumed enough AVAX to pay the required fee, we should stop 1441 // consuming UTXOs. 1442 if !s.shouldConsumeAsset(b.context.AVAXAssetID) && excessAVAX >= requiredFee { 1443 break 1444 } 1445 1446 out, _, err := unwrapOutput(utxo.Out) 1447 if err != nil { 1448 return nil, nil, nil, err 1449 } 1450 1451 inputSigIndices, ok := common.MatchOwners(&out.OutputOwners, addrs, minIssuanceTime) 1452 if !ok { 1453 // We couldn't spend this UTXO, so we skip to the next one 1454 continue 1455 } 1456 1457 err = s.addInput(&avax.TransferableInput{ 1458 UTXOID: utxo.UTXOID, 1459 Asset: utxo.Asset, 1460 In: &secp256k1fx.TransferInput{ 1461 Amt: out.Amt, 1462 Input: secp256k1fx.Input{ 1463 SigIndices: inputSigIndices, 1464 }, 1465 }, 1466 }) 1467 if err != nil { 1468 return nil, nil, nil, err 1469 } 1470 1471 excess := s.consumeAsset(b.context.AVAXAssetID, out.Amt) 1472 excessAVAX, err = math.Add(excessAVAX, excess) 1473 if err != nil { 1474 return nil, nil, nil, err 1475 } 1476 1477 // If we need to consume additional AVAX, we should be returning the 1478 // change to the change address. 1479 ownerOverride = changeOwner 1480 } 1481 1482 if err := s.verifyAssetsConsumed(); err != nil { 1483 return nil, nil, nil, err 1484 } 1485 1486 requiredFee, err := s.calculateFee() 1487 if err != nil { 1488 return nil, nil, nil, err 1489 } 1490 if excessAVAX < requiredFee { 1491 return nil, nil, nil, fmt.Errorf( 1492 "%w: provided UTXOs needed %d more nAVAX (%q)", 1493 ErrInsufficientFunds, 1494 requiredFee-excessAVAX, 1495 b.context.AVAXAssetID, 1496 ) 1497 } 1498 1499 secpExcessAVAXOutput := &secp256k1fx.TransferOutput{ 1500 Amt: 0, // Populated later if used 1501 OutputOwners: *ownerOverride, 1502 } 1503 excessAVAXOutput := &avax.TransferableOutput{ 1504 Asset: avax.Asset{ 1505 ID: b.context.AVAXAssetID, 1506 }, 1507 Out: secpExcessAVAXOutput, 1508 } 1509 if err := s.addOutputComplexity(excessAVAXOutput); err != nil { 1510 return nil, nil, nil, err 1511 } 1512 1513 requiredFeeWithChange, err := s.calculateFee() 1514 if err != nil { 1515 return nil, nil, nil, err 1516 } 1517 if excessAVAX > requiredFeeWithChange { 1518 // It is worth adding the change output 1519 secpExcessAVAXOutput.Amt = excessAVAX - requiredFeeWithChange 1520 s.changeOutputs = append(s.changeOutputs, excessAVAXOutput) 1521 } 1522 1523 utils.Sort(s.inputs) // sort inputs 1524 avax.SortTransferableOutputs(s.changeOutputs, txs.Codec) // sort the change outputs 1525 avax.SortTransferableOutputs(s.stakeOutputs, txs.Codec) // sort stake outputs 1526 return s.inputs, s.changeOutputs, s.stakeOutputs, nil 1527 } 1528 1529 func (b *builder) authorizeSubnet(subnetID ids.ID, options *common.Options) (*secp256k1fx.Input, error) { 1530 ownerIntf, err := b.backend.GetSubnetOwner(options.Context(), subnetID) 1531 if err != nil { 1532 return nil, fmt.Errorf( 1533 "failed to fetch subnet owner for %q: %w", 1534 subnetID, 1535 err, 1536 ) 1537 } 1538 owner, ok := ownerIntf.(*secp256k1fx.OutputOwners) 1539 if !ok { 1540 return nil, ErrUnknownOwnerType 1541 } 1542 1543 addrs := options.Addresses(b.addrs) 1544 minIssuanceTime := options.MinIssuanceTime() 1545 inputSigIndices, ok := common.MatchOwners(owner, addrs, minIssuanceTime) 1546 if !ok { 1547 // We can't authorize the subnet 1548 return nil, ErrInsufficientAuthorization 1549 } 1550 return &secp256k1fx.Input{ 1551 SigIndices: inputSigIndices, 1552 }, nil 1553 } 1554 1555 func (b *builder) initCtx(tx txs.UnsignedTx) error { 1556 ctx, err := NewSnowContext(b.context.NetworkID, b.context.AVAXAssetID) 1557 if err != nil { 1558 return err 1559 } 1560 1561 tx.InitCtx(ctx) 1562 return nil 1563 } 1564 1565 type spendHelper struct { 1566 weights gas.Dimensions 1567 gasPrice gas.Price 1568 1569 toBurn map[ids.ID]uint64 1570 toStake map[ids.ID]uint64 1571 complexity gas.Dimensions 1572 1573 inputs []*avax.TransferableInput 1574 changeOutputs []*avax.TransferableOutput 1575 stakeOutputs []*avax.TransferableOutput 1576 } 1577 1578 func (s *spendHelper) addInput(input *avax.TransferableInput) error { 1579 newInputComplexity, err := fee.InputComplexity(input) 1580 if err != nil { 1581 return err 1582 } 1583 s.complexity, err = s.complexity.Add(&newInputComplexity) 1584 if err != nil { 1585 return err 1586 } 1587 1588 s.inputs = append(s.inputs, input) 1589 return nil 1590 } 1591 1592 func (s *spendHelper) addChangeOutput(output *avax.TransferableOutput) error { 1593 s.changeOutputs = append(s.changeOutputs, output) 1594 return s.addOutputComplexity(output) 1595 } 1596 1597 func (s *spendHelper) addStakedOutput(output *avax.TransferableOutput) error { 1598 s.stakeOutputs = append(s.stakeOutputs, output) 1599 return s.addOutputComplexity(output) 1600 } 1601 1602 func (s *spendHelper) addOutputComplexity(output *avax.TransferableOutput) error { 1603 newOutputComplexity, err := fee.OutputComplexity(output) 1604 if err != nil { 1605 return err 1606 } 1607 s.complexity, err = s.complexity.Add(&newOutputComplexity) 1608 return err 1609 } 1610 1611 func (s *spendHelper) shouldConsumeLockedAsset(assetID ids.ID) bool { 1612 return s.toStake[assetID] != 0 1613 } 1614 1615 func (s *spendHelper) shouldConsumeAsset(assetID ids.ID) bool { 1616 return s.toBurn[assetID] != 0 || s.shouldConsumeLockedAsset(assetID) 1617 } 1618 1619 func (s *spendHelper) consumeLockedAsset(assetID ids.ID, amount uint64) uint64 { 1620 // Stake any value that should be staked 1621 toStake := min( 1622 s.toStake[assetID], // Amount we still need to stake 1623 amount, // Amount available to stake 1624 ) 1625 s.toStake[assetID] -= toStake 1626 return amount - toStake 1627 } 1628 1629 func (s *spendHelper) consumeAsset(assetID ids.ID, amount uint64) uint64 { 1630 // Burn any value that should be burned 1631 toBurn := min( 1632 s.toBurn[assetID], // Amount we still need to burn 1633 amount, // Amount available to burn 1634 ) 1635 s.toBurn[assetID] -= toBurn 1636 1637 // Stake any remaining value that should be staked 1638 return s.consumeLockedAsset(assetID, amount-toBurn) 1639 } 1640 1641 func (s *spendHelper) calculateFee() (uint64, error) { 1642 gas, err := s.complexity.ToGas(s.weights) 1643 if err != nil { 1644 return 0, err 1645 } 1646 return gas.Cost(s.gasPrice) 1647 } 1648 1649 func (s *spendHelper) verifyAssetsConsumed() error { 1650 for assetID, amount := range s.toStake { 1651 if amount == 0 { 1652 continue 1653 } 1654 1655 return fmt.Errorf( 1656 "%w: provided UTXOs need %d more units of asset %q to stake", 1657 ErrInsufficientFunds, 1658 amount, 1659 assetID, 1660 ) 1661 } 1662 for assetID, amount := range s.toBurn { 1663 if amount == 0 { 1664 continue 1665 } 1666 1667 return fmt.Errorf( 1668 "%w: provided UTXOs need %d more units of asset %q", 1669 ErrInsufficientFunds, 1670 amount, 1671 assetID, 1672 ) 1673 } 1674 return nil 1675 } 1676 1677 type utxosByLocktime struct { 1678 unlocked []*avax.UTXO 1679 locked []*avax.UTXO 1680 } 1681 1682 // splitByLocktime separates the provided UTXOs into two slices: 1683 // 1. UTXOs that are unlocked with the provided issuance time 1684 // 2. UTXOs that are locked with the provided issuance time 1685 func splitByLocktime(utxos []*avax.UTXO, minIssuanceTime uint64) utxosByLocktime { 1686 split := utxosByLocktime{ 1687 unlocked: make([]*avax.UTXO, 0, len(utxos)), 1688 locked: make([]*avax.UTXO, 0, len(utxos)), 1689 } 1690 for _, utxo := range utxos { 1691 if lockedOut, ok := utxo.Out.(*stakeable.LockOut); ok && minIssuanceTime < lockedOut.Locktime { 1692 split.locked = append(split.locked, utxo) 1693 } else { 1694 split.unlocked = append(split.unlocked, utxo) 1695 } 1696 } 1697 return split 1698 } 1699 1700 type utxosByAssetID struct { 1701 requested []*avax.UTXO 1702 other []*avax.UTXO 1703 } 1704 1705 // splitByAssetID separates the provided UTXOs into two slices: 1706 // 1. UTXOs with the provided assetID 1707 // 2. UTXOs with a different assetID 1708 func splitByAssetID(utxos []*avax.UTXO, assetID ids.ID) utxosByAssetID { 1709 split := utxosByAssetID{ 1710 requested: make([]*avax.UTXO, 0, len(utxos)), 1711 other: make([]*avax.UTXO, 0, len(utxos)), 1712 } 1713 for _, utxo := range utxos { 1714 if utxo.AssetID() == assetID { 1715 split.requested = append(split.requested, utxo) 1716 } else { 1717 split.other = append(split.other, utxo) 1718 } 1719 } 1720 return split 1721 } 1722 1723 // unwrapOutput returns the *secp256k1fx.TransferOutput that was, potentially, 1724 // wrapped by a *stakeable.LockOut. 1725 // 1726 // If the output was stakeable and locked, the locktime is returned. Otherwise, 1727 // the locktime returned will be 0. 1728 // 1729 // If the output is not a, potentially wrapped, *secp256k1fx.TransferOutput, an 1730 // error is returned. 1731 func unwrapOutput(output verify.State) (*secp256k1fx.TransferOutput, uint64, error) { 1732 var locktime uint64 1733 if lockedOut, ok := output.(*stakeable.LockOut); ok { 1734 output = lockedOut.TransferableOut 1735 locktime = lockedOut.Locktime 1736 } 1737 1738 unwrappedOutput, ok := output.(*secp256k1fx.TransferOutput) 1739 if !ok { 1740 return nil, 0, ErrUnknownOutputType 1741 } 1742 return unwrappedOutput, locktime, nil 1743 }