Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dictionaries key'd by type are order-dependent #2681

Closed
austinkline opened this issue Jul 26, 2023 · 10 comments · Fixed by #2981
Closed

Dictionaries key'd by type are order-dependent #2681

austinkline opened this issue Jul 26, 2023 · 10 comments · Fixed by #2981
Assignees
Labels
Bug Something isn't working Feedback Storage Breaking Change Breaks existing stored data (needs a storage migration)

Comments

@austinkline
Copy link

austinkline commented Jul 26, 2023

Current Behavior

If I have a dictionary key'd by Type, the order of their interface conformance matters:

import FungibleToken from 0x1

pub fun main(): String {
  let t1 = Type<&{FungibleToken.Provider, FungibleToken.Balance}>()
  let t2 = Type<&{FungibleToken.Balance, FungibleToken.Provider}>()

  let dict: {Type: Bool} = {}
  dict[t1] = true

  assert(dict[t2] != nil, message: "key not found!")
  return "Done"
}

However, if I compare the two types, they are equal:

import FungibleToken from 0x1

pub fun main(): String {
  let t1 = Type<&{FungibleToken.Provider, FungibleToken.Balance}>()
  let t2 = Type<&{FungibleToken.Balance, FungibleToken.Provider}>()

  assert(t1 == t2, message: "types are not equal")

  return "Done"
}

Expected Behavior

I expect that two types which are equal should match to the same key in a dictionary

If I have a type &{A, B} and it is equivalent to &{B, A}, then my expectation is that a dictionary key'd on types should match regardless of their order

Steps To Reproduce

Run this script on the flow emulator:

import FungibleToken from 0xee82856bf20e2aa6

pub fun main(): String {
  let t1 = Type<&{FungibleToken.Provider, FungibleToken.Balance}>()
  let t2 = Type<&{FungibleToken.Balance, FungibleToken.Provider}>()

  // this will pass
  assert(t1 == t2, message: "types are not equal")

  let dict: {Type: Bool} = {}
  dict[t1] = true

  // this will fail
  assert(dict[t2] != nil, message: "key not found!")

  return "done"
}

Environment

- Cadence version: v1.3.2 (flow cli version)
- Network: Emulator
@dsainati1
Copy link
Contributor

In general it would be nice to enforce that for any two types T and U such that T = U, Type<T>() = Type<U>() at runtime.

@turbolent
Copy link
Member

turbolent commented Aug 1, 2023

To fix this, we need to:

  • Fix the encoding and hashing of type sets (e.g. currently, restricted types; in Stable Cadence, interface sets) of interpreter static types to produce a canonical representation, i.e. fixed order. Like in CCF, we can probably use bytewise lexicographic order.
  • Migrate existing data, which involves re-hashing of dictionary keys

@austinkline
Copy link
Author

To fix this, we need to:

  • Fix the encoding and hashing of type sets (e.g. currently, restricted types; in Stable Cadence, interface sets) of interpreter static types to produce a canonical representation, i.e. fixed order. Like in CCF, we can probably use bytewise lexicographic order.
  • Migrate existing data, which involves re-hashing of dictionary keys

Thanks for looking into this! Makes sense that a fix would be split into two parts. Is this something that makes more sense to bundle with stable cadence since migrations are already needed? Or it is able to be done at the next spork after encoding is "fixed" (whenever that may be)?

@dsainati1
Copy link
Contributor

This would (I believe) be a breaking change, since any existing code that relies on these two type keys being different would have different behavior after we change them to be the same.

@austinkline
Copy link
Author

This would (I believe) be a breaking change, since any existing code that relies on these two type keys being different would have different behavior after we change them to be the same.

Definitely, yeah. Does how this gets handled change with Entitlements anyway? How is that migration being planned? Maybe this has some overlap

@dsainati1
Copy link
Contributor

onflow/flips#95 has the discussion of the entitlements migration

@j1010001
Copy link
Member

Fix requires migration as part of Stable Cadence

@turbolent turbolent added the Storage Breaking Change Breaks existing stored data (needs a storage migration) label Aug 10, 2023
@turbolent
Copy link
Member

@dsainati1 It just occurred to me that authorizations of reference types should also be ordered. Could you please open an issue for this?

@j1010001
Copy link
Member

Med effort incl. migration

@turbolent turbolent self-assigned this Nov 27, 2023
@turbolent
Copy link
Member

Add a test case which stores/loads (lookup) across multiple transactions, so encoding. equality comparison, hash value generation, etc. is tested

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Something isn't working Feedback Storage Breaking Change Breaks existing stored data (needs a storage migration)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants