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 }