github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/cmd/util/ledger/migrations/contract_checking_migration.go (about)

     1  package migrations
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/onflow/cadence/runtime/common"
     8  	"github.com/onflow/cadence/runtime/interpreter"
     9  	"github.com/onflow/cadence/runtime/pretty"
    10  	"github.com/rs/zerolog"
    11  
    12  	"github.com/onflow/flow-go/cmd/util/ledger/reporters"
    13  	"github.com/onflow/flow-go/cmd/util/ledger/util/registers"
    14  	"github.com/onflow/flow-go/model/flow"
    15  )
    16  
    17  const contractCheckingReporterName = "contract-checking"
    18  const contractCountEstimate = 1000
    19  
    20  // NewContractCheckingMigration returns a migration that checks all contracts.
    21  // It parses and checks all contract code and stores the programs in the provided map.
    22  func NewContractCheckingMigration(
    23  	log zerolog.Logger,
    24  	rwf reporters.ReportWriterFactory,
    25  	chainID flow.ChainID,
    26  	verboseErrorOutput bool,
    27  	programs map[common.Location]*interpreter.Program,
    28  ) RegistersMigration {
    29  	return func(registersByAccount *registers.ByAccount) error {
    30  
    31  		reporter := rwf.ReportWriter(contractCheckingReporterName)
    32  
    33  		mr, err := NewInterpreterMigrationRuntime(
    34  			registersByAccount,
    35  			chainID,
    36  			InterpreterMigrationRuntimeConfig{},
    37  		)
    38  		if err != nil {
    39  			return fmt.Errorf("failed to create interpreter migration runtime: %w", err)
    40  		}
    41  
    42  		// Gather all contracts
    43  
    44  		contractsByLocation := make(map[common.Location][]byte, contractCountEstimate)
    45  
    46  		err = registersByAccount.ForEach(func(owner string, key string, value []byte) error {
    47  
    48  			// Skip payloads that are not contract code
    49  			contractName := flow.KeyContractName(key)
    50  			if contractName == "" {
    51  				return nil
    52  			}
    53  
    54  			address := common.Address([]byte(owner))
    55  			code := value
    56  			location := common.AddressLocation{
    57  				Address: address,
    58  				Name:    contractName,
    59  			}
    60  
    61  			contractsByLocation[location] = code
    62  
    63  			return nil
    64  		})
    65  		if err != nil {
    66  			return fmt.Errorf("failed to iterate over registers: %w", err)
    67  		}
    68  
    69  		// Check all contracts
    70  
    71  		for location, code := range contractsByLocation {
    72  			log.Info().Msgf("checking contract %s ...", location)
    73  
    74  			// Check contract code
    75  			const getAndSetProgram = true
    76  			program, err := mr.ContractAdditionHandler.ParseAndCheckProgram(code, location, getAndSetProgram)
    77  			if err != nil {
    78  
    79  				// Pretty print the error
    80  				var builder strings.Builder
    81  				errorPrinter := pretty.NewErrorPrettyPrinter(&builder, false)
    82  
    83  				printErr := errorPrinter.PrettyPrintError(err, location, contractsByLocation)
    84  
    85  				var errorDetails string
    86  				if printErr == nil {
    87  					errorDetails = builder.String()
    88  				} else {
    89  					errorDetails = err.Error()
    90  				}
    91  
    92  				addressLocation := location.(common.AddressLocation)
    93  
    94  				if verboseErrorOutput {
    95  					log.Error().Msgf(
    96  						"error checking contract %s: %s",
    97  						location,
    98  						errorDetails,
    99  					)
   100  				}
   101  
   102  				reporter.Write(contractCheckingFailure{
   103  					AccountAddressHex: addressLocation.Address.HexWithPrefix(),
   104  					ContractName:      addressLocation.Name,
   105  					Error:             errorDetails,
   106  				})
   107  
   108  				continue
   109  			} else {
   110  				// Record the checked program for future use
   111  				programs[location] = program
   112  			}
   113  		}
   114  
   115  		reporter.Close()
   116  
   117  		return nil
   118  	}
   119  }
   120  
   121  type contractCheckingFailure struct {
   122  	AccountAddressHex string `json:"address"`
   123  	ContractName      string `json:"name"`
   124  	Error             string `json:"error"`
   125  }