github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/transactionPayerBalanceChecker.go (about)

     1  package fvm
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/onflow/cadence"
     7  
     8  	"github.com/onflow/flow-go/fvm/environment"
     9  	"github.com/onflow/flow-go/fvm/errors"
    10  	"github.com/onflow/flow-go/fvm/storage"
    11  )
    12  
    13  type TransactionPayerBalanceChecker struct{}
    14  
    15  const VerifyPayerBalanceResultTypeCanExecuteTransactionFieldName = "canExecuteTransaction"
    16  const VerifyPayerBalanceResultTypeRequiredBalanceFieldName = "requiredBalance"
    17  const VerifyPayerBalanceResultTypeMaximumTransactionFeesFieldName = "maximumTransactionFees"
    18  
    19  var VerifyPayerBalanceResultType = &cadence.StructType{
    20  	Fields: []cadence.Field{
    21  		{
    22  			Identifier: VerifyPayerBalanceResultTypeCanExecuteTransactionFieldName,
    23  			Type:       cadence.BoolType,
    24  		},
    25  		{
    26  			Identifier: VerifyPayerBalanceResultTypeRequiredBalanceFieldName,
    27  			Type:       cadence.UFix64Type,
    28  		},
    29  		{
    30  			Identifier: VerifyPayerBalanceResultTypeMaximumTransactionFeesFieldName,
    31  			Type:       cadence.UFix64Type,
    32  		},
    33  	},
    34  }
    35  
    36  // decodeVerifyPayerBalanceResult decodes the VerifyPayerBalanceResult struct
    37  // https://github.com/onflow/flow-core-contracts/blob/7c70c6a1d33c2879b60c78e363fa68fc6fce13b9/contracts/FlowFees.cdc#L75
    38  func decodeVerifyPayerBalanceResult(resultValue cadence.Value) (
    39  	canExecuteTransaction cadence.Bool,
    40  	requiredBalance cadence.UFix64,
    41  	maximumTransactionFees cadence.UFix64,
    42  	err error,
    43  ) {
    44  	result, ok := resultValue.(cadence.Struct)
    45  	if !ok {
    46  		return false, 0, 0, fmt.Errorf("invalid VerifyPayerBalanceResult value: not a struct")
    47  	}
    48  
    49  	fields := cadence.FieldsMappedByName(result)
    50  
    51  	canExecuteTransaction, ok = fields[VerifyPayerBalanceResultTypeCanExecuteTransactionFieldName].(cadence.Bool)
    52  	if !ok {
    53  		return false, 0, 0, fmt.Errorf(
    54  			"invalid VerifyPayerBalanceResult field: %s",
    55  			VerifyPayerBalanceResultTypeCanExecuteTransactionFieldName,
    56  		)
    57  	}
    58  
    59  	requiredBalance, ok = fields[VerifyPayerBalanceResultTypeRequiredBalanceFieldName].(cadence.UFix64)
    60  	if !ok {
    61  		return false, 0, 0, fmt.Errorf(
    62  			"invalid VerifyPayerBalanceResult field: %s",
    63  			VerifyPayerBalanceResultTypeRequiredBalanceFieldName,
    64  		)
    65  	}
    66  
    67  	maximumTransactionFees, ok = fields[VerifyPayerBalanceResultTypeMaximumTransactionFeesFieldName].(cadence.UFix64)
    68  	if !ok {
    69  		return false, 0, 0, fmt.Errorf(
    70  			"invalid VerifyPayerBalanceResult field: %s",
    71  			VerifyPayerBalanceResultTypeMaximumTransactionFeesFieldName,
    72  		)
    73  	}
    74  
    75  	return canExecuteTransaction, requiredBalance, maximumTransactionFees, nil
    76  }
    77  
    78  func (_ TransactionPayerBalanceChecker) CheckPayerBalanceAndReturnMaxFees(
    79  	proc *TransactionProcedure,
    80  	txnState storage.TransactionPreparer,
    81  	env environment.Environment,
    82  ) (uint64, error) {
    83  	if !env.TransactionFeesEnabled() {
    84  		// if the transaction fees are not enabled,
    85  		// the payer can pay for the transaction,
    86  		// and the max transaction fees are 0.
    87  		// This is also the condition that gets hit during bootstrapping.
    88  		return 0, nil
    89  	}
    90  
    91  	var resultValue cadence.Value
    92  	var err error
    93  	txnState.RunWithAllLimitsDisabled(func() {
    94  		// Don't meter the payer balance check.
    95  		// It has a static cost, and its cost should be part of the inclusion fees, not part of execution fees.
    96  		resultValue, err = env.CheckPayerBalanceAndGetMaxTxFees(
    97  			proc.Transaction.Payer,
    98  			proc.Transaction.InclusionEffort(),
    99  			uint64(txnState.TotalComputationLimit()),
   100  		)
   101  	})
   102  	if err != nil {
   103  		return 0, errors.NewPayerBalanceCheckFailure(proc.Transaction.Payer, err)
   104  	}
   105  
   106  	payerCanPay, requiredBalance, maxFees, err := decodeVerifyPayerBalanceResult(resultValue)
   107  	if err != nil {
   108  		return 0, errors.NewPayerBalanceCheckFailure(proc.Transaction.Payer, err)
   109  	}
   110  
   111  	if !payerCanPay {
   112  		return 0, errors.NewInsufficientPayerBalanceError(proc.Transaction.Payer, requiredBalance)
   113  	}
   114  
   115  	return uint64(maxFees), nil
   116  }