onflow / cadence

Cadence, the resource-oriented smart contract programming language 🏃‍♂️
https://cadence-lang.org
Apache License 2.0
534 stars 138 forks source link

Cannot use interface-converted type-requirements in fields #3527

Closed ttruga closed 3 months ago

ttruga commented 3 months ago

Current Behavior

I am trying to port our current contract deployed here to Cadence 1.0.

The Cadence 1.0 specification no longer allows the definition of bare resources inside an interface contract. As a result, I had to change some of the previously defined resources into interfaces. However, after making this change, it is no longer possible to use these resources (now interfaces) as types within the same contract interface.

Expected Behavior

I expect to be able to use "Minter" in the same way it was defined in the pre-Crescendo contract. Specifically, I want to use the following code:

  access(all) resource interface IProducer {
    access(contract) let minters: @{String: Minter} // HERE
  }

  access(all) resource interface Producer: IContent, IProducer {
    access(contract) let minters: @{String: Minter} // AND HERE
  }

Steps To Reproduce

I want to port our current contract deployed here to cadence 1.0.

Due to the new specification, bare resources cannot be defined inside an interface contract. Therefore, I changed the definition of some resources into interfaces.

From this:

  pub resource Minter: IMinter {
    pub let id: String
    pub var lastMintNumber: UInt32
    pub let contentCapability: Capability
    pub fun withdraw(mintNumber: UInt32): @AnyResource{TiblesNFT.INFT}
    pub fun mintNext()
  }

To this:

access(all) resource interface Minter: IMinter { // note here the addition of the word "interface"
    access(all) let id: String
    access(all) var lastMintNumber: UInt32
    // access(all) let contentCapability: Capability<&{TiblesProducer.IContent}>
    access(all) fun withdraw(mintNumber: UInt32): @{TiblesNFT.INFT}
    access(all) fun mintNext()
  }

This change removes the error message resource declarations cannot be nested inside contract interface declarations but introduces a new issue in the same contract where this resource (now an interface) is used.

The contract uses the "Minter" interface as a type inside two places:

  access(all) resource interface IProducer {
    access(contract) let minters: @{String: Minter} // HERE
  }

  access(all) resource interface Producer: IContent, IProducer {
    access(contract) let minters: @{String: Minter} // AND HERE
  }

However, now the "Minter" interface (previously a resource) cannot be used as a type in the same way, and the following error occurs:

error: invalid use of interface as type
  --> cadence/contracts/crescendo/TiblesProducer.cdc:27:34
   |
27 |     access(contract) let minters: @{String: Minter}
   |                                   ^^^^^^^^^^^^^^^^^ got `{String: TiblesProducer.Minter}`; consider using `{String: {TiblesProducer.Minter}}`

error: invalid use of interface as type
  --> cadence/contracts/crescendo/TiblesProducer.cdc:31:34
   |
31 |     access(contract) let minters: @{String: Minter}
   |                                   ^^^^^^^^^^^^^^^^^ got `{String: TiblesProducer.Minter}`; consider using `{String: {TiblesProducer.Minter}}`

✘ e93c412c964bdf40.TiblesProducer 

When I follow the validation suggestion and try to stage the contract again, another error is generated:

error: mismatching field `minters` in `IProducer`
  --> cadence/contracts/crescendo/TiblesProducer.cdc:27:34
   |
27 |     access(contract) let minters: @{String: {Minter}}
   |                                   ^^^^^^^^^^^^^^^^^^^ incompatible type annotations. expected `Minter`, found `{Minter}`

error: mismatching field `minters` in `Producer`
  --> cadence/contracts/crescendo/TiblesProducer.cdc:31:34
   |
31 |     access(contract) let minters: @{String: {Minter}}
   |                                   ^^^^^^^^^^^^^^^^^^^ incompatible type annotations. expected `Minter`, found `{Minter}`

✘ e93c412c964bdf40.TiblesProducer 

The same issue occurs when I use the complete type:

error: mismatching field `minters` in `IProducer`
  --> cadence/contracts/crescendo/TiblesProducer.cdc:27:34
   |
27 |     access(contract) let minters: @{String: {TiblesProducer.Minter}}
   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incompatible type annotations. expected `Minter`, found `{TiblesProducer.Minter}`

error: mismatching field `minters` in `Producer`
  --> cadence/contracts/crescendo/TiblesProducer.cdc:31:34
   |
31 |     access(contract) let minters: @{String: {TiblesProducer.Minter}}
   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incompatible type annotations. expected `Minter`, found `{TiblesProducer.Minter}`

Here is the full code of the Crescendo 1.0 TiblesProducer contract interface as it currently stands:

import TiblesNFT from 0xe93c412c964bdf40

access(all) contract interface TiblesProducer {
  access(all) let ProducerStoragePath: StoragePath
  access(all) let ProducerPath: PrivatePath
  access(all) let ContentPath: PublicPath
  // access(all) let contentCapability: Capability<&{TiblesProducer.IContent}>

  access(all) event MinterCreated(minterId: String)
  access(all) event TibleMinted(minterId: String, mintNumber: UInt32, id: UInt64)

  // Producers must provide a ContentLocation struct so that NFTs can access metadata.
  access(all) struct interface ContentLocation {}
  access(all) struct interface IContentLocation {}

  // This is a public resource that lets the individual tibles get their metadata.
  // Adding content is done through the Producer.
  access(all) resource interface IContent {
    // Content is stored in the set/item/variant structures. To retrieve it, we have a contentId that maps to the path.
    access(all) view fun getMetadata(contentId: String): {String: AnyStruct}?
  }

  // Provides access to producer activities like content creation and NFT minting.
  // The resource is stored in the app account's storage with a link in /private.
  access(all) resource interface IProducer {
    // Minters create and store tibles before they are sold. One minter per set-item-variant combo.
    access(contract) let minters: @{String: Minter}
  }

  access(all) resource interface Producer: IContent, IProducer {
    access(contract) let minters: @{String: Minter}
  }

  // Mints new NFTs for a specific set/item/variant combination.
  access(all) resource interface IMinter {
    access(all) let id: String
    // Keeps track of the mint number for items.
    access(all) var lastMintNumber: UInt32
    // Stored with each minted NFT so that it can access metadata.
    // access(all) let contentCapability: Capability<&{TiblesProducer.IContent}>
    // Used only on original purchase, when the NFT gets transferred from the producer to the user's collection.
    access(all) fun withdraw(mintNumber: UInt32): @{TiblesNFT.INFT}
    access(all) fun mintNext()
  }

  access(all) resource interface Minter: IMinter {
    access(all) let id: String
    access(all) var lastMintNumber: UInt32
    // access(all) let contentCapability: Capability<&{TiblesProducer.IContent}>
    access(all) fun withdraw(mintNumber: UInt32): @{TiblesNFT.INFT}
    access(all) fun mintNext()
  }
}

Environment

using flow-c1 Version: v1.21.0-cadence-v1.0.0-preview.32

- Cadence version: 1.0
- Network: Testnet