github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/txs/add_delegator_tx.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package txs
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  
    10  	"github.com/MetalBlockchain/metalgo/ids"
    11  	"github.com/MetalBlockchain/metalgo/snow"
    12  	"github.com/MetalBlockchain/metalgo/utils/constants"
    13  	"github.com/MetalBlockchain/metalgo/utils/crypto/bls"
    14  	"github.com/MetalBlockchain/metalgo/utils/math"
    15  	"github.com/MetalBlockchain/metalgo/vms/components/avax"
    16  	"github.com/MetalBlockchain/metalgo/vms/components/verify"
    17  	"github.com/MetalBlockchain/metalgo/vms/platformvm/fx"
    18  	"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
    19  )
    20  
    21  var (
    22  	_ DelegatorTx     = (*AddDelegatorTx)(nil)
    23  	_ ScheduledStaker = (*AddDelegatorTx)(nil)
    24  
    25  	errDelegatorWeightMismatch = errors.New("delegator weight is not equal to total stake weight")
    26  	errStakeMustBeAVAX         = errors.New("stake must be AVAX")
    27  )
    28  
    29  // AddDelegatorTx is an unsigned addDelegatorTx
    30  type AddDelegatorTx struct {
    31  	// Metadata, inputs and outputs
    32  	BaseTx `serialize:"true"`
    33  	// Describes the delegatee
    34  	Validator `serialize:"true" json:"validator"`
    35  	// Where to send staked tokens when done validating
    36  	StakeOuts []*avax.TransferableOutput `serialize:"true" json:"stake"`
    37  	// Where to send staking rewards when done validating
    38  	DelegationRewardsOwner fx.Owner `serialize:"true" json:"rewardsOwner"`
    39  }
    40  
    41  // InitCtx sets the FxID fields in the inputs and outputs of this
    42  // [UnsignedAddDelegatorTx]. Also sets the [ctx] to the given [vm.ctx] so that
    43  // the addresses can be json marshalled into human readable format
    44  func (tx *AddDelegatorTx) InitCtx(ctx *snow.Context) {
    45  	tx.BaseTx.InitCtx(ctx)
    46  	for _, out := range tx.StakeOuts {
    47  		out.FxID = secp256k1fx.ID
    48  		out.InitCtx(ctx)
    49  	}
    50  	tx.DelegationRewardsOwner.InitCtx(ctx)
    51  }
    52  
    53  func (*AddDelegatorTx) SubnetID() ids.ID {
    54  	return constants.PrimaryNetworkID
    55  }
    56  
    57  func (tx *AddDelegatorTx) NodeID() ids.NodeID {
    58  	return tx.Validator.NodeID
    59  }
    60  
    61  func (*AddDelegatorTx) PublicKey() (*bls.PublicKey, bool, error) {
    62  	return nil, false, nil
    63  }
    64  
    65  func (*AddDelegatorTx) PendingPriority() Priority {
    66  	return PrimaryNetworkDelegatorApricotPendingPriority
    67  }
    68  
    69  func (*AddDelegatorTx) CurrentPriority() Priority {
    70  	return PrimaryNetworkDelegatorCurrentPriority
    71  }
    72  
    73  func (tx *AddDelegatorTx) Stake() []*avax.TransferableOutput {
    74  	return tx.StakeOuts
    75  }
    76  
    77  func (tx *AddDelegatorTx) RewardsOwner() fx.Owner {
    78  	return tx.DelegationRewardsOwner
    79  }
    80  
    81  // SyntacticVerify returns nil iff [tx] is valid
    82  func (tx *AddDelegatorTx) SyntacticVerify(ctx *snow.Context) error {
    83  	switch {
    84  	case tx == nil:
    85  		return ErrNilTx
    86  	case tx.SyntacticallyVerified: // already passed syntactic verification
    87  		return nil
    88  	}
    89  
    90  	if err := tx.BaseTx.SyntacticVerify(ctx); err != nil {
    91  		return err
    92  	}
    93  	if err := verify.All(&tx.Validator, tx.DelegationRewardsOwner); err != nil {
    94  		return fmt.Errorf("failed to verify validator or rewards owner: %w", err)
    95  	}
    96  
    97  	totalStakeWeight := uint64(0)
    98  	for _, out := range tx.StakeOuts {
    99  		if err := out.Verify(); err != nil {
   100  			return fmt.Errorf("output verification failed: %w", err)
   101  		}
   102  		newWeight, err := math.Add64(totalStakeWeight, out.Output().Amount())
   103  		if err != nil {
   104  			return err
   105  		}
   106  		totalStakeWeight = newWeight
   107  
   108  		assetID := out.AssetID()
   109  		if assetID != ctx.AVAXAssetID {
   110  			return fmt.Errorf("%w but is %q", errStakeMustBeAVAX, assetID)
   111  		}
   112  	}
   113  
   114  	switch {
   115  	case !avax.IsSortedTransferableOutputs(tx.StakeOuts, Codec):
   116  		return errOutputsNotSorted
   117  	case totalStakeWeight != tx.Wght:
   118  		return fmt.Errorf("%w, delegator weight %d total stake weight %d",
   119  			errDelegatorWeightMismatch,
   120  			tx.Wght,
   121  			totalStakeWeight,
   122  		)
   123  	}
   124  
   125  	// cache that this is valid
   126  	tx.SyntacticallyVerified = true
   127  	return nil
   128  }
   129  
   130  func (tx *AddDelegatorTx) Visit(visitor Visitor) error {
   131  	return visitor.AddDelegatorTx(tx)
   132  }