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

     1  package migrations
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/onflow/cadence/runtime/common"
     7  	"github.com/onflow/cadence/runtime/interpreter"
     8  
     9  	"github.com/onflow/flow-go/cmd/util/ledger/reporters"
    10  	"github.com/onflow/flow-go/cmd/util/ledger/util/registers"
    11  	"github.com/onflow/flow-go/ledger"
    12  	"github.com/onflow/flow-go/model/flow"
    13  )
    14  
    15  type diffKind int
    16  
    17  const (
    18  	storageMapExistDiffKind        diffKind = iota // Storage map only exists in one state
    19  	storageMapKeyDiffKind                          // Storage map keys are different
    20  	storageMapValueDiffKind                        // Storage map values are different (only with verbose logging)
    21  	cadenceValueDiffKind                           // Cadence values are different
    22  	cadenceValueTypeDiffKind                       // Cadence value types are different
    23  	cadenceValueStaticTypeDiffKind                 // Cadence value static types are different
    24  )
    25  
    26  var diffKindString = map[diffKind]string{
    27  	storageMapExistDiffKind:        "storage_map_exist_diff",
    28  	storageMapKeyDiffKind:          "storage_map_key_diff",
    29  	storageMapValueDiffKind:        "storage_map_value_diff",
    30  	cadenceValueDiffKind:           "cadence_value_diff",
    31  	cadenceValueTypeDiffKind:       "cadence_value_type_diff",
    32  	cadenceValueStaticTypeDiffKind: "cadence_value_static_type_diff",
    33  }
    34  
    35  type diffErrorKind int
    36  
    37  const (
    38  	abortErrorKind diffErrorKind = iota
    39  	storageMapKeyNotImplementingStorageMapKeyDiffErrorKind
    40  	cadenceValueNotImplementEquatableValueDiffErrorKind
    41  )
    42  
    43  var diffErrorKindString = map[diffErrorKind]string{
    44  	abortErrorKind: "error_diff_failed",
    45  	storageMapKeyNotImplementingStorageMapKeyDiffErrorKind: "error_storage_map_key_not_implemeting_StorageMapKey",
    46  	cadenceValueNotImplementEquatableValueDiffErrorKind:    "error_cadence_value_not_implementing_EquatableValue",
    47  }
    48  
    49  type diffError struct {
    50  	Address string
    51  	Kind    string
    52  	Msg     string
    53  }
    54  
    55  type diffProblem struct {
    56  	Address string
    57  	Domain  string
    58  	Kind    string
    59  	Msg     string
    60  	Trace   string `json:",omitempty"`
    61  }
    62  
    63  type difference struct {
    64  	Address            string
    65  	Domain             string
    66  	Kind               string
    67  	Msg                string
    68  	Trace              string `json:",omitempty"`
    69  	OldValue           string `json:",omitempty"`
    70  	NewValue           string `json:",omitempty"`
    71  	OldValueStaticType string `json:",omitempty"`
    72  	NewValueStaticType string `json:",omitempty"`
    73  }
    74  
    75  type CadenceValueDiffReporter struct {
    76  	address        common.Address
    77  	chainID        flow.ChainID
    78  	reportWriter   reporters.ReportWriter
    79  	verboseLogging bool
    80  	nWorkers       int
    81  }
    82  
    83  func NewCadenceValueDiffReporter(
    84  	address common.Address,
    85  	chainID flow.ChainID,
    86  	rw reporters.ReportWriter,
    87  	verboseLogging bool,
    88  	nWorkers int,
    89  ) *CadenceValueDiffReporter {
    90  	return &CadenceValueDiffReporter{
    91  		address:        address,
    92  		chainID:        chainID,
    93  		reportWriter:   rw,
    94  		verboseLogging: verboseLogging,
    95  		nWorkers:       nWorkers,
    96  	}
    97  }
    98  
    99  func (dr *CadenceValueDiffReporter) newStorageRuntime(
   100  	payloads []*ledger.Payload,
   101  ) (
   102  	*InterpreterMigrationRuntime,
   103  	registers.Registers,
   104  	error,
   105  ) {
   106  	registersByAccount, err := registers.NewByAccountFromPayloads(payloads)
   107  	if err != nil {
   108  		return nil, nil, err
   109  	}
   110  
   111  	// TODO: maybe make read-only again
   112  	runtimeInterface, err := NewInterpreterMigrationRuntime(
   113  		registersByAccount,
   114  		dr.chainID,
   115  		InterpreterMigrationRuntimeConfig{},
   116  	)
   117  	if err != nil {
   118  		return nil, nil, err
   119  	}
   120  
   121  	return runtimeInterface, registersByAccount, nil
   122  }
   123  
   124  func (dr *CadenceValueDiffReporter) DiffStates(oldPayloads, newPayloads []*ledger.Payload, domains []string) {
   125  	// Create all the runtime components we need for comparing Cadence values.
   126  	oldRuntime, oldRegs, err := dr.newStorageRuntime(oldPayloads)
   127  	if err != nil {
   128  		dr.reportWriter.Write(
   129  			diffError{
   130  				Address: dr.address.Hex(),
   131  				Kind:    diffErrorKindString[abortErrorKind],
   132  				Msg:     fmt.Sprintf("failed to create runtime with old state payloads: %s", err),
   133  			})
   134  		return
   135  	}
   136  
   137  	err = loadAtreeSlabsInStorage(oldRuntime.Storage, oldRegs, dr.nWorkers)
   138  	if err != nil {
   139  		dr.reportWriter.Write(
   140  			diffError{
   141  				Address: dr.address.Hex(),
   142  				Kind:    diffErrorKindString[abortErrorKind],
   143  				Msg:     fmt.Sprintf("failed to preload old state atree payloads: %s", err),
   144  			})
   145  		return
   146  	}
   147  
   148  	newRuntime, newRegs, err := dr.newStorageRuntime(newPayloads)
   149  	if err != nil {
   150  		dr.reportWriter.Write(
   151  			diffError{
   152  				Address: dr.address.Hex(),
   153  				Kind:    diffErrorKindString[abortErrorKind],
   154  				Msg:     fmt.Sprintf("failed to create runtime with new state payloads: %s", err),
   155  			})
   156  		return
   157  	}
   158  
   159  	err = loadAtreeSlabsInStorage(newRuntime.Storage, newRegs, dr.nWorkers)
   160  	if err != nil {
   161  		dr.reportWriter.Write(
   162  			diffError{
   163  				Address: dr.address.Hex(),
   164  				Kind:    diffErrorKindString[abortErrorKind],
   165  				Msg:     fmt.Sprintf("failed to preload new state atree payloads: %s", err),
   166  			})
   167  		return
   168  	}
   169  
   170  	// Iterate through all domains and compare cadence values.
   171  	for _, domain := range domains {
   172  		dr.diffStorageDomain(oldRuntime, newRuntime, domain)
   173  	}
   174  }
   175  
   176  func (dr *CadenceValueDiffReporter) diffStorageDomain(
   177  	oldRuntime *InterpreterMigrationRuntime,
   178  	newRuntime *InterpreterMigrationRuntime,
   179  	domain string,
   180  ) {
   181  	oldStorageMap := oldRuntime.Storage.GetStorageMap(dr.address, domain, false)
   182  	newStorageMap := newRuntime.Storage.GetStorageMap(dr.address, domain, false)
   183  
   184  	if oldStorageMap == nil && newStorageMap == nil {
   185  		// No storage maps for this domain.
   186  		return
   187  	}
   188  
   189  	if oldStorageMap == nil && newStorageMap != nil {
   190  		dr.reportWriter.Write(
   191  			difference{
   192  				Address: dr.address.Hex(),
   193  				Domain:  domain,
   194  				Kind:    diffKindString[storageMapExistDiffKind],
   195  				Msg: fmt.Sprintf(
   196  					"old storage map doesn't exist, new storage map has %d elements with keys %v",
   197  					newStorageMap.Count(),
   198  					getStorageMapKeys(newStorageMap),
   199  				),
   200  			})
   201  
   202  		return
   203  	}
   204  
   205  	if oldStorageMap != nil && newStorageMap == nil {
   206  		dr.reportWriter.Write(
   207  			difference{
   208  				Address: dr.address.Hex(),
   209  				Domain:  domain,
   210  				Kind:    diffKindString[storageMapExistDiffKind],
   211  				Msg: fmt.Sprintf(
   212  					"new storage map doesn't exist, old storage map has %d elements with keys %v",
   213  					oldStorageMap.Count(),
   214  					getStorageMapKeys(oldStorageMap),
   215  				),
   216  			})
   217  
   218  		return
   219  	}
   220  
   221  	oldKeys := getStorageMapKeys(oldStorageMap)
   222  	newKeys := getStorageMapKeys(newStorageMap)
   223  
   224  	onlyOldKeys, onlyNewKeys, sharedKeys := diff(oldKeys, newKeys)
   225  
   226  	// Log keys only present in old storage map
   227  	if len(onlyOldKeys) > 0 {
   228  		dr.reportWriter.Write(
   229  			difference{
   230  				Address: dr.address.Hex(),
   231  				Domain:  domain,
   232  				Kind:    diffKindString[storageMapKeyDiffKind],
   233  				Msg: fmt.Sprintf(
   234  					"old storage map has %d elements with keys %v, that are not present in new storge map",
   235  					len(onlyOldKeys),
   236  					onlyOldKeys,
   237  				),
   238  			})
   239  	}
   240  
   241  	// Log keys only present in new storage map
   242  	if len(onlyNewKeys) > 0 {
   243  		dr.reportWriter.Write(
   244  			difference{
   245  				Address: dr.address.Hex(),
   246  				Domain:  domain,
   247  				Kind:    diffKindString[storageMapKeyDiffKind],
   248  				Msg: fmt.Sprintf(
   249  					"new storage map has %d elements with keys %v, that are not present in old storge map",
   250  					len(onlyNewKeys),
   251  					onlyNewKeys,
   252  				),
   253  			})
   254  	}
   255  
   256  	// Compare elements present in both storage maps
   257  	for _, key := range sharedKeys {
   258  
   259  		trace := fmt.Sprintf("%s[%v]", domain, key)
   260  
   261  		var mapKey interpreter.StorageMapKey
   262  
   263  		switch key := key.(type) {
   264  		case interpreter.StringAtreeValue:
   265  			mapKey = interpreter.StringStorageMapKey(key)
   266  
   267  		case interpreter.Uint64AtreeValue:
   268  			mapKey = interpreter.Uint64StorageMapKey(key)
   269  
   270  		case interpreter.StringStorageMapKey:
   271  			mapKey = key
   272  
   273  		case interpreter.Uint64StorageMapKey:
   274  			mapKey = key
   275  
   276  		default:
   277  			dr.reportWriter.Write(
   278  				diffProblem{
   279  					Address: dr.address.Hex(),
   280  					Domain:  domain,
   281  					Kind:    diffErrorKindString[storageMapKeyNotImplementingStorageMapKeyDiffErrorKind],
   282  					Trace:   trace,
   283  					Msg: fmt.Sprintf(
   284  						"invalid storage map key %v (%T), expected interpreter.StorageMapKey",
   285  						key,
   286  						key,
   287  					),
   288  				})
   289  			continue
   290  		}
   291  
   292  		oldValue := oldStorageMap.ReadValue(nil, mapKey)
   293  
   294  		newValue := newStorageMap.ReadValue(nil, mapKey)
   295  
   296  		hasDifference := dr.diffValues(
   297  			oldRuntime.Interpreter,
   298  			oldValue,
   299  			newRuntime.Interpreter,
   300  			newValue,
   301  			domain,
   302  			trace,
   303  		)
   304  		if hasDifference {
   305  			if dr.verboseLogging {
   306  				// Log potentially large values at top level only when verbose logging is enabled.
   307  				dr.reportWriter.Write(
   308  					difference{
   309  						Address:            dr.address.Hex(),
   310  						Domain:             domain,
   311  						Kind:               diffKindString[storageMapValueDiffKind],
   312  						Msg:                "storage map elements are different",
   313  						Trace:              trace,
   314  						OldValue:           oldValue.String(),
   315  						NewValue:           newValue.String(),
   316  						OldValueStaticType: oldValue.StaticType(oldRuntime.Interpreter).String(),
   317  						NewValueStaticType: newValue.StaticType(newRuntime.Interpreter).String(),
   318  					})
   319  			}
   320  		}
   321  
   322  	}
   323  }
   324  
   325  func (dr *CadenceValueDiffReporter) diffValues(
   326  	vInterpreter *interpreter.Interpreter,
   327  	v interpreter.Value,
   328  	otherInterpreter *interpreter.Interpreter,
   329  	other interpreter.Value,
   330  	domain string,
   331  	trace string,
   332  ) (hasDifference bool) {
   333  	switch v := v.(type) {
   334  	case *interpreter.ArrayValue:
   335  		return dr.diffCadenceArrayValue(vInterpreter, v, otherInterpreter, other, domain, trace)
   336  
   337  	case *interpreter.CompositeValue:
   338  		return dr.diffCadenceCompositeValue(vInterpreter, v, otherInterpreter, other, domain, trace)
   339  
   340  	case *interpreter.DictionaryValue:
   341  		return dr.diffCadenceDictionaryValue(vInterpreter, v, otherInterpreter, other, domain, trace)
   342  
   343  	case *interpreter.SomeValue:
   344  		return dr.diffCadenceSomeValue(vInterpreter, v, otherInterpreter, other, domain, trace)
   345  
   346  	default:
   347  		oldValue, ok := v.(interpreter.EquatableValue)
   348  		if !ok {
   349  			dr.reportWriter.Write(
   350  				diffProblem{
   351  					Address: dr.address.Hex(),
   352  					Domain:  domain,
   353  					Kind:    diffErrorKindString[cadenceValueNotImplementEquatableValueDiffErrorKind],
   354  					Trace:   trace,
   355  					Msg:     fmt.Sprintf("old value doesn't implement interpreter.EquatableValue: %s (%T)", oldValue.String(), oldValue),
   356  				})
   357  			return true
   358  		}
   359  
   360  		if !oldValue.Equal(nil, interpreter.EmptyLocationRange, other) {
   361  			dr.reportWriter.Write(
   362  				difference{
   363  					Address:            dr.address.Hex(),
   364  					Domain:             domain,
   365  					Kind:               diffKindString[cadenceValueDiffKind],
   366  					Msg:                fmt.Sprintf("values differ: %T vs %T", oldValue, other),
   367  					Trace:              trace,
   368  					OldValue:           v.String(),
   369  					NewValue:           other.String(),
   370  					OldValueStaticType: v.StaticType(vInterpreter).String(),
   371  					NewValueStaticType: other.StaticType(otherInterpreter).String(),
   372  				})
   373  			return true
   374  		}
   375  	}
   376  
   377  	return false
   378  }
   379  
   380  func (dr *CadenceValueDiffReporter) diffCadenceSomeValue(
   381  	vInterpreter *interpreter.Interpreter,
   382  	v *interpreter.SomeValue,
   383  	otherInterpreter *interpreter.Interpreter,
   384  	other interpreter.Value,
   385  	domain string,
   386  	trace string,
   387  ) (hasDifference bool) {
   388  	otherSome, ok := other.(*interpreter.SomeValue)
   389  	if !ok {
   390  		dr.reportWriter.Write(
   391  			difference{
   392  				Address: dr.address.Hex(),
   393  				Domain:  domain,
   394  				Kind:    diffKindString[cadenceValueTypeDiffKind],
   395  				Trace:   trace,
   396  				Msg:     fmt.Sprintf("types differ: %T != %T", v, other),
   397  			})
   398  		return true
   399  	}
   400  
   401  	innerValue := v.InnerValue(vInterpreter, interpreter.EmptyLocationRange)
   402  
   403  	otherInnerValue := otherSome.InnerValue(otherInterpreter, interpreter.EmptyLocationRange)
   404  
   405  	return dr.diffValues(vInterpreter, innerValue, otherInterpreter, otherInnerValue, domain, trace)
   406  }
   407  
   408  func (dr *CadenceValueDiffReporter) diffCadenceArrayValue(
   409  	vInterpreter *interpreter.Interpreter,
   410  	v *interpreter.ArrayValue,
   411  	otherInterpreter *interpreter.Interpreter,
   412  	other interpreter.Value,
   413  	domain string,
   414  	trace string,
   415  ) (hasDifference bool) {
   416  	otherArray, ok := other.(*interpreter.ArrayValue)
   417  	if !ok {
   418  		dr.reportWriter.Write(
   419  			difference{
   420  				Address: dr.address.Hex(),
   421  				Domain:  domain,
   422  				Kind:    diffKindString[cadenceValueTypeDiffKind],
   423  				Trace:   trace,
   424  				Msg:     fmt.Sprintf("types differ: %T != %T", v, other),
   425  			})
   426  		return true
   427  	}
   428  
   429  	if v.Type == nil && otherArray.Type != nil {
   430  		hasDifference = true
   431  
   432  		dr.reportWriter.Write(
   433  			difference{
   434  				Address: dr.address.Hex(),
   435  				Domain:  domain,
   436  				Kind:    diffKindString[cadenceValueStaticTypeDiffKind],
   437  				Trace:   trace,
   438  				Msg:     fmt.Sprintf("array static types differ: nil != %s", otherArray.Type),
   439  			})
   440  	}
   441  
   442  	if v.Type != nil && otherArray.Type == nil {
   443  		hasDifference = true
   444  
   445  		dr.reportWriter.Write(
   446  			difference{
   447  				Address: dr.address.Hex(),
   448  				Domain:  domain,
   449  				Kind:    diffKindString[cadenceValueStaticTypeDiffKind],
   450  				Trace:   trace,
   451  				Msg:     fmt.Sprintf("array static types differ: %s != nil", v.Type),
   452  			})
   453  	}
   454  
   455  	if v.Type != nil && otherArray.Type != nil && !v.Type.Equal(otherArray.Type) {
   456  		hasDifference = true
   457  
   458  		dr.reportWriter.Write(
   459  			difference{
   460  				Address: dr.address.Hex(),
   461  				Domain:  domain,
   462  				Kind:    diffKindString[cadenceValueStaticTypeDiffKind],
   463  				Trace:   trace,
   464  				Msg:     fmt.Sprintf("array static types differ: %s != %s", v.Type, otherArray.Type),
   465  			})
   466  	}
   467  
   468  	count := v.Count()
   469  	if count != otherArray.Count() {
   470  		hasDifference = true
   471  
   472  		d := difference{
   473  			Address: dr.address.Hex(),
   474  			Domain:  domain,
   475  			Kind:    diffKindString[cadenceValueDiffKind],
   476  			Trace:   trace,
   477  			Msg:     fmt.Sprintf("array counts differ: %d != %d", count, otherArray.Count()),
   478  		}
   479  
   480  		if dr.verboseLogging {
   481  			d.OldValue = v.String()
   482  			d.NewValue = other.String()
   483  		}
   484  
   485  		dr.reportWriter.Write(d)
   486  	}
   487  
   488  	// Compare array elements
   489  	for i := 0; i < min(count, otherArray.Count()); i++ {
   490  		element := v.Get(vInterpreter, interpreter.EmptyLocationRange, i)
   491  		otherElement := otherArray.Get(otherInterpreter, interpreter.EmptyLocationRange, i)
   492  
   493  		elementTrace := fmt.Sprintf("%s[%d]", trace, i)
   494  		elementHasDifference := dr.diffValues(vInterpreter, element, otherInterpreter, otherElement, domain, elementTrace)
   495  		if elementHasDifference {
   496  			hasDifference = true
   497  		}
   498  	}
   499  
   500  	return hasDifference
   501  }
   502  
   503  func (dr *CadenceValueDiffReporter) diffCadenceCompositeValue(
   504  	vInterpreter *interpreter.Interpreter,
   505  	v *interpreter.CompositeValue,
   506  	otherInterpreter *interpreter.Interpreter,
   507  	other interpreter.Value,
   508  	domain string,
   509  	trace string,
   510  ) (hasDifference bool) {
   511  	otherComposite, ok := other.(*interpreter.CompositeValue)
   512  	if !ok {
   513  		dr.reportWriter.Write(
   514  			difference{
   515  				Address: dr.address.Hex(),
   516  				Domain:  domain,
   517  				Kind:    diffKindString[cadenceValueTypeDiffKind],
   518  				Trace:   trace,
   519  				Msg:     fmt.Sprintf("types differ: %T != %T", v, other),
   520  			})
   521  		return true
   522  	}
   523  
   524  	if !v.StaticType(vInterpreter).Equal(otherComposite.StaticType(otherInterpreter)) {
   525  		hasDifference = true
   526  
   527  		dr.reportWriter.Write(
   528  			difference{
   529  				Address: dr.address.Hex(),
   530  				Domain:  domain,
   531  				Kind:    diffKindString[cadenceValueStaticTypeDiffKind],
   532  				Trace:   trace,
   533  				Msg: fmt.Sprintf(
   534  					"composite static types differ: %s != %s",
   535  					v.StaticType(vInterpreter),
   536  					otherComposite.StaticType(otherInterpreter)),
   537  			})
   538  	}
   539  
   540  	if v.Kind != otherComposite.Kind {
   541  		hasDifference = true
   542  
   543  		dr.reportWriter.Write(
   544  			difference{
   545  				Address: dr.address.Hex(),
   546  				Domain:  domain,
   547  				Kind:    diffKindString[cadenceValueStaticTypeDiffKind],
   548  				Trace:   trace,
   549  				Msg: fmt.Sprintf(
   550  					"composite kinds differ: %d != %d",
   551  					v.Kind,
   552  					otherComposite.Kind,
   553  				),
   554  			})
   555  	}
   556  
   557  	oldFieldNames := make([]string, 0, v.FieldCount())
   558  	v.ForEachFieldName(func(fieldName string) bool {
   559  		oldFieldNames = append(oldFieldNames, fieldName)
   560  		return true
   561  	})
   562  
   563  	newFieldNames := make([]string, 0, otherComposite.FieldCount())
   564  	otherComposite.ForEachFieldName(func(fieldName string) bool {
   565  		newFieldNames = append(newFieldNames, fieldName)
   566  		return true
   567  	})
   568  
   569  	onlyOldFieldNames, onlyNewFieldNames, sharedFieldNames := diff(oldFieldNames, newFieldNames)
   570  
   571  	// Log field names only present in old composite value
   572  	if len(onlyOldFieldNames) > 0 {
   573  		hasDifference = true
   574  
   575  		dr.reportWriter.Write(
   576  			difference{
   577  				Address: dr.address.Hex(),
   578  				Domain:  domain,
   579  				Kind:    diffKindString[cadenceValueDiffKind],
   580  				Trace:   trace,
   581  				Msg: fmt.Sprintf(
   582  					"old composite value has %d fields with keys %v, that are not present in new composite value",
   583  					len(onlyOldFieldNames),
   584  					onlyOldFieldNames,
   585  				),
   586  			})
   587  	}
   588  
   589  	// Log field names only present in new composite value
   590  	if len(onlyNewFieldNames) > 0 {
   591  		hasDifference = true
   592  
   593  		dr.reportWriter.Write(
   594  			difference{
   595  				Address: dr.address.Hex(),
   596  				Domain:  domain,
   597  				Kind:    diffKindString[cadenceValueDiffKind],
   598  				Trace:   trace,
   599  				Msg: fmt.Sprintf(
   600  					"new composite value has %d fields with keys %v, that are not present in old composite value",
   601  					len(onlyNewFieldNames),
   602  					onlyNewFieldNames,
   603  				),
   604  			})
   605  	}
   606  
   607  	// Compare fields in both composite values
   608  	for _, fieldName := range sharedFieldNames {
   609  		fieldValue := v.GetField(vInterpreter, interpreter.EmptyLocationRange, fieldName)
   610  		otherFieldValue := otherComposite.GetField(otherInterpreter, interpreter.EmptyLocationRange, fieldName)
   611  
   612  		fieldTrace := fmt.Sprintf("%s.%s", trace, fieldName)
   613  		fieldHasDifference := dr.diffValues(vInterpreter, fieldValue, otherInterpreter, otherFieldValue, domain, fieldTrace)
   614  		if fieldHasDifference {
   615  			hasDifference = true
   616  		}
   617  	}
   618  
   619  	return hasDifference
   620  }
   621  
   622  func (dr *CadenceValueDiffReporter) diffCadenceDictionaryValue(
   623  	vInterpreter *interpreter.Interpreter,
   624  	v *interpreter.DictionaryValue,
   625  	otherInterpreter *interpreter.Interpreter,
   626  	other interpreter.Value,
   627  	domain string,
   628  	trace string,
   629  ) (hasDifference bool) {
   630  	otherDictionary, ok := other.(*interpreter.DictionaryValue)
   631  	if !ok {
   632  		dr.reportWriter.Write(
   633  			difference{
   634  				Address: dr.address.Hex(),
   635  				Domain:  domain,
   636  				Kind:    diffKindString[cadenceValueTypeDiffKind],
   637  				Trace:   trace,
   638  				Msg:     fmt.Sprintf("types differ: %T != %T", v, other),
   639  			})
   640  		return true
   641  	}
   642  
   643  	if !v.Type.Equal(otherDictionary.Type) {
   644  		hasDifference = true
   645  
   646  		dr.reportWriter.Write(
   647  			difference{
   648  				Address: dr.address.Hex(),
   649  				Domain:  domain,
   650  				Kind:    diffKindString[cadenceValueStaticTypeDiffKind],
   651  				Trace:   trace,
   652  				Msg: fmt.Sprintf(
   653  					"dict static types differ: %s != %s",
   654  					v.Type,
   655  					otherDictionary.Type),
   656  			})
   657  	}
   658  
   659  	oldKeys := make([]interpreter.Value, 0, v.Count())
   660  	v.IterateKeys(vInterpreter, interpreter.EmptyLocationRange, func(key interpreter.Value) (resume bool) {
   661  		oldKeys = append(oldKeys, key)
   662  		return true
   663  	})
   664  
   665  	newKeys := make([]interpreter.Value, 0, otherDictionary.Count())
   666  	otherDictionary.IterateKeys(otherInterpreter, interpreter.EmptyLocationRange, func(key interpreter.Value) (resume bool) {
   667  		newKeys = append(newKeys, key)
   668  		return true
   669  	})
   670  
   671  	onlyOldKeys, onlyNewKeys, sharedKeys := diffCadenceValues(oldKeys, newKeys)
   672  
   673  	// Log keys only present in old dict value
   674  	if len(onlyOldKeys) > 0 {
   675  		hasDifference = true
   676  
   677  		dr.reportWriter.Write(
   678  			difference{
   679  				Address: dr.address.Hex(),
   680  				Domain:  domain,
   681  				Kind:    diffKindString[cadenceValueDiffKind],
   682  				Trace:   trace,
   683  				Msg: fmt.Sprintf(
   684  					"old dict value has %d elements with keys %v, that are not present in new dict value",
   685  					len(onlyOldKeys),
   686  					onlyOldKeys,
   687  				),
   688  			})
   689  	}
   690  
   691  	// Log field names only present in new composite value
   692  	if len(onlyNewKeys) > 0 {
   693  		hasDifference = true
   694  
   695  		dr.reportWriter.Write(
   696  			difference{
   697  				Address: dr.address.Hex(),
   698  				Domain:  domain,
   699  				Kind:    diffKindString[cadenceValueDiffKind],
   700  				Trace:   trace,
   701  				Msg: fmt.Sprintf(
   702  					"new dict value has %d elements with keys %v, that are not present in old dict value",
   703  					len(onlyNewKeys),
   704  					onlyNewKeys,
   705  				),
   706  			})
   707  	}
   708  
   709  	// Compare elements in both dict values
   710  	for _, key := range sharedKeys {
   711  		valueTrace := fmt.Sprintf("%s[%v]", trace, key)
   712  
   713  		oldValue, _ := v.Get(vInterpreter, interpreter.EmptyLocationRange, key)
   714  
   715  		newValue, _ := otherDictionary.Get(otherInterpreter, interpreter.EmptyLocationRange, key)
   716  
   717  		elementHasDifference := dr.diffValues(vInterpreter, oldValue, otherInterpreter, newValue, domain, valueTrace)
   718  		if elementHasDifference {
   719  			hasDifference = true
   720  		}
   721  	}
   722  
   723  	return hasDifference
   724  }
   725  
   726  func getStorageMapKeys(storageMap *interpreter.StorageMap) []any {
   727  	keys := make([]any, 0, storageMap.Count())
   728  
   729  	iter := storageMap.Iterator(nil)
   730  	for {
   731  		key := iter.NextKey()
   732  		if key == nil {
   733  			break
   734  		}
   735  		keys = append(keys, key)
   736  	}
   737  
   738  	return keys
   739  }
   740  
   741  func diff[T comparable](old, new []T) (onlyOld, onlyNew, shared []T) {
   742  	onlyOld = make([]T, 0, len(old))
   743  	onlyNew = make([]T, 0, len(new))
   744  	shared = make([]T, 0, min(len(old), len(new)))
   745  
   746  	sharedNew := make([]bool, len(new))
   747  
   748  	for _, o := range old {
   749  		found := false
   750  
   751  		for i, n := range new {
   752  			if o == n {
   753  				shared = append(shared, o)
   754  				found = true
   755  				sharedNew[i] = true
   756  				break
   757  			}
   758  		}
   759  
   760  		if !found {
   761  			onlyOld = append(onlyOld, o)
   762  		}
   763  	}
   764  
   765  	for i, shared := range sharedNew {
   766  		if !shared {
   767  			onlyNew = append(onlyNew, new[i])
   768  		}
   769  	}
   770  
   771  	return
   772  }
   773  
   774  func diffCadenceValues(old, new []interpreter.Value) (onlyOld, onlyNew, shared []interpreter.Value) {
   775  	onlyOld = make([]interpreter.Value, 0, len(old))
   776  	onlyNew = make([]interpreter.Value, 0, len(new))
   777  	shared = make([]interpreter.Value, 0, min(len(old), len(new)))
   778  
   779  	sharedNew := make([]bool, len(new))
   780  
   781  	for _, o := range old {
   782  		found := false
   783  
   784  		for i, n := range new {
   785  			foundShared := false
   786  
   787  			if ev, ok := o.(interpreter.EquatableValue); ok {
   788  				if ev.Equal(nil, interpreter.EmptyLocationRange, n) {
   789  					foundShared = true
   790  				}
   791  			} else {
   792  				if o == n {
   793  					foundShared = true
   794  				}
   795  			}
   796  
   797  			if foundShared {
   798  				shared = append(shared, o)
   799  				found = true
   800  				sharedNew[i] = true
   801  				break
   802  			}
   803  		}
   804  
   805  		if !found {
   806  			onlyOld = append(onlyOld, o)
   807  		}
   808  	}
   809  
   810  	for i, shared := range sharedNew {
   811  		if !shared {
   812  			onlyNew = append(onlyNew, new[i])
   813  		}
   814  	}
   815  
   816  	return
   817  }
   818  
   819  func min(a, b int) int {
   820  	if a <= b {
   821  		return a
   822  	}
   823  	return b
   824  }