From 032890b8e3dc557377e3bd3112d07cbf6c93abda Mon Sep 17 00:00:00 2001 From: "Bjarte S. Karlsen" Date: Wed, 2 Nov 2022 20:01:26 +0100 Subject: [PATCH] Use reflection to parse values (#78) * stated on generic struct mapper * added todos * added better tests * tested creating struct * added struct tags * fixing tests * fixing final tests * added example of Struct * added more convenience to create structs from account * move from camelCase to snake_case * remove println * remove the type since it can be derrived from value * fixed nested struct support * fixed NFT contract and added MetadataViews contract * added metadata structs except royalties with caps and some shared ones * remove WithStructArg * added option to skip field in struct if we do not want it in cadence * use json structtag if it is there and cadence is not there * tidy --- cadence.go | 149 +++++ cadence_test.go | 86 +++ contracts/Debug.cdc | 9 + contracts/MetadataViews.cdc | 622 ++++++++++++++++++ contracts/NonFungibleToken.cdc | 281 ++++---- doc_test.go | 12 +- event_fetcher.go | 2 +- flow.json | 10 +- go.mod | 1 + go.sum | 2 + identifier_integration_test.go | 25 + interaction_builder.go | 35 +- metadata.go | 69 ++ script_integration_old_test.go | 2 +- setup.go | 14 + state.go | 84 ++- testdata/TestParseConfig/parse.golden | 63 +- .../TestParseConfig/parse_and_filter.golden | 33 +- .../TestParseConfig/parse_and_merge.golden | 63 +- transaction_integration_old_test.go | 10 +- transaction_integration_test.go | 186 +++++- transaction_v3_test.go | 2 +- transactions/create_nft_collection.cdc | 10 +- tx/mint_tokens.cdc | 28 + 24 files changed, 1502 insertions(+), 296 deletions(-) create mode 100644 contracts/MetadataViews.cdc create mode 100644 identifier_integration_test.go create mode 100644 metadata.go create mode 100644 tx/mint_tokens.cdc diff --git a/cadence.go b/cadence.go index 4a2dff9..6736671 100644 --- a/cadence.go +++ b/cadence.go @@ -2,8 +2,12 @@ package overflow import ( "encoding/json" + "fmt" + "reflect" "strconv" + "strings" + "github.com/fatih/structtag" "github.com/onflow/cadence" ) @@ -113,3 +117,148 @@ func CadenceValueToInterface(field cadence.Value) interface{} { return field.String() } } + +// a resolver to resolve a input type into a name, can be used to resolve struct names for instance +type InputResolver func(string) (string, error) + +func InputToCadence(v interface{}, resolver InputResolver) (cadence.Value, error) { + f := reflect.ValueOf(v) + return ReflectToCadence(f, resolver) +} + +func ReflectToCadence(value reflect.Value, resolver InputResolver) (cadence.Value, error) { + inputType := value.Type() + + kind := inputType.Kind() + switch kind { + case reflect.Interface: + return cadence.NewValue(value.Interface()) + case reflect.Struct: + var val []cadence.Value + fields := []cadence.Field{} + for i := 0; i < value.NumField(); i++ { + fieldValue := value.Field(i) + cadenceVal, err := ReflectToCadence(fieldValue, resolver) + if err != nil { + return nil, err + } + cadenceType := cadenceVal.Type() + + field := inputType.Field(i) + + tags, err := structtag.Parse(string(field.Tag)) + if err != nil { + return nil, err + } + + name := "" + tag, err := tags.Get("cadence") + if err != nil { + tag, _ = tags.Get("json") + } + if tag != nil { + name = tag.Name + } + + if name == "-" { + continue + } + + if name == "" { + name = strings.ToLower(field.Name) + } + + fields = append(fields, cadence.Field{ + Identifier: name, + Type: cadenceType, + }) + + val = append(val, cadenceVal) + } + + resolvedIdentifier, err := resolver(inputType.Name()) + if err != nil { + return nil, err + } + structType := cadence.StructType{ + QualifiedIdentifier: resolvedIdentifier, + Fields: fields, + } + + structValue := cadence.NewStruct(val).WithType(&structType) + return structValue, nil + + case reflect.Pointer: + if value.IsNil() { + return cadence.NewOptional(nil), nil + } + + ptrValue, err := ReflectToCadence(value.Elem(), resolver) + if err != nil { + return nil, err + } + return cadence.NewOptional(ptrValue), nil + + case reflect.Int: + return cadence.NewInt(value.Interface().(int)), nil + case reflect.Int8: + return cadence.NewInt8(value.Interface().(int8)), nil + case reflect.Int16: + return cadence.NewInt16(value.Interface().(int16)), nil + case reflect.Int32: + return cadence.NewInt32(value.Interface().(int32)), nil + case reflect.Int64: + return cadence.NewInt64(value.Interface().(int64)), nil + case reflect.Bool: + return cadence.NewBool(value.Interface().(bool)), nil + case reflect.Uint: + return cadence.NewUInt(value.Interface().(uint)), nil + case reflect.Uint8: + return cadence.NewUInt8(value.Interface().(uint8)), nil + case reflect.Uint16: + return cadence.NewUInt16(value.Interface().(uint16)), nil + case reflect.Uint32: + return cadence.NewUInt32(value.Interface().(uint32)), nil + case reflect.Uint64: + return cadence.NewUInt64(value.Interface().(uint64)), nil + case reflect.String: + result, err := cadence.NewString(value.Interface().(string)) + return result, err + case reflect.Float64: + result, err := cadence.NewUFix64(fmt.Sprintf("%f", value.Interface().(float64))) + return result, err + + case reflect.Map: + array := []cadence.KeyValuePair{} + iter := value.MapRange() + + for iter.Next() { + key := iter.Key() + val := iter.Value() + cadenceKey, err := ReflectToCadence(key, resolver) + if err != nil { + return nil, err + } + cadenceVal, err := ReflectToCadence(val, resolver) + if err != nil { + return nil, err + } + array = append(array, cadence.KeyValuePair{Key: cadenceKey, Value: cadenceVal}) + } + return cadence.NewDictionary(array), nil + case reflect.Slice, reflect.Array: + array := []cadence.Value{} + for i := 0; i < value.Len(); i++ { + arrValue := value.Index(i) + cadenceVal, err := ReflectToCadence(arrValue, resolver) + if err != nil { + return nil, err + } + array = append(array, cadenceVal) + } + return cadence.NewArray(array), nil + + } + + return nil, fmt.Errorf("Not supported type for now. Type : %s", inputType.Kind()) +} diff --git a/cadence_test.go b/cadence_test.go index 986d1a2..8f4f82e 100644 --- a/cadence_test.go +++ b/cadence_test.go @@ -1,6 +1,8 @@ package overflow import ( + "encoding/json" + "fmt" "testing" "github.com/hexops/autogold" @@ -96,5 +98,89 @@ func TestCadenceValueToJson(t *testing.T) { result, err := CadenceValueToJsonString(cadence.String("")) assert.NoError(t, err) assert.Equal(t, "", result) +} + +func TestParseInputValue(t *testing.T) { + + foo := "foo" + + var strPointer *string = nil + values := []interface{}{ + "foo", + uint64(42), + map[string]uint64{"foo": uint64(42)}, + []uint64{42, 69}, + [2]string{"foo", "bar"}, + &foo, + strPointer, + } + + for idx, value := range values { + t.Run(fmt.Sprintf("parse input %d", idx), func(t *testing.T) { + cv, err := InputToCadence(value, func(string) (string, error) { + return "", nil + }) + assert.NoError(t, err) + v := CadenceValueToInterface(cv) + + vj, err := json.Marshal(v) + assert.NoError(t, err) + + cvj, err := json.Marshal(value) + assert.NoError(t, err) + + assert.Equal(t, string(cvj), string(vj)) + }) + } + +} + +func TestMarshalCadenceStruct(t *testing.T) { + + val, err := InputToCadence(Foo{Bar: "foo"}, func(string) (string, error) { + return "A.123.Foo.Bar", nil + }) + assert.NoError(t, err) + assert.Equal(t, "A.123.Foo.Bar", val.Type().ID()) + jsonVal, err := CadenceValueToJsonString(val) + assert.NoError(t, err) + assert.JSONEq(t, `{ "bar": "foo" }`, jsonVal) + +} + +func TestMarshalCadenceStructWithStructTag(t *testing.T) { + + val, err := InputToCadence(Foo{Bar: "foo"}, func(string) (string, error) { + return "A.123.Foo.Baz", nil + }) + assert.NoError(t, err) + assert.Equal(t, "A.123.Foo.Baz", val.Type().ID()) + jsonVal, err := CadenceValueToJsonString(val) + assert.NoError(t, err) + assert.JSONEq(t, `{ "bar": "foo" }`, jsonVal) + +} + +// in Debug.cdc +type Foo struct { + Bar string +} + +type Debug_FooBar struct { + Bar string + Foo Debug_Foo +} + +type Debug_Foo_Skip struct { + Bar string + Skip string `cadence:"-"` +} + +type Debug_Foo struct { + Bar string +} +// in Foo.Bar.Baz +type Baz struct { + Something string `json:"bar"` } diff --git a/contracts/Debug.cdc b/contracts/Debug.cdc index 7be88ee..f48ec5c 100644 --- a/contracts/Debug.cdc +++ b/contracts/Debug.cdc @@ -2,6 +2,15 @@ import NonFungibleToken from "./NonFungibleToken.cdc" pub contract Debug { + pub struct FooBar { + pub let foo:Foo + pub let bar:String + + init(foo:Foo, bar:String) { + self.foo=foo + self.bar=bar + } + } pub struct Foo{ pub let bar: String diff --git a/contracts/MetadataViews.cdc b/contracts/MetadataViews.cdc new file mode 100644 index 0000000..0bdc21e --- /dev/null +++ b/contracts/MetadataViews.cdc @@ -0,0 +1,622 @@ +/** + +This contract implements the metadata standard proposed +in FLIP-0636. + +Ref: https://github.com/onflow/flow/blob/master/flips/20210916-nft-metadata.md + +Structs and resources can implement one or more +metadata types, called views. Each view type represents +a different kind of metadata, such as a creator biography +or a JPEG image file. +*/ + +import FungibleToken from "./FungibleToken.cdc" +import NonFungibleToken from "./NonFungibleToken.cdc" + +pub contract MetadataViews { + + /// A Resolver provides access to a set of metadata views. + /// + /// A struct or resource (e.g. an NFT) can implement this interface + /// to provide access to the views that it supports. + /// + pub resource interface Resolver { + pub fun getViews(): [Type] + pub fun resolveView(_ view: Type): AnyStruct? + } + + /// A ResolverCollection is a group of view resolvers index by ID. + /// + pub resource interface ResolverCollection { + pub fun borrowViewResolver(id: UInt64): &{Resolver} + pub fun getIDs(): [UInt64] + } + + /// Display is a basic view that includes the name, description and + /// thumbnail for an object. Most objects should implement this view. + /// + pub struct Display { + + /// The name of the object. + /// + /// This field will be displayed in lists and therefore should + /// be short an concise. + /// + pub let name: String + + /// A written description of the object. + /// + /// This field will be displayed in a detailed view of the object, + /// so can be more verbose (e.g. a paragraph instead of a single line). + /// + pub let description: String + + /// A small thumbnail representation of the object. + /// + /// This field should be a web-friendly file (i.e JPEG, PNG) + /// that can be displayed in lists, link previews, etc. + /// + pub let thumbnail: AnyStruct{File} + + init( + name: String, + description: String, + thumbnail: AnyStruct{File} + ) { + self.name = name + self.description = description + self.thumbnail = thumbnail + } + } + + /// A helper to get Display in a typesafe way + pub fun getDisplay(_ viewResolver: &{Resolver}) : Display? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Display { + return v + } + } + return nil + } + + /// File is a generic interface that represents a file stored on or off chain. + /// + /// Files can be used to references images, videos and other media. + /// + pub struct interface File { + pub fun uri(): String + } + + /// HTTPFile is a file that is accessible at an HTTP (or HTTPS) URL. + /// + pub struct HTTPFile: File { + pub let url: String + + init(url: String) { + self.url = url + } + + pub fun uri(): String { + return self.url + } + } + + /// IPFSFile returns a thumbnail image for an object + /// stored as an image file in IPFS. + /// + /// IPFS images are referenced by their content identifier (CID) + /// rather than a direct URI. A client application can use this CID + /// to find and load the image via an IPFS gateway. + /// + pub struct IPFSFile: File { + + /// CID is the content identifier for this IPFS file. + /// + /// Ref: https://docs.ipfs.io/concepts/content-addressing/ + /// + pub let cid: String + + /// Path is an optional path to the file resource in an IPFS directory. + /// + /// This field is only needed if the file is inside a directory. + /// + /// Ref: https://docs.ipfs.io/concepts/file-systems/ + /// + pub let path: String? + + init(cid: String, path: String?) { + self.cid = cid + self.path = path + } + + /// This function returns the IPFS native URL for this file. + /// + /// Ref: https://docs.ipfs.io/how-to/address-ipfs-on-web/#native-urls + /// + pub fun uri(): String { + if let path = self.path { + return "ipfs://".concat(self.cid).concat("/").concat(path) + } + + return "ipfs://".concat(self.cid) + } + } + + /// Editions is an optional view for collections that issues multiple objects + /// with the same or similar metadata, for example an X of 100 set. This information is + /// useful for wallets and marketplaes. + /// + /// An NFT might be part of multiple editions, which is why the edition information + /// is returned as an arbitrary sized array + /// + pub struct Editions { + + /// An arbitrary-sized list for any number of editions + /// that the NFT might be a part of + pub let infoList: [Edition] + + init(_ infoList: [Edition]) { + self.infoList = infoList + } + } + + /// A helper to get Editions in a typesafe way + pub fun getEditions(_ viewResolver: &{Resolver}) : Editions? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Editions { + return v + } + } + return nil + } + + /// Edition information for a single edition + pub struct Edition { + + /// The name of the edition + /// For example, this could be Set, Play, Series, + /// or any other way a project could classify its editions + pub let name: String? + + /// The edition number of the object. + /// + /// For an "24 of 100 (#24/100)" item, the number is 24. + /// + pub let number: UInt64 + + /// The max edition number of this type of objects. + /// + /// This field should only be provided for limited-editioned objects. + /// For an "24 of 100 (#24/100)" item, max is 100. + /// For an item with unlimited edition, max should be set to nil. + /// + pub let max: UInt64? + + init(name: String?, number: UInt64, max: UInt64?) { + if max != nil { + assert(number <= max!, message: "The number cannot be greater than the max number!") + } + self.name = name + self.number = number + self.max = max + } + } + + + /// A view representing a project-defined serial number for a specific NFT + /// Projects have different definitions for what a serial number should be + /// Some may use the NFTs regular ID and some may use a different classification system + /// The serial number is expected to be unique among other NFTs within that project + /// + pub struct Serial { + pub let number: UInt64 + + init(_ number: UInt64) { + self.number = number + } + } + + /// A helper to get Serial in a typesafe way + pub fun getSerial(_ viewResolver: &{Resolver}) : Serial? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Serial { + return v + } + } + return nil + } + + /* + * Royalty Views + * Defines the composable royalty standard that gives marketplaces a unified interface + * to support NFT royalties. + * + * Marketplaces can query this `Royalties` struct from NFTs + * and are expected to pay royalties based on these specifications. + * + */ + pub struct Royalties { + + /// Array that tracks the individual royalties + access(self) let cutInfos: [Royalty] + + pub init(_ cutInfos: [Royalty]) { + // Validate that sum of all cut multipliers should not be greater than 1.0 + var totalCut = 0.0 + for royalty in cutInfos { + totalCut = totalCut + royalty.cut + } + assert(totalCut <= 1.0, message: "Sum of cutInfos multipliers should not be greater than 1.0") + // Assign the cutInfos + self.cutInfos = cutInfos + } + + /// Return the cutInfos list + pub fun getRoyalties(): [Royalty] { + return self.cutInfos + } + } + + /// A helper to get Royalties in a typesafe way + pub fun getRoyalties(_ viewResolver: &{Resolver}) : Royalties? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Royalties { + return v + } + } + return nil + } + + /// Struct to store details of a single royalty cut for a given NFT + pub struct Royalty { + + /// Generic FungibleToken Receiver for the beneficiary of the royalty + /// Can get the concrete type of the receiver with receiver.getType() + /// Recommendation - Users should create a new link for a FlowToken receiver for this using `getRoyaltyReceiverPublicPath()`, + /// and not use the default FlowToken receiver. + /// This will allow users to update the capability in the future to use a more generic capability + pub let receiver: Capability<&AnyResource{FungibleToken.Receiver}> + + /// Multiplier used to calculate the amount of sale value transferred to royalty receiver. + /// Note - It should be between 0.0 and 1.0 + /// Ex - If the sale value is x and multiplier is 0.56 then the royalty value would be 0.56 * x. + /// + /// Generally percentage get represented in terms of basis points + /// in solidity based smart contracts while cadence offers `UFix64` that already supports + /// the basis points use case because its operations + /// are entirely deterministic integer operations and support up to 8 points of precision. + pub let cut: UFix64 + + /// Optional description: This can be the cause of paying the royalty, + /// the relationship between the `wallet` and the NFT, or anything else that the owner might want to specify + pub let description: String + + init(recepient: Capability<&AnyResource{FungibleToken.Receiver}>, cut: UFix64, description: String) { + pre { + cut >= 0.0 && cut <= 1.0 : "Cut value should be in valid range i.e [0,1]" + } + self.receiver = recepient + self.cut = cut + self.description = description + } + } + + /// Get the path that should be used for receiving royalties + /// This is a path that will eventually be used for a generic switchboard receiver, + /// hence the name but will only be used for royalties for now. + pub fun getRoyaltyReceiverPublicPath(): PublicPath { + return /public/GenericFTReceiver + } + + /// Medias is an optional view for collections that issue objects with multiple Media sources in it + /// + pub struct Medias { + + /// An arbitrary-sized list for any number of Media items + pub let items: [Media] + + init(_ items: [Media]) { + self.items = items + } + } + + /// A helper to get Medias in a typesafe way + pub fun getMedias(_ viewResolver: &{Resolver}) : Medias? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Medias { + return v + } + } + return nil + } + + /// A view to represent Media, a file with an correspoiding mediaType. + pub struct Media { + + /// File for the media + pub let file: AnyStruct{File} + + /// media-type comes on the form of type/subtype as described here https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types + pub let mediaType: String + + init(file: AnyStruct{File}, mediaType: String) { + self.file=file + self.mediaType=mediaType + } + } + + /// A license according to https://spdx.org/licenses/ + /// + /// This view can be used if the content of an NFT is licensed. + pub struct License { + pub let spdxIdentifier: String + + init(_ identifier: String) { + self.spdxIdentifier = identifier + } + } + + /// A helper to get License in a typesafe way + pub fun getLicense(_ viewResolver: &{Resolver}) : License? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? License { + return v + } + } + return nil + } + + + /// A view to expose a URL to this item on an external site. + /// + /// This can be used by applications like .find and Blocto to direct users to the original link for an NFT. + pub struct ExternalURL { + pub let url: String + + init(_ url: String) { + self.url=url + } + } + + /// A helper to get ExternalURL in a typesafe way + pub fun getExternalURL(_ viewResolver: &{Resolver}) : ExternalURL? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? ExternalURL { + return v + } + } + return nil + } + + // A view to expose the information needed store and retrieve an NFT + // + // This can be used by applications to setup a NFT collection with proper storage and public capabilities. + pub struct NFTCollectionData { + /// Path in storage where this NFT is recommended to be stored. + pub let storagePath: StoragePath + + /// Public path which must be linked to expose public capabilities of this NFT + /// including standard NFT interfaces and metadataviews interfaces + pub let publicPath: PublicPath + + /// Private path which should be linked to expose the provider + /// capability to withdraw NFTs from the collection holding NFTs + pub let providerPath: PrivatePath + + /// Public collection type that is expected to provide sufficient read-only access to standard + /// functions (deposit + getIDs + borrowNFT) + /// This field is for backwards compatibility with collections that have not used the standard + /// NonFungibleToken.CollectionPublic interface when setting up collections. For new + /// collections, this may be set to be equal to the type specified in `publicLinkedType`. + pub let publicCollection: Type + + /// Type that should be linked at the aforementioned public path. This is normally a + /// restricted type with many interfaces. Notably the `NFT.CollectionPublic`, + /// `NFT.Receiver`, and `MetadataViews.ResolverCollection` interfaces are required. + pub let publicLinkedType: Type + + /// Type that should be linked at the aforementioned private path. This is normally + /// a restricted type with at a minimum the `NFT.Provider` interface + pub let providerLinkedType: Type + + /// Function that allows creation of an empty NFT collection that is intended to store + /// this NFT. + pub let createEmptyCollection: ((): @NonFungibleToken.Collection) + + init( + storagePath: StoragePath, + publicPath: PublicPath, + providerPath: PrivatePath, + publicCollection: Type, + publicLinkedType: Type, + providerLinkedType: Type, + createEmptyCollectionFunction: ((): @NonFungibleToken.Collection) + ) { + pre { + publicLinkedType.isSubtype(of: Type<&{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>()): "Public type must include NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, and MetadataViews.ResolverCollection interfaces." + providerLinkedType.isSubtype(of: Type<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection}>()): "Provider type must include NonFungibleToken.Provider, NonFungibleToken.CollectionPublic, and MetadataViews.ResolverCollection interface." + } + self.storagePath=storagePath + self.publicPath=publicPath + self.providerPath = providerPath + self.publicCollection=publicCollection + self.publicLinkedType=publicLinkedType + self.providerLinkedType = providerLinkedType + self.createEmptyCollection=createEmptyCollectionFunction + } + } + + /// A helper to get NFTCollectionData in a way that will return an typed Optional + pub fun getNFTCollectionData(_ viewResolver: &{Resolver}) : NFTCollectionData? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? NFTCollectionData { + return v + } + } + return nil + } + + // A view to expose the information needed to showcase this NFT's collection + // + // This can be used by applications to give an overview and graphics of the NFT collection + // this NFT belongs to. + pub struct NFTCollectionDisplay { + // Name that should be used when displaying this NFT collection. + pub let name: String + + // Description that should be used to give an overview of this collection. + pub let description: String + + // External link to a URL to view more information about this collection. + pub let externalURL: ExternalURL + + // Square-sized image to represent this collection. + pub let squareImage: Media + + // Banner-sized image for this collection, recommended to have a size near 1200x630. + pub let bannerImage: Media + + // Social links to reach this collection's social homepages. + // Possible keys may be "instagram", "twitter", "discord", etc. + pub let socials: {String: ExternalURL} + + init( + name: String, + description: String, + externalURL: ExternalURL, + squareImage: Media, + bannerImage: Media, + socials: {String: ExternalURL} + ) { + self.name = name + self.description = description + self.externalURL = externalURL + self.squareImage = squareImage + self.bannerImage = bannerImage + self.socials = socials + } + } + + /// A helper to get NFTCollectionDisplay in a way that will return an typed Optional + pub fun getNFTCollectionDisplay(_ viewResolver: &{Resolver}) : NFTCollectionDisplay? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? NFTCollectionDisplay { + return v + } + } + return nil + } + + // A view to represent a single field of metadata on an NFT. + // + // This is used to get traits of individual key/value pairs along with some contextualized data about the trait + pub struct Trait { + // The name of the trait. Like Background, Eyes, Hair, etc. + pub let name: String + + // The underlying value of the trait, the rest of the fields of a trait provide context to the value. + pub let value: AnyStruct + + // displayType is used to show some context about what this name and value represent + // for instance, you could set value to a unix timestamp, and specify displayType as "Date" to tell + // platforms to consume this trait as a date and not a number + pub let displayType: String? + + // Rarity can also be used directly on an attribute. + // + // This is optional because not all attributes need to contribute to the NFT's rarity. + pub let rarity: Rarity? + + init(name: String, value: AnyStruct, displayType: String?, rarity: Rarity?) { + self.name = name + self.value = value + self.displayType = displayType + self.rarity = rarity + } + } + + // A view to return all the traits on an NFT. + // + // This is used to return traits as individual key/value pairs along with some contextualized data about each trait. + pub struct Traits { + pub let traits: [Trait] + + init(_ traits: [Trait]) { + self.traits = traits + } + + pub fun addTrait(_ t: Trait) { + self.traits.append(t) + } + } + + /// A helper to get Traits view in a typesafe way + pub fun getTraits(_ viewResolver: &{Resolver}) : Traits? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Traits { + return v + } + } + return nil + } + + // A helper function to easily convert a dictionary to traits. For NFT collections that do not need either of the + // optional values of a Trait, this method should suffice to give them an array of valid traits. + pub fun dictToTraits(dict: {String: AnyStruct}, excludedNames: [String]?): Traits { + // Collection owners might not want all the fields in their metadata included. + // They might want to handle some specially, or they might just not want them included at all. + if excludedNames != nil { + for k in excludedNames! { + dict.remove(key: k) + } + } + + let traits: [Trait] = [] + for k in dict.keys { + let trait = Trait(name: k, value: dict[k]!, displayType: nil, rarity: nil) + traits.append(trait) + } + + return Traits(traits) + } + + /// Rarity information for a single rarity + // + /// Note that a rarity needs to have either score or description but it can have both + pub struct Rarity { + /// The score of the rarity as a number + /// + pub let score: UFix64? + + /// The maximum value of score + /// + pub let max: UFix64? + + /// The description of the rarity as a string. + /// + /// This could be Legendary, Epic, Rare, Uncommon, Common or any other string value + pub let description: String? + + init(score: UFix64?, max: UFix64?, description: String?) { + if score == nil && description == nil { + panic("A Rarity needs to set score, description or both") + } + + self.score = score + self.max = max + self.description = description + } + } + + /// A helper to get Rarity view in a typesafe way + pub fun getRarity(_ viewResolver: &{Resolver}) : Rarity? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Rarity { + return v + } + } + return nil + } + +} diff --git a/contracts/NonFungibleToken.cdc b/contracts/NonFungibleToken.cdc index 189b1da..17ab31b 100644 --- a/contracts/NonFungibleToken.cdc +++ b/contracts/NonFungibleToken.cdc @@ -1,141 +1,144 @@ -// NFTv2.cdc -// -// This is a complete version of the NonFungibleToken contract -// that includes withdraw and deposit functionality, as well as a -// collection resource that can be used to bundle NFTs together. +/** + +## The Flow Non-Fungible Token standard + +## `NonFungibleToken` contract interface + +The interface that all non-fungible token contracts could conform to. +If a user wants to deploy a new nft contract, their contract would need +to implement the NonFungibleToken interface. + +Their contract would have to follow all the rules and naming +that the interface specifies. + +## `NFT` resource + +The core resource type that represents an NFT in the smart contract. + +## `Collection` Resource + +The resource that stores a user's NFT collection. +It includes a few functions to allow the owner to easily +move tokens in and out of the collection. + +## `Provider` and `Receiver` resource interfaces + +These interfaces declare functions with some pre and post conditions +that require the Collection to follow certain naming and behavior standards. + +They are separate because it gives the user the ability to share a reference +to their Collection that only exposes the fields and functions in one or more +of the interfaces. It also gives users the ability to make custom resources +that implement these interfaces to do various things with the tokens. + +By using resources and interfaces, users of NFT smart contracts can send +and receive tokens peer-to-peer, without having to interact with a central ledger +smart contract. + +To send an NFT to another user, a user would simply withdraw the NFT +from their Collection, then call the deposit function on another user's +Collection to complete the transfer. + +*/ + +// The main NFT contract interface. Other NFT contracts will +// import and implement this interface // -// It also includes a definition for the Minter resource, -// which can be used by admins to mint new NFTs. - -pub contract NonFungibleToken { - - pub event TestEvent(ufix:UFix64, uint: UInt64, test: String) - // Declare the NFT resource type - pub resource NFT { - // The unique ID that differentiates each NFT - pub let id: UInt64 - - // Initialize both fields in the init function - init(initID: UInt64) { - self.id = initID - } - } - - // We define this interface purely as a way to allow users - // to create public, restricted references to their NFT Collection. - // They would use this to only expose the deposit, getIDs, - // and idExists fields in their Collection - pub resource interface NFTReceiver { - - pub fun deposit(token: @NFT) - - pub fun getIDs(): [UInt64] - - pub fun idExists(id: UInt64): Bool - } - - // The definition of the Collection resource that - // holds the NFTs that a user owns - pub resource Collection: NFTReceiver { - // dictionary of NFT conforming tokens - // NFT is a resource type with an `UInt64` ID field - pub var ownedNFTs: @{UInt64: NFT} - - // Initialize the NFTs field to an empty collection - init () { - self.ownedNFTs <- {} - } - - // withdraw - // - // Function that removes an NFT from the collection - // and moves it to the calling context - pub fun withdraw(withdrawID: UInt64): @NFT { - // If the NFT isn't found, the transaction panics and reverts - let token <- self.ownedNFTs.remove(key: withdrawID)! - - return <-token - } - - // deposit - // - // Function that takes a NFT as an argument and - // adds it to the collections dictionary - pub fun deposit(token: @NFT) { - // add the new token to the dictionary which removes the old one - let oldToken <- self.ownedNFTs[token.id] <- token - destroy oldToken - } - - // idExists checks to see if a NFT - // with the given ID exists in the collection - pub fun idExists(id: UInt64): Bool { - return self.ownedNFTs[id] != nil - } - - // getIDs returns an array of the IDs that are in the collection - pub fun getIDs(): [UInt64] { - return self.ownedNFTs.keys - } - - // If a resource has member fields that are resources, - // it is required to define a `destroy` block to specify - // what should happen to those member fields - // if the top level object is destroyed - destroy() { - destroy self.ownedNFTs - } - } - - // creates a new empty Collection resource and returns it - pub fun createEmptyCollection(): @Collection { - return <- create Collection() - } - - // NFTMinter - // - // Resource that would be owned by an admin or by a smart contract - // that allows them to mint new NFTs when needed - pub resource NFTMinter { - - // the ID that is used to mint NFTs - // it is onlt incremented so that NFT ids remain - // unique. It also keeps track of the total number of NFTs - // in existence - pub var idCount: UInt64 - - init() { - self.idCount = 1 - } - - // mintNFT - // - // Function that mints a new NFT with a new ID - // and deposits it in the recipients collection - // using their collection reference - pub fun mintNFT(recipient: &AnyResource{NFTReceiver}) { - - // create a new NFT - var newNFT <- create NFT(initID: self.idCount) - - // deposit it in the recipient's account using their reference - recipient.deposit(token: <-newNFT) - - // change the id so that each ID is unique - self.idCount = self.idCount + UInt64(1) - } - } - - init() { - // store an empty NFT Collection in account storage - self.account.save<@Collection>(<-self.createEmptyCollection(), to: /storage/NFTCollection) - - // publish a reference to the Collection in storage - self.account.link<&{NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection) - - // store a minter resource in account storage - self.account.save<@NFTMinter>(<-create NFTMinter(), to: /storage/NFTMinter) - - emit TestEvent(ufix: 64.10, uint: 64, test: "foobar" ) - } +pub contract interface NonFungibleToken { + + // The total number of tokens of this type in existence + pub var totalSupply: UInt64 + + // Event that emitted when the NFT contract is initialized + // + pub event ContractInitialized() + + // Event that is emitted when a token is withdrawn, + // indicating the owner of the collection that it was withdrawn from. + // + // If the collection is not in an account's storage, `from` will be `nil`. + // + pub event Withdraw(id: UInt64, from: Address?) + + // Event that emitted when a token is deposited to a collection. + // + // It indicates the owner of the collection that it was deposited to. + // + pub event Deposit(id: UInt64, to: Address?) + + // Interface that the NFTs have to conform to + // + pub resource interface INFT { + // The unique ID that each NFT has + pub let id: UInt64 + } + + // Requirement that all conforming NFT smart contracts have + // to define a resource called NFT that conforms to INFT + pub resource NFT: INFT { + pub let id: UInt64 + } + + // Interface to mediate withdraws from the Collection + // + pub resource interface Provider { + // withdraw removes an NFT from the collection and moves it to the caller + pub fun withdraw(withdrawID: UInt64): @NFT { + post { + result.id == withdrawID: "The ID of the withdrawn token must be the same as the requested ID" + } + } + } + + // Interface to mediate deposits to the Collection + // + pub resource interface Receiver { + + // deposit takes an NFT as an argument and adds it to the Collection + // + pub fun deposit(token: @NFT) + } + + // Interface that an account would commonly + // publish for their collection + pub resource interface CollectionPublic { + pub fun deposit(token: @NFT) + pub fun getIDs(): [UInt64] + pub fun borrowNFT(id: UInt64): &NFT + } + + // Requirement for the the concrete resource type + // to be declared in the implementing contract + // + pub resource Collection: Provider, Receiver, CollectionPublic { + + // Dictionary to hold the NFTs in the Collection + pub var ownedNFTs: @{UInt64: NFT} + + // withdraw removes an NFT from the collection and moves it to the caller + pub fun withdraw(withdrawID: UInt64): @NFT + + // deposit takes a NFT and adds it to the collections dictionary + // and adds the ID to the id array + pub fun deposit(token: @NFT) + + // getIDs returns an array of the IDs that are in the collection + pub fun getIDs(): [UInt64] + + // Returns a borrowed reference to an NFT in the collection + // so that the caller can read data and call methods from it + pub fun borrowNFT(id: UInt64): &NFT { + pre { + self.ownedNFTs[id] != nil: "NFT does not exist in the collection!" + } + } + } + + // createEmptyCollection creates an empty Collection + // and returns it to the caller so that they can own NFTs + pub fun createEmptyCollection(): @Collection { + post { + result.getIDs().length == 0: "The created collection must be empty!" + } + } } diff --git a/doc_test.go b/doc_test.go index 92eebd9..b6169c1 100644 --- a/doc_test.go +++ b/doc_test.go @@ -15,7 +15,7 @@ func Example() { //Output: //🧑 Created account: emulator-first with address: 01cf0e2f2f715450 with flow: 10.00 //🧑 Created account: emulator-second with address: 179b6b1cb6755e31 with flow: 10.00 - //📜 deploy contracts NonFungibleToken, Debug + //📜 deploy contracts NonFungibleToken, Debug, MetadataViews } func ExampleOverflowState_Tx() { @@ -33,7 +33,7 @@ func ExampleOverflowState_Tx() { //Output: //🧑 Created account: emulator-first with address: 01cf0e2f2f715450 with flow: 10.00 //🧑 Created account: emulator-second with address: 179b6b1cb6755e31 with flow: 10.00 - //📜 deploy contracts NonFungibleToken, Debug + //📜 deploy contracts NonFungibleToken, Debug, MetadataViews //👌 Tx:arguments fee:0.00000100 gas:0 // } @@ -55,7 +55,7 @@ func ExampleOverflowState_Tx_inline() { //Output: //🧑 Created account: emulator-first with address: 01cf0e2f2f715450 with flow: 10.00 //🧑 Created account: emulator-second with address: 179b6b1cb6755e31 with flow: 10.00 - //📜 deploy contracts NonFungibleToken, Debug + //📜 deploy contracts NonFungibleToken, Debug, MetadataViews //👌 Tx: fee:0.00000134 gas:7 //=== Events === //A.f8d6e0586b0a20c7.Debug.Log @@ -81,7 +81,7 @@ func ExampleOverflowState_Tx_multisign() { //Output: //🧑 Created account: emulator-first with address: 01cf0e2f2f715450 with flow: 10.00 //🧑 Created account: emulator-second with address: 179b6b1cb6755e31 with flow: 10.00 - //📜 deploy contracts NonFungibleToken, Debug + //📜 deploy contracts NonFungibleToken, Debug, MetadataViews //👌 Tx: fee:0.00000134 gas:7 //=== Events === //A.f8d6e0586b0a20c7.Debug.Log @@ -107,7 +107,7 @@ func ExampleOverflowState_Script() { //Output: //🧑 Created account: emulator-first with address: 01cf0e2f2f715450 with flow: 10.00 //🧑 Created account: emulator-second with address: 179b6b1cb6755e31 with flow: 10.00 - //📜 deploy contracts NonFungibleToken, Debug + //📜 deploy contracts NonFungibleToken, Debug, MetadataViews //⭐ Script test run result:"0x01cf0e2f2f715450" } @@ -126,6 +126,6 @@ pub fun main(account: Address): String { //Output: //🧑 Created account: emulator-first with address: 01cf0e2f2f715450 with flow: 10.00 //🧑 Created account: emulator-second with address: 179b6b1cb6755e31 with flow: 10.00 - //📜 deploy contracts NonFungibleToken, Debug + //📜 deploy contracts NonFungibleToken, Debug, MetadataViews //⭐ Script get_address run result:"0x01cf0e2f2f715450" } diff --git a/event_fetcher.go b/event_fetcher.go index 40c250e..b9222e8 100644 --- a/event_fetcher.go +++ b/event_fetcher.go @@ -173,7 +173,7 @@ func WithWorkers(workers int) OverflowEventFetcherOption { } } -// Set the batch sice for FetchEvents +// Set the batch size for FetchEvents func WithBatchSize(size uint64) OverflowEventFetcherOption { return func(e *OverflowEventFetcherBuilder) { e.EventBatchSize = size diff --git a/flow.json b/flow.json index 91a1f71..938bfe1 100644 --- a/flow.json +++ b/flow.json @@ -30,6 +30,13 @@ "testnet": "0x631e88ae7f1d7c20", "mainnet": "0x1d7e57aa55817448" } + }, + "MetadataViews": { + "source": "./contracts/MetadataViews.cdc", + "aliases": { + "testnet": "0x631e88ae7f1d7c20", + "mainnet": "0x1d7e57aa55817448" + } } }, "networks": { @@ -67,7 +74,8 @@ "emulator": { "emulator-account": [ "NonFungibleToken", - "Debug" + "Debug", + "MetadataViews" ], "emulator-first": [], "emulator-second": [] diff --git a/go.mod b/go.mod index 6fbe58a..7b85dde 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/enescakir/emoji v1.0.0 github.com/fatih/color v1.13.0 + github.com/fatih/structtag v1.2.0 github.com/hexops/autogold v1.3.0 github.com/onflow/cadence v0.28.0 github.com/onflow/flow-cli/pkg/flowkit v0.0.0-20221013174805-71f721b956bf diff --git a/go.sum b/go.sum index 1b21c50..d354754 100644 --- a/go.sum +++ b/go.sum @@ -235,6 +235,8 @@ github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= diff --git a/identifier_integration_test.go b/identifier_integration_test.go new file mode 100644 index 0000000..cb9b6bf --- /dev/null +++ b/identifier_integration_test.go @@ -0,0 +1,25 @@ +package overflow + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIdentifierIntegration(t *testing.T) { + o, err := OverflowTesting() + assert.NoError(t, err) + + result, err := o.QualifiedIdentifier("MetadataViews", "Display") + assert.NoError(t, err) + assert.Equal(t, "A.f8d6e0586b0a20c7.MetadataViews.Display", result) +} + +func TestIdentifierTestnet(t *testing.T) { + o := Overflow(WithNetwork("testnet")) + assert.NoError(t, o.Error) + + result, err := o.QualifiedIdentifier("MetadataViews", "Display") + assert.NoError(t, err) + assert.Equal(t, "A.631e88ae7f1d7c20.MetadataViews.Display", result) +} diff --git a/interaction_builder.go b/interaction_builder.go index 98babf5..f260007 100644 --- a/interaction_builder.go +++ b/interaction_builder.go @@ -111,7 +111,7 @@ func (oib OverflowInteractionBuilder) getContractCode(codeFileName string) ([]by return code, nil } -//A function to customize the transaction builder +// A function to customize the transaction builder type OverflowInteractionOption func(*OverflowInteractionBuilder) // force no printing for this interaction @@ -171,6 +171,39 @@ func WithArg(name string, value interface{}) OverflowInteractionOption { } } +// Send an list of structs into a transaction + +// use the `cadence` struct tag to name a field or it will be given the lowercase name of the field +func WithStructArgsCustomQualifier(name string, resolver InputResolver, values ...interface{}) OverflowInteractionOption { + return func(oib *OverflowInteractionBuilder) { + + array := []cadence.Value{} + for _, value := range values { + structValue, err := InputToCadence(value, resolver) + if err != nil { + oib.Error = err + return + } + array = append(array, structValue) + } + oib.NamedArgs[name] = cadence.NewArray(array) + } +} + +// Send an struct as argument into a transaction + +// use the `cadence` struct tag to name a field or it will be given the lowercase name of the field +func WithStructArgCustomResolver(name string, resolver InputResolver, value interface{}) OverflowInteractionOption { + return func(oib *OverflowInteractionBuilder) { + structValue, err := InputToCadence(value, resolver) + if err != nil { + oib.Error = err + return + } + oib.NamedArgs[name] = structValue + } +} + // sending in a timestamp as an arg is quite complicated, use this method with the name of the arg, the datestring and the given timezone to parse it at func WithArgDateTime(name string, dateString string, timezone string) OverflowInteractionOption { return func(oib *OverflowInteractionBuilder) { diff --git a/metadata.go b/metadata.go new file mode 100644 index 0000000..0129517 --- /dev/null +++ b/metadata.go @@ -0,0 +1,69 @@ +package overflow + +type MetadataViews_HTTPFile struct { + Url string +} + +type MetadataViews_IPFSFile struct { + Cid string + Path *string +} + +type MetadataViews_Display_IPFS struct { + Name string + Description string + Thumbnail MetadataViews_IPFSFile +} +type MetadataViews_Display_Http struct { + Name string + Description string + Thumbnail MetadataViews_HTTPFile +} + +type MetadataViews_Edition struct { + Name *string + Number uint64 + Max *uint64 +} + +type MetadataViews_Editions struct { + Editions []MetadataViews_Edition `cadence:"infoList"` +} + +type MetadataViews_Serial struct { + Number uint64 +} + +type MetadataViews_Media_IPFS struct { + File MetadataViews_IPFSFile + MediaType string `cadence:"mediaType"` +} + +type MetadataViews_Media_HTTP struct { + File MetadataViews_HTTPFile + MediaType string `cadence:"mediaType"` +} +type MetadtaViews_Licensce struct { + Spdx string `cadence:"spdxIdentifier"` +} + +type MetadataViews_ExternalURL struct { + Url string +} + +type MetadataViews_Rarity struct { + Score *string + Max *uint64 + Description *string +} + +type MetadataViews_Trait struct { + Name string + Value interface{} + DisplayType string `cadence:"displayType"` + Rarity *MetadataViews_Rarity +} + +type MetadataViews_Traits struct { + Traits []MetadataViews_Trait +} diff --git a/script_integration_old_test.go b/script_integration_old_test.go index b5cece3..5f210fd 100644 --- a/script_integration_old_test.go +++ b/script_integration_old_test.go @@ -119,7 +119,7 @@ pub struct Report{ NamedArguments(map[string]string{}). RunReturnsInterface() - assert.Equal(t, uint64(4), value) + assert.Equal(t, uint64(5), value) }) } diff --git a/setup.go b/setup.go index 6af86ef..bad7c51 100644 --- a/setup.go +++ b/setup.go @@ -99,6 +99,7 @@ type OverflowBuilder struct { PrintOptions *[]OverflowPrinterOption NewAccountFlowAmount float64 ReaderWriter flowkit.ReaderWriter + InputResolver *InputResolver } func (o *OverflowBuilder) StartE() (*OverflowState, error) { @@ -156,6 +157,13 @@ func (o *OverflowBuilder) StartResult() *OverflowState { } overflow.State = state + if o.InputResolver != nil { + overflow.InputResolver = *o.InputResolver + } else { + overflow.InputResolver = func(name string) (string, error) { + return overflow.QualifiedIdentiferFromSnakeCase(name) + } + } logger := output.NewStdoutLogger(o.LogLevel) overflow.Logger = logger var memlog bytes.Buffer @@ -454,6 +462,12 @@ func WithEmbedFS(fs embed.FS) OverflowOption { } } +func WithInputResolver(ir InputResolver) OverflowOption { + return func(o *OverflowBuilder) { + o.InputResolver = &ir + } +} + type EmbedWrapper struct { Embed embed.FS } diff --git a/state.go b/state.go index 80ddb7d..22e825c 100644 --- a/state.go +++ b/state.go @@ -77,6 +77,8 @@ type OverflowState struct { //Mint this amount of flow to new accounts NewUserFlowAmount float64 + + InputResolver InputResolver } type OverflowArgument struct { @@ -88,6 +90,47 @@ type OverflowArgument struct { type OverflowArguments map[string]OverflowArgument type OverflowArgumentList []OverflowArgument +// Qualified identifier from a snakeCase string Account_Contract_Struct +func (o *OverflowState) QualifiedIdentiferFromSnakeCase(typeName string) (string, error) { + + words := strings.Split(typeName, "_") + if len(words) < 2 { + return "", fmt.Errorf("Invalid snake_case type string Contract_Name") + } + return o.QualifiedIdentifier(words[0], words[1]) +} + +// Create a qualified identifier from account, contract, name + +// account can either be a name from accounts or the raw value +func (o *OverflowState) QualifiedIdentifier(contract string, name string) (string, error) { + + flowContract, err := o.State.Contracts().ByNameAndNetwork(contract, o.Network) + if err != nil { + return "", err + } + + //we found the contract specified in contracts section + if flowContract != nil && flowContract.Alias != "" { + return fmt.Sprintf("A.%s.%s.%s", strings.TrimPrefix(flowContract.Alias, "0x"), contract, name), nil + } + + flowDeploymentContracts, err := o.State.DeploymentContractsByNetwork(o.Network) + if err != nil { + return "", err + + } + + for _, flowDeploymentContract := range flowDeploymentContracts { + if flowDeploymentContract.Name == contract { + return fmt.Sprintf("A.%s.%s.%s", flowDeploymentContract.AccountAddress, contract, name), nil + } + } + + return "", fmt.Errorf("You are trying to get the qualified identifier for something you are not creating or have mentioned in flow.json with name=%s", contract) + +} + func (o *OverflowState) parseArguments(fileName string, code []byte, inputArgs map[string]interface{}) ([]cadence.Value, CadenceArguments, error) { var resultArgs []cadence.Value = make([]cadence.Value, 0) resultArgsMap := CadenceArguments{} @@ -170,35 +213,16 @@ func (o *OverflowState) parseArguments(fileName string, code []byte, inputArgs m argumentString = "nil" case string: argumentString = a - case []float64: - argumentString = strings.Join(strings.Fields(fmt.Sprintf("%v", a)), ", ") - case []uint64: - argumentString = strings.Join(strings.Fields(fmt.Sprintf("%v", a)), ", ") - case []string: - argumentString = fmt.Sprintf("[\"%s\"]", strings.Join(a, "\", \"")) - case map[string]string: - args := []string{} - for key, value := range a { - args = append(args, fmt.Sprintf(`"%s":"%s"`, key, value)) - } - argumentString = fmt.Sprintf("{%s}", strings.Join(args, ", ")) - case map[string]float64: - args := []string{} - for key, value := range a { - args = append(args, fmt.Sprintf(`"%s":%f`, key, value)) - } - argumentString = fmt.Sprintf("{%s}", strings.Join(args, ", ")) - case map[string]uint64: - args := []string{} - for key, value := range a { - args = append(args, fmt.Sprintf(`"%s":%d`, key, value)) - } - argumentString = fmt.Sprintf("{%s}", strings.Join(args, ", ")) - - case float64: - argumentString = fmt.Sprintf("%f", a) + case int: + argumentString = fmt.Sprintf("%v", a) default: - argumentString = fmt.Sprintf("%v", argument) + cadenceVal, err := InputToCadence(argument, o.InputResolver) + if err != nil { + return nil, nil, err + } + resultArgs = append(resultArgs, cadenceVal) + resultArgsMap[name] = cadenceVal + continue } semaType := checker.ConvertType(oa.Type) @@ -259,7 +283,7 @@ func (o *OverflowState) Address(key string) string { return fmt.Sprintf("0x%s", o.Account(key).Address().String()) } -//return the account of a given account +// return the account of a given account func (o *OverflowState) Account(key string) *flowkit.Account { account, err := o.AccountE(key) if err != nil { @@ -508,7 +532,7 @@ func (o *OverflowState) TxFileNameFN(filename string, outerOpts ...OverflowInter } } -//The main function for running an transasction in overflow +// The main function for running an transasction in overflow func (o *OverflowState) Tx(filename string, opts ...OverflowInteractionOption) *OverflowResult { ftb := o.BuildInteraction(filename, "transaction", opts...) result := ftb.Send() diff --git a/testdata/TestParseConfig/parse.golden b/testdata/TestParseConfig/parse.golden index 5a24573..04be166 100644 --- a/testdata/TestParseConfig/parse.golden +++ b/testdata/TestParseConfig/parse.golden @@ -125,18 +125,9 @@ transaction(test:Address) { log(test) } }`, - "create_nft_collection": `import NonFungibleToken from 0xf8d6e0586b0a20c7 - -// This transaction creates an empty NFT Collection in the signer's account + "create_nft_collection": `// This transaction creates an empty NFT Collection in the signer's account transaction { prepare(acct: AuthAccount) { - // store an empty NFT Collection in account storage - acct.save<@NonFungibleToken.Collection>(<-NonFungibleToken.createEmptyCollection(), to: /storage/NFTCollection) - - // publish a capability to the Collection in storage - acct.link<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection) - - log("Created a new empty collection and published a reference") } }`, "emulatorFoo": `// This transaction creates an empty NFT Collection in the signer's account @@ -204,6 +195,15 @@ transaction(test:String) { pub contract Debug { +pub struct FooBar { + pub let foo:Foo + pub let bar:String + + init(foo:Foo, bar:String) { + self.foo=foo + self.bar=bar + } +} pub struct Foo{ pub let bar: String @@ -227,7 +227,8 @@ pub fun log(_ msg: String) : String { } `, - "NonFungibleToken": "// NFTv2.cdc\n//\n// This is a complete version of the NonFungibleToken contract\n// that includes withdraw and deposit functionality, as well as a\n// collection resource that can be used to bundle NFTs together.\n//\n// It also includes a definition for the Minter resource,\n// which can be used by admins to mint new NFTs.\n\npub contract NonFungibleToken {\n\n pub event TestEvent(ufix:UFix64, uint: UInt64, test: String)\n // Declare the NFT resource type\n pub resource NFT {\n // The unique ID that differentiates each NFT\n pub let id: UInt64\n\n // Initialize both fields in the init function\n init(initID: UInt64) {\n self.id = initID\n }\n }\n\n // We define this interface purely as a way to allow users\n // to create public, restricted references to their NFT Collection.\n // They would use this to only expose the deposit, getIDs,\n // and idExists fields in their Collection\n pub resource interface NFTReceiver {\n\n pub fun deposit(token: @NFT)\n\n pub fun getIDs(): [UInt64]\n\n pub fun idExists(id: UInt64): Bool\n }\n\n // The definition of the Collection resource that\n // holds the NFTs that a user owns\n pub resource Collection: NFTReceiver {\n // dictionary of NFT conforming tokens\n // NFT is a resource type with an `UInt64` ID field\n pub var ownedNFTs: @{UInt64: NFT}\n\n // Initialize the NFTs field to an empty collection\n init () {\n self.ownedNFTs <- {}\n }\n\n // withdraw\n //\n // Function that removes an NFT from the collection\n // and moves it to the calling context\n pub fun withdraw(withdrawID: UInt64): @NFT {\n // If the NFT isn't found, the transaction panics and reverts\n let token <- self.ownedNFTs.remove(key: withdrawID)!\n\n return <-token\n }\n\n // deposit\n //\n // Function that takes a NFT as an argument and\n // adds it to the collections dictionary\n pub fun deposit(token: @NFT) {\n // add the new token to the dictionary which removes the old one\n let oldToken <- self.ownedNFTs[token.id] <- token\n destroy oldToken\n }\n\n // idExists checks to see if a NFT\n // with the given ID exists in the collection\n pub fun idExists(id: UInt64): Bool {\n return self.ownedNFTs[id] != nil\n }\n\n // getIDs returns an array of the IDs that are in the collection\n pub fun getIDs(): [UInt64] {\n return self.ownedNFTs.keys\n }\n\n // If a resource has member fields that are resources,\n // it is required to define a `destroy` block to specify\n // what should happen to those member fields\n // if the top level object is destroyed\n destroy() {\n destroy self.ownedNFTs\n }\n }\n\n // creates a new empty Collection resource and returns it\n pub fun createEmptyCollection(): @Collection {\n return <- create Collection()\n }\n\n // NFTMinter\n //\n // Resource that would be owned by an admin or by a smart contract\n // that allows them to mint new NFTs when needed\n pub resource NFTMinter {\n\n // the ID that is used to mint NFTs\n // it is onlt incremented so that NFT ids remain\n // unique. It also keeps track of the total number of NFTs\n // in existence\n pub var idCount: UInt64\n\n init() {\n self.idCount = 1\n }\n\n // mintNFT\n //\n // Function that mints a new NFT with a new ID\n // and deposits it in the recipients collection\n // using their collection reference\n pub fun mintNFT(recipient: &AnyResource{NFTReceiver}) {\n\n // create a new NFT\n var newNFT <- create NFT(initID: self.idCount)\n\n // deposit it in the recipient's account using their reference\n recipient.deposit(token: <-newNFT)\n\n // change the id so that each ID is unique\n self.idCount = self.idCount + UInt64(1)\n }\n }\n\n init() {\n // store an empty NFT Collection in account storage\n self.account.save<@Collection>(<-self.createEmptyCollection(), to: /storage/NFTCollection)\n\n // publish a reference to the Collection in storage\n self.account.link<&{NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection)\n\n // store a minter resource in account storage\n self.account.save<@NFTMinter>(<-create NFTMinter(), to: /storage/NFTMinter)\n\n emit TestEvent(ufix: 64.10, uint: 64, test: \"foobar\" )\n }\n}\n", + "MetadataViews": "/**\n\nThis contract implements the metadata standard proposed\nin FLIP-0636.\n\nRef: https://github.com/onflow/flow/blob/master/flips/20210916-nft-metadata.md\n\nStructs and resources can implement one or more\nmetadata types, called views. Each view type represents\na different kind of metadata, such as a creator biography\nor a JPEG image file.\n*/\n\nimport FungibleToken from 0xee82856bf20e2aa6\nimport NonFungibleToken from 0xf8d6e0586b0a20c7\n\npub contract MetadataViews {\n\n /// A Resolver provides access to a set of metadata views.\n ///\n /// A struct or resource (e.g. an NFT) can implement this interface\n /// to provide access to the views that it supports.\n ///\n pub resource interface Resolver {\n pub fun getViews(): [Type]\n pub fun resolveView(_ view: Type): AnyStruct?\n }\n\n /// A ResolverCollection is a group of view resolvers index by ID.\n ///\n pub resource interface ResolverCollection {\n pub fun borrowViewResolver(id: UInt64): &{Resolver}\n pub fun getIDs(): [UInt64]\n }\n\n /// Display is a basic view that includes the name, description and\n /// thumbnail for an object. Most objects should implement this view.\n ///\n pub struct Display {\n\n /// The name of the object. \n ///\n /// This field will be displayed in lists and therefore should\n /// be short an concise.\n ///\n pub let name: String\n\n /// A written description of the object. \n ///\n /// This field will be displayed in a detailed view of the object,\n /// so can be more verbose (e.g. a paragraph instead of a single line).\n ///\n pub let description: String\n\n /// A small thumbnail representation of the object.\n ///\n /// This field should be a web-friendly file (i.e JPEG, PNG)\n /// that can be displayed in lists, link previews, etc.\n ///\n pub let thumbnail: AnyStruct{File}\n\n init(\n name: String,\n description: String,\n thumbnail: AnyStruct{File}\n ) {\n self.name = name\n self.description = description\n self.thumbnail = thumbnail\n }\n }\n\n /// A helper to get Display in a typesafe way\n pub fun getDisplay(_ viewResolver: &{Resolver}) : Display? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? Display {\n return v\n }\n }\n return nil\n }\n\n /// File is a generic interface that represents a file stored on or off chain.\n ///\n /// Files can be used to references images, videos and other media.\n ///\n pub struct interface File {\n pub fun uri(): String\n }\n\n /// HTTPFile is a file that is accessible at an HTTP (or HTTPS) URL. \n ///\n pub struct HTTPFile: File {\n pub let url: String\n\n init(url: String) {\n self.url = url\n }\n\n pub fun uri(): String {\n return self.url\n }\n }\n\n /// IPFSFile returns a thumbnail image for an object\n /// stored as an image file in IPFS.\n ///\n /// IPFS images are referenced by their content identifier (CID)\n /// rather than a direct URI. A client application can use this CID\n /// to find and load the image via an IPFS gateway.\n ///\n pub struct IPFSFile: File {\n\n /// CID is the content identifier for this IPFS file.\n ///\n /// Ref: https://docs.ipfs.io/concepts/content-addressing/\n ///\n pub let cid: String\n\n /// Path is an optional path to the file resource in an IPFS directory.\n ///\n /// This field is only needed if the file is inside a directory.\n ///\n /// Ref: https://docs.ipfs.io/concepts/file-systems/\n ///\n pub let path: String?\n\n init(cid: String, path: String?) {\n self.cid = cid\n self.path = path\n }\n\n /// This function returns the IPFS native URL for this file.\n ///\n /// Ref: https://docs.ipfs.io/how-to/address-ipfs-on-web/#native-urls\n ///\n pub fun uri(): String {\n if let path = self.path {\n return \"ipfs://\".concat(self.cid).concat(\"/\").concat(path)\n }\n\n return \"ipfs://\".concat(self.cid)\n }\n }\n\n /// Editions is an optional view for collections that issues multiple objects\n /// with the same or similar metadata, for example an X of 100 set. This information is \n /// useful for wallets and marketplaes.\n ///\n /// An NFT might be part of multiple editions, which is why the edition information\n /// is returned as an arbitrary sized array\n /// \n pub struct Editions {\n\n /// An arbitrary-sized list for any number of editions\n /// that the NFT might be a part of\n pub let infoList: [Edition]\n\n init(_ infoList: [Edition]) {\n self.infoList = infoList\n }\n }\n\n /// A helper to get Editions in a typesafe way\n pub fun getEditions(_ viewResolver: &{Resolver}) : Editions? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? Editions {\n return v\n }\n }\n return nil\n }\n\n /// Edition information for a single edition\n pub struct Edition {\n\n /// The name of the edition\n /// For example, this could be Set, Play, Series,\n /// or any other way a project could classify its editions\n pub let name: String?\n\n /// The edition number of the object.\n ///\n /// For an \"24 of 100 (#24/100)\" item, the number is 24. \n ///\n pub let number: UInt64\n\n /// The max edition number of this type of objects.\n /// \n /// This field should only be provided for limited-editioned objects.\n /// For an \"24 of 100 (#24/100)\" item, max is 100.\n /// For an item with unlimited edition, max should be set to nil.\n /// \n pub let max: UInt64?\n\n init(name: String?, number: UInt64, max: UInt64?) {\n if max != nil {\n assert(number <= max!, message: \"The number cannot be greater than the max number!\")\n }\n self.name = name\n self.number = number\n self.max = max\n }\n }\n\n\n /// A view representing a project-defined serial number for a specific NFT\n /// Projects have different definitions for what a serial number should be\n /// Some may use the NFTs regular ID and some may use a different classification system\n /// The serial number is expected to be unique among other NFTs within that project\n ///\n pub struct Serial {\n pub let number: UInt64\n\n init(_ number: UInt64) {\n self.number = number\n }\n }\n\n /// A helper to get Serial in a typesafe way\n pub fun getSerial(_ viewResolver: &{Resolver}) : Serial? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? Serial {\n return v\n }\n }\n return nil\n }\n\n /*\n * Royalty Views\n * Defines the composable royalty standard that gives marketplaces a unified interface\n * to support NFT royalties.\n *\n * Marketplaces can query this `Royalties` struct from NFTs \n * and are expected to pay royalties based on these specifications.\n *\n */\n pub struct Royalties {\n\n /// Array that tracks the individual royalties\n access(self) let cutInfos: [Royalty]\n\n pub init(_ cutInfos: [Royalty]) {\n // Validate that sum of all cut multipliers should not be greater than 1.0\n var totalCut = 0.0\n for royalty in cutInfos {\n totalCut = totalCut + royalty.cut\n }\n assert(totalCut <= 1.0, message: \"Sum of cutInfos multipliers should not be greater than 1.0\")\n // Assign the cutInfos\n self.cutInfos = cutInfos\n }\n\n /// Return the cutInfos list\n pub fun getRoyalties(): [Royalty] {\n return self.cutInfos\n }\n }\n\n /// A helper to get Royalties in a typesafe way\n pub fun getRoyalties(_ viewResolver: &{Resolver}) : Royalties? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? Royalties {\n return v\n }\n }\n return nil\n }\n\n /// Struct to store details of a single royalty cut for a given NFT\n pub struct Royalty {\n\n /// Generic FungibleToken Receiver for the beneficiary of the royalty\n /// Can get the concrete type of the receiver with receiver.getType()\n /// Recommendation - Users should create a new link for a FlowToken receiver for this using `getRoyaltyReceiverPublicPath()`,\n /// and not use the default FlowToken receiver.\n /// This will allow users to update the capability in the future to use a more generic capability\n pub let receiver: Capability<&AnyResource{FungibleToken.Receiver}>\n\n /// Multiplier used to calculate the amount of sale value transferred to royalty receiver.\n /// Note - It should be between 0.0 and 1.0 \n /// Ex - If the sale value is x and multiplier is 0.56 then the royalty value would be 0.56 * x.\n ///\n /// Generally percentage get represented in terms of basis points\n /// in solidity based smart contracts while cadence offers `UFix64` that already supports\n /// the basis points use case because its operations\n /// are entirely deterministic integer operations and support up to 8 points of precision.\n pub let cut: UFix64\n\n /// Optional description: This can be the cause of paying the royalty,\n /// the relationship between the `wallet` and the NFT, or anything else that the owner might want to specify\n pub let description: String\n\n init(recepient: Capability<&AnyResource{FungibleToken.Receiver}>, cut: UFix64, description: String) {\n pre {\n cut >= 0.0 && cut <= 1.0 : \"Cut value should be in valid range i.e [0,1]\"\n }\n self.receiver = recepient\n self.cut = cut\n self.description = description\n }\n }\n\n /// Get the path that should be used for receiving royalties\n /// This is a path that will eventually be used for a generic switchboard receiver,\n /// hence the name but will only be used for royalties for now.\n pub fun getRoyaltyReceiverPublicPath(): PublicPath {\n return /public/GenericFTReceiver\n }\n\n /// Medias is an optional view for collections that issue objects with multiple Media sources in it\n ///\n pub struct Medias {\n\n /// An arbitrary-sized list for any number of Media items\n pub let items: [Media]\n\n init(_ items: [Media]) {\n self.items = items\n }\n }\n\n /// A helper to get Medias in a typesafe way\n pub fun getMedias(_ viewResolver: &{Resolver}) : Medias? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? Medias {\n return v\n }\n }\n return nil\n }\n\n /// A view to represent Media, a file with an correspoiding mediaType.\n pub struct Media {\n\n /// File for the media\n pub let file: AnyStruct{File}\n\n /// media-type comes on the form of type/subtype as described here https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types\n pub let mediaType: String\n\n init(file: AnyStruct{File}, mediaType: String) {\n self.file=file\n self.mediaType=mediaType\n }\n }\n\n /// A license according to https://spdx.org/licenses/\n ///\n /// This view can be used if the content of an NFT is licensed. \n pub struct License {\n pub let spdxIdentifier: String\n\n init(_ identifier: String) {\n self.spdxIdentifier = identifier\n }\n }\n\n /// A helper to get License in a typesafe way\n pub fun getLicense(_ viewResolver: &{Resolver}) : License? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? License {\n return v\n }\n }\n return nil\n }\n\n\n /// A view to expose a URL to this item on an external site.\n ///\n /// This can be used by applications like .find and Blocto to direct users to the original link for an NFT.\n pub struct ExternalURL {\n pub let url: String\n\n init(_ url: String) {\n self.url=url\n }\n }\n\n /// A helper to get ExternalURL in a typesafe way\n pub fun getExternalURL(_ viewResolver: &{Resolver}) : ExternalURL? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? ExternalURL {\n return v\n }\n }\n return nil\n }\n\n // A view to expose the information needed store and retrieve an NFT\n //\n // This can be used by applications to setup a NFT collection with proper storage and public capabilities.\n pub struct NFTCollectionData {\n /// Path in storage where this NFT is recommended to be stored.\n pub let storagePath: StoragePath\n\n /// Public path which must be linked to expose public capabilities of this NFT\n /// including standard NFT interfaces and metadataviews interfaces\n pub let publicPath: PublicPath\n\n /// Private path which should be linked to expose the provider\n /// capability to withdraw NFTs from the collection holding NFTs\n pub let providerPath: PrivatePath\n\n /// Public collection type that is expected to provide sufficient read-only access to standard\n /// functions (deposit + getIDs + borrowNFT)\n /// This field is for backwards compatibility with collections that have not used the standard\n /// NonFungibleToken.CollectionPublic interface when setting up collections. For new\n /// collections, this may be set to be equal to the type specified in `publicLinkedType`.\n pub let publicCollection: Type\n\n /// Type that should be linked at the aforementioned public path. This is normally a\n /// restricted type with many interfaces. Notably the `NFT.CollectionPublic`,\n /// `NFT.Receiver`, and `MetadataViews.ResolverCollection` interfaces are required.\n pub let publicLinkedType: Type\n\n /// Type that should be linked at the aforementioned private path. This is normally\n /// a restricted type with at a minimum the `NFT.Provider` interface\n pub let providerLinkedType: Type\n\n /// Function that allows creation of an empty NFT collection that is intended to store\n /// this NFT.\n pub let createEmptyCollection: ((): @NonFungibleToken.Collection)\n\n init(\n storagePath: StoragePath,\n publicPath: PublicPath,\n providerPath: PrivatePath,\n publicCollection: Type,\n publicLinkedType: Type,\n providerLinkedType: Type,\n createEmptyCollectionFunction: ((): @NonFungibleToken.Collection)\n ) {\n pre {\n publicLinkedType.isSubtype(of: Type<&{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>()): \"Public type must include NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, and MetadataViews.ResolverCollection interfaces.\"\n providerLinkedType.isSubtype(of: Type<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection}>()): \"Provider type must include NonFungibleToken.Provider, NonFungibleToken.CollectionPublic, and MetadataViews.ResolverCollection interface.\"\n }\n self.storagePath=storagePath\n self.publicPath=publicPath\n self.providerPath = providerPath\n self.publicCollection=publicCollection\n self.publicLinkedType=publicLinkedType\n self.providerLinkedType = providerLinkedType\n self.createEmptyCollection=createEmptyCollectionFunction\n }\n }\n\n /// A helper to get NFTCollectionData in a way that will return an typed Optional\n pub fun getNFTCollectionData(_ viewResolver: &{Resolver}) : NFTCollectionData? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? NFTCollectionData {\n return v\n }\n }\n return nil\n }\n\n // A view to expose the information needed to showcase this NFT's collection\n //\n // This can be used by applications to give an overview and graphics of the NFT collection\n // this NFT belongs to.\n pub struct NFTCollectionDisplay {\n // Name that should be used when displaying this NFT collection.\n pub let name: String\n\n // Description that should be used to give an overview of this collection.\n pub let description: String\n\n // External link to a URL to view more information about this collection.\n pub let externalURL: ExternalURL\n\n // Square-sized image to represent this collection.\n pub let squareImage: Media\n\n // Banner-sized image for this collection, recommended to have a size near 1200x630.\n pub let bannerImage: Media\n\n // Social links to reach this collection's social homepages.\n // Possible keys may be \"instagram\", \"twitter\", \"discord\", etc.\n pub let socials: {String: ExternalURL}\n\n init(\n name: String,\n description: String,\n externalURL: ExternalURL,\n squareImage: Media,\n bannerImage: Media,\n socials: {String: ExternalURL}\n ) {\n self.name = name\n self.description = description\n self.externalURL = externalURL\n self.squareImage = squareImage\n self.bannerImage = bannerImage\n self.socials = socials\n }\n }\n\n /// A helper to get NFTCollectionDisplay in a way that will return an typed Optional\n pub fun getNFTCollectionDisplay(_ viewResolver: &{Resolver}) : NFTCollectionDisplay? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? NFTCollectionDisplay {\n return v\n }\n }\n return nil\n }\n\n // A view to represent a single field of metadata on an NFT.\n //\n // This is used to get traits of individual key/value pairs along with some contextualized data about the trait\n pub struct Trait {\n // The name of the trait. Like Background, Eyes, Hair, etc.\n pub let name: String\n\n // The underlying value of the trait, the rest of the fields of a trait provide context to the value.\n pub let value: AnyStruct\n\n // displayType is used to show some context about what this name and value represent\n // for instance, you could set value to a unix timestamp, and specify displayType as \"Date\" to tell\n // platforms to consume this trait as a date and not a number\n pub let displayType: String?\n\n // Rarity can also be used directly on an attribute.\n //\n // This is optional because not all attributes need to contribute to the NFT's rarity.\n pub let rarity: Rarity?\n\n init(name: String, value: AnyStruct, displayType: String?, rarity: Rarity?) {\n self.name = name\n self.value = value\n self.displayType = displayType\n self.rarity = rarity\n }\n }\n\n // A view to return all the traits on an NFT.\n //\n // This is used to return traits as individual key/value pairs along with some contextualized data about each trait.\n pub struct Traits {\n pub let traits: [Trait]\n\n init(_ traits: [Trait]) {\n self.traits = traits\n }\n\n pub fun addTrait(_ t: Trait) {\n self.traits.append(t)\n }\n }\n\n /// A helper to get Traits view in a typesafe way\n pub fun getTraits(_ viewResolver: &{Resolver}) : Traits? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? Traits {\n return v\n }\n }\n return nil\n }\n\n // A helper function to easily convert a dictionary to traits. For NFT collections that do not need either of the\n // optional values of a Trait, this method should suffice to give them an array of valid traits.\n pub fun dictToTraits(dict: {String: AnyStruct}, excludedNames: [String]?): Traits {\n // Collection owners might not want all the fields in their metadata included.\n // They might want to handle some specially, or they might just not want them included at all.\n if excludedNames != nil {\n for k in excludedNames! {\n dict.remove(key: k)\n }\n }\n\n let traits: [Trait] = []\n for k in dict.keys {\n let trait = Trait(name: k, value: dict[k]!, displayType: nil, rarity: nil)\n traits.append(trait)\n }\n\n return Traits(traits)\n }\n\n /// Rarity information for a single rarity\n //\n /// Note that a rarity needs to have either score or description but it can have both\n pub struct Rarity {\n /// The score of the rarity as a number\n ///\n pub let score: UFix64?\n\n /// The maximum value of score\n ///\n pub let max: UFix64?\n\n /// The description of the rarity as a string.\n ///\n /// This could be Legendary, Epic, Rare, Uncommon, Common or any other string value\n pub let description: String?\n\n init(score: UFix64?, max: UFix64?, description: String?) {\n if score == nil && description == nil {\n panic(\"A Rarity needs to set score, description or both\")\n }\n\n self.score = score\n self.max = max\n self.description = description\n }\n }\n\n /// A helper to get Rarity view in a typesafe way\n pub fun getRarity(_ viewResolver: &{Resolver}) : Rarity? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? Rarity {\n return v\n }\n }\n return nil\n }\n\n}\n", + "NonFungibleToken": "/**\n\n## The Flow Non-Fungible Token standard\n\n## `NonFungibleToken` contract interface\n\nThe interface that all non-fungible token contracts could conform to.\nIf a user wants to deploy a new nft contract, their contract would need\nto implement the NonFungibleToken interface.\n\nTheir contract would have to follow all the rules and naming\nthat the interface specifies.\n\n## `NFT` resource\n\nThe core resource type that represents an NFT in the smart contract.\n\n## `Collection` Resource\n\nThe resource that stores a user's NFT collection.\nIt includes a few functions to allow the owner to easily\nmove tokens in and out of the collection.\n\n## `Provider` and `Receiver` resource interfaces\n\nThese interfaces declare functions with some pre and post conditions\nthat require the Collection to follow certain naming and behavior standards.\n\nThey are separate because it gives the user the ability to share a reference\nto their Collection that only exposes the fields and functions in one or more\nof the interfaces. It also gives users the ability to make custom resources\nthat implement these interfaces to do various things with the tokens.\n\nBy using resources and interfaces, users of NFT smart contracts can send\nand receive tokens peer-to-peer, without having to interact with a central ledger\nsmart contract.\n\nTo send an NFT to another user, a user would simply withdraw the NFT\nfrom their Collection, then call the deposit function on another user's\nCollection to complete the transfer.\n\n*/\n\n// The main NFT contract interface. Other NFT contracts will\n// import and implement this interface\n//\npub contract interface NonFungibleToken {\n\n // The total number of tokens of this type in existence\n pub var totalSupply: UInt64\n\n // Event that emitted when the NFT contract is initialized\n //\n pub event ContractInitialized()\n\n // Event that is emitted when a token is withdrawn,\n // indicating the owner of the collection that it was withdrawn from.\n //\n // If the collection is not in an account's storage, `from` will be `nil`.\n //\n pub event Withdraw(id: UInt64, from: Address?)\n\n // Event that emitted when a token is deposited to a collection.\n //\n // It indicates the owner of the collection that it was deposited to.\n //\n pub event Deposit(id: UInt64, to: Address?)\n\n // Interface that the NFTs have to conform to\n //\n pub resource interface INFT {\n // The unique ID that each NFT has\n pub let id: UInt64\n }\n\n // Requirement that all conforming NFT smart contracts have\n // to define a resource called NFT that conforms to INFT\n pub resource NFT: INFT {\n pub let id: UInt64\n }\n\n // Interface to mediate withdraws from the Collection\n //\n pub resource interface Provider {\n // withdraw removes an NFT from the collection and moves it to the caller\n pub fun withdraw(withdrawID: UInt64): @NFT {\n post {\n result.id == withdrawID: \"The ID of the withdrawn token must be the same as the requested ID\"\n }\n }\n }\n\n // Interface to mediate deposits to the Collection\n //\n pub resource interface Receiver {\n\n // deposit takes an NFT as an argument and adds it to the Collection\n //\n pub fun deposit(token: @NFT)\n }\n\n // Interface that an account would commonly \n // publish for their collection\n pub resource interface CollectionPublic {\n pub fun deposit(token: @NFT)\n pub fun getIDs(): [UInt64]\n pub fun borrowNFT(id: UInt64): &NFT\n }\n\n // Requirement for the the concrete resource type\n // to be declared in the implementing contract\n //\n pub resource Collection: Provider, Receiver, CollectionPublic {\n\n // Dictionary to hold the NFTs in the Collection\n pub var ownedNFTs: @{UInt64: NFT}\n\n // withdraw removes an NFT from the collection and moves it to the caller\n pub fun withdraw(withdrawID: UInt64): @NFT\n\n // deposit takes a NFT and adds it to the collections dictionary\n // and adds the ID to the id array\n pub fun deposit(token: @NFT)\n\n // getIDs returns an array of the IDs that are in the collection\n pub fun getIDs(): [UInt64]\n\n // Returns a borrowed reference to an NFT in the collection\n // so that the caller can read data and call methods from it\n pub fun borrowNFT(id: UInt64): &NFT {\n pre {\n self.ownedNFTs[id] != nil: \"NFT does not exist in the collection!\"\n }\n }\n }\n\n // createEmptyCollection creates an empty Collection\n // and returns it to the caller so that they can own NFTs\n pub fun createEmptyCollection(): @Collection {\n post {\n result.getIDs().length == 0: \"The created collection must be empty!\"\n }\n }\n}\n", }, }, "mainnet": &overflow.OverflowSolutionNetwork{ @@ -287,18 +288,9 @@ transaction(test:Address) { log(test) } }`, - "create_nft_collection": `import NonFungibleToken from 0x1d7e57aa55817448 - -// This transaction creates an empty NFT Collection in the signer's account + "create_nft_collection": `// This transaction creates an empty NFT Collection in the signer's account transaction { prepare(acct: AuthAccount) { - // store an empty NFT Collection in account storage - acct.save<@NonFungibleToken.Collection>(<-NonFungibleToken.createEmptyCollection(), to: /storage/NFTCollection) - - // publish a capability to the Collection in storage - acct.link<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection) - - log("Created a new empty collection and published a reference") } }`, "emulatorFoo": `// This transaction creates an empty NFT Collection in the signer's account @@ -365,6 +357,15 @@ transaction(test:String) { pub contract Debug { +pub struct FooBar { + pub let foo:Foo + pub let bar:String + + init(foo:Foo, bar:String) { + self.foo=foo + self.bar=bar + } +} pub struct Foo{ pub let bar: String @@ -446,18 +447,9 @@ transaction(test:Address) { log(test) } }`, - "create_nft_collection": `import NonFungibleToken from 0x631e88ae7f1d7c20 - -// This transaction creates an empty NFT Collection in the signer's account + "create_nft_collection": `// This transaction creates an empty NFT Collection in the signer's account transaction { prepare(acct: AuthAccount) { - // store an empty NFT Collection in account storage - acct.save<@NonFungibleToken.Collection>(<-NonFungibleToken.createEmptyCollection(), to: /storage/NFTCollection) - - // publish a capability to the Collection in storage - acct.link<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection) - - log("Created a new empty collection and published a reference") } }`, "emulatorFoo": `// This transaction creates an empty NFT Collection in the signer's account @@ -524,6 +516,15 @@ transaction(test:String) { pub contract Debug { +pub struct FooBar { + pub let foo:Foo + pub let bar:String + + init(foo:Foo, bar:String) { + self.foo=foo + self.bar=bar + } +} pub struct Foo{ pub let bar: String diff --git a/testdata/TestParseConfig/parse_and_filter.golden b/testdata/TestParseConfig/parse_and_filter.golden index 44fd07c..72d04e2 100644 --- a/testdata/TestParseConfig/parse_and_filter.golden +++ b/testdata/TestParseConfig/parse_and_filter.golden @@ -98,18 +98,9 @@ pub fun main(): Type { }`, }, Transactions: map[string]string{ - "create_nft_collection": `import NonFungibleToken from 0xf8d6e0586b0a20c7 - -// This transaction creates an empty NFT Collection in the signer's account + "create_nft_collection": `// This transaction creates an empty NFT Collection in the signer's account transaction { prepare(acct: AuthAccount) { - // store an empty NFT Collection in account storage - acct.save<@NonFungibleToken.Collection>(<-NonFungibleToken.createEmptyCollection(), to: /storage/NFTCollection) - - // publish a capability to the Collection in storage - acct.link<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection) - - log("Created a new empty collection and published a reference") } }`, "emulatorFoo": `// This transaction creates an empty NFT Collection in the signer's account @@ -213,18 +204,9 @@ pub fun main(): Type { }`, }, Transactions: map[string]string{ - "create_nft_collection": `import NonFungibleToken from 0x1d7e57aa55817448 - -// This transaction creates an empty NFT Collection in the signer's account + "create_nft_collection": `// This transaction creates an empty NFT Collection in the signer's account transaction { prepare(acct: AuthAccount) { - // store an empty NFT Collection in account storage - acct.save<@NonFungibleToken.Collection>(<-NonFungibleToken.createEmptyCollection(), to: /storage/NFTCollection) - - // publish a capability to the Collection in storage - acct.link<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection) - - log("Created a new empty collection and published a reference") } }`, "emulatorFoo": `// This transaction creates an empty NFT Collection in the signer's account @@ -328,18 +310,9 @@ pub fun main(): Type { }`, }, Transactions: map[string]string{ - "create_nft_collection": `import NonFungibleToken from 0x631e88ae7f1d7c20 - -// This transaction creates an empty NFT Collection in the signer's account + "create_nft_collection": `// This transaction creates an empty NFT Collection in the signer's account transaction { prepare(acct: AuthAccount) { - // store an empty NFT Collection in account storage - acct.save<@NonFungibleToken.Collection>(<-NonFungibleToken.createEmptyCollection(), to: /storage/NFTCollection) - - // publish a capability to the Collection in storage - acct.link<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection) - - log("Created a new empty collection and published a reference") } }`, "emulatorFoo": `// This transaction creates an empty NFT Collection in the signer's account diff --git a/testdata/TestParseConfig/parse_and_merge.golden b/testdata/TestParseConfig/parse_and_merge.golden index 1833e51..1748be4 100644 --- a/testdata/TestParseConfig/parse_and_merge.golden +++ b/testdata/TestParseConfig/parse_and_merge.golden @@ -105,18 +105,9 @@ transaction(test:Address) { }, }, "create_nft_collection": overflow.OverflowCodeWithSpec{ - Code: `import NonFungibleToken from 0xf8d6e0586b0a20c7 - -// This transaction creates an empty NFT Collection in the signer's account + Code: `// This transaction creates an empty NFT Collection in the signer's account transaction { prepare(acct: AuthAccount) { - // store an empty NFT Collection in account storage - acct.save<@NonFungibleToken.Collection>(<-NonFungibleToken.createEmptyCollection(), to: /storage/NFTCollection) - - // publish a capability to the Collection in storage - acct.link<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection) - - log("Created a new empty collection and published a reference") } }`, Spec: &overflow.OverflowDeclarationInfo{ @@ -185,6 +176,15 @@ transaction(test:String) { pub contract Debug { +pub struct FooBar { + pub let foo:Foo + pub let bar:String + + init(foo:Foo, bar:String) { + self.foo=foo + self.bar=bar + } +} pub struct Foo{ pub let bar: String @@ -208,7 +208,8 @@ pub fun log(_ msg: String) : String { } `, - "NonFungibleToken": "// NFTv2.cdc\n//\n// This is a complete version of the NonFungibleToken contract\n// that includes withdraw and deposit functionality, as well as a\n// collection resource that can be used to bundle NFTs together.\n//\n// It also includes a definition for the Minter resource,\n// which can be used by admins to mint new NFTs.\n\npub contract NonFungibleToken {\n\n pub event TestEvent(ufix:UFix64, uint: UInt64, test: String)\n // Declare the NFT resource type\n pub resource NFT {\n // The unique ID that differentiates each NFT\n pub let id: UInt64\n\n // Initialize both fields in the init function\n init(initID: UInt64) {\n self.id = initID\n }\n }\n\n // We define this interface purely as a way to allow users\n // to create public, restricted references to their NFT Collection.\n // They would use this to only expose the deposit, getIDs,\n // and idExists fields in their Collection\n pub resource interface NFTReceiver {\n\n pub fun deposit(token: @NFT)\n\n pub fun getIDs(): [UInt64]\n\n pub fun idExists(id: UInt64): Bool\n }\n\n // The definition of the Collection resource that\n // holds the NFTs that a user owns\n pub resource Collection: NFTReceiver {\n // dictionary of NFT conforming tokens\n // NFT is a resource type with an `UInt64` ID field\n pub var ownedNFTs: @{UInt64: NFT}\n\n // Initialize the NFTs field to an empty collection\n init () {\n self.ownedNFTs <- {}\n }\n\n // withdraw\n //\n // Function that removes an NFT from the collection\n // and moves it to the calling context\n pub fun withdraw(withdrawID: UInt64): @NFT {\n // If the NFT isn't found, the transaction panics and reverts\n let token <- self.ownedNFTs.remove(key: withdrawID)!\n\n return <-token\n }\n\n // deposit\n //\n // Function that takes a NFT as an argument and\n // adds it to the collections dictionary\n pub fun deposit(token: @NFT) {\n // add the new token to the dictionary which removes the old one\n let oldToken <- self.ownedNFTs[token.id] <- token\n destroy oldToken\n }\n\n // idExists checks to see if a NFT\n // with the given ID exists in the collection\n pub fun idExists(id: UInt64): Bool {\n return self.ownedNFTs[id] != nil\n }\n\n // getIDs returns an array of the IDs that are in the collection\n pub fun getIDs(): [UInt64] {\n return self.ownedNFTs.keys\n }\n\n // If a resource has member fields that are resources,\n // it is required to define a `destroy` block to specify\n // what should happen to those member fields\n // if the top level object is destroyed\n destroy() {\n destroy self.ownedNFTs\n }\n }\n\n // creates a new empty Collection resource and returns it\n pub fun createEmptyCollection(): @Collection {\n return <- create Collection()\n }\n\n // NFTMinter\n //\n // Resource that would be owned by an admin or by a smart contract\n // that allows them to mint new NFTs when needed\n pub resource NFTMinter {\n\n // the ID that is used to mint NFTs\n // it is onlt incremented so that NFT ids remain\n // unique. It also keeps track of the total number of NFTs\n // in existence\n pub var idCount: UInt64\n\n init() {\n self.idCount = 1\n }\n\n // mintNFT\n //\n // Function that mints a new NFT with a new ID\n // and deposits it in the recipients collection\n // using their collection reference\n pub fun mintNFT(recipient: &AnyResource{NFTReceiver}) {\n\n // create a new NFT\n var newNFT <- create NFT(initID: self.idCount)\n\n // deposit it in the recipient's account using their reference\n recipient.deposit(token: <-newNFT)\n\n // change the id so that each ID is unique\n self.idCount = self.idCount + UInt64(1)\n }\n }\n\n init() {\n // store an empty NFT Collection in account storage\n self.account.save<@Collection>(<-self.createEmptyCollection(), to: /storage/NFTCollection)\n\n // publish a reference to the Collection in storage\n self.account.link<&{NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection)\n\n // store a minter resource in account storage\n self.account.save<@NFTMinter>(<-create NFTMinter(), to: /storage/NFTMinter)\n\n emit TestEvent(ufix: 64.10, uint: 64, test: \"foobar\" )\n }\n}\n", + "MetadataViews": "/**\n\nThis contract implements the metadata standard proposed\nin FLIP-0636.\n\nRef: https://github.com/onflow/flow/blob/master/flips/20210916-nft-metadata.md\n\nStructs and resources can implement one or more\nmetadata types, called views. Each view type represents\na different kind of metadata, such as a creator biography\nor a JPEG image file.\n*/\n\nimport FungibleToken from 0xee82856bf20e2aa6\nimport NonFungibleToken from 0xf8d6e0586b0a20c7\n\npub contract MetadataViews {\n\n /// A Resolver provides access to a set of metadata views.\n ///\n /// A struct or resource (e.g. an NFT) can implement this interface\n /// to provide access to the views that it supports.\n ///\n pub resource interface Resolver {\n pub fun getViews(): [Type]\n pub fun resolveView(_ view: Type): AnyStruct?\n }\n\n /// A ResolverCollection is a group of view resolvers index by ID.\n ///\n pub resource interface ResolverCollection {\n pub fun borrowViewResolver(id: UInt64): &{Resolver}\n pub fun getIDs(): [UInt64]\n }\n\n /// Display is a basic view that includes the name, description and\n /// thumbnail for an object. Most objects should implement this view.\n ///\n pub struct Display {\n\n /// The name of the object. \n ///\n /// This field will be displayed in lists and therefore should\n /// be short an concise.\n ///\n pub let name: String\n\n /// A written description of the object. \n ///\n /// This field will be displayed in a detailed view of the object,\n /// so can be more verbose (e.g. a paragraph instead of a single line).\n ///\n pub let description: String\n\n /// A small thumbnail representation of the object.\n ///\n /// This field should be a web-friendly file (i.e JPEG, PNG)\n /// that can be displayed in lists, link previews, etc.\n ///\n pub let thumbnail: AnyStruct{File}\n\n init(\n name: String,\n description: String,\n thumbnail: AnyStruct{File}\n ) {\n self.name = name\n self.description = description\n self.thumbnail = thumbnail\n }\n }\n\n /// A helper to get Display in a typesafe way\n pub fun getDisplay(_ viewResolver: &{Resolver}) : Display? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? Display {\n return v\n }\n }\n return nil\n }\n\n /// File is a generic interface that represents a file stored on or off chain.\n ///\n /// Files can be used to references images, videos and other media.\n ///\n pub struct interface File {\n pub fun uri(): String\n }\n\n /// HTTPFile is a file that is accessible at an HTTP (or HTTPS) URL. \n ///\n pub struct HTTPFile: File {\n pub let url: String\n\n init(url: String) {\n self.url = url\n }\n\n pub fun uri(): String {\n return self.url\n }\n }\n\n /// IPFSFile returns a thumbnail image for an object\n /// stored as an image file in IPFS.\n ///\n /// IPFS images are referenced by their content identifier (CID)\n /// rather than a direct URI. A client application can use this CID\n /// to find and load the image via an IPFS gateway.\n ///\n pub struct IPFSFile: File {\n\n /// CID is the content identifier for this IPFS file.\n ///\n /// Ref: https://docs.ipfs.io/concepts/content-addressing/\n ///\n pub let cid: String\n\n /// Path is an optional path to the file resource in an IPFS directory.\n ///\n /// This field is only needed if the file is inside a directory.\n ///\n /// Ref: https://docs.ipfs.io/concepts/file-systems/\n ///\n pub let path: String?\n\n init(cid: String, path: String?) {\n self.cid = cid\n self.path = path\n }\n\n /// This function returns the IPFS native URL for this file.\n ///\n /// Ref: https://docs.ipfs.io/how-to/address-ipfs-on-web/#native-urls\n ///\n pub fun uri(): String {\n if let path = self.path {\n return \"ipfs://\".concat(self.cid).concat(\"/\").concat(path)\n }\n\n return \"ipfs://\".concat(self.cid)\n }\n }\n\n /// Editions is an optional view for collections that issues multiple objects\n /// with the same or similar metadata, for example an X of 100 set. This information is \n /// useful for wallets and marketplaes.\n ///\n /// An NFT might be part of multiple editions, which is why the edition information\n /// is returned as an arbitrary sized array\n /// \n pub struct Editions {\n\n /// An arbitrary-sized list for any number of editions\n /// that the NFT might be a part of\n pub let infoList: [Edition]\n\n init(_ infoList: [Edition]) {\n self.infoList = infoList\n }\n }\n\n /// A helper to get Editions in a typesafe way\n pub fun getEditions(_ viewResolver: &{Resolver}) : Editions? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? Editions {\n return v\n }\n }\n return nil\n }\n\n /// Edition information for a single edition\n pub struct Edition {\n\n /// The name of the edition\n /// For example, this could be Set, Play, Series,\n /// or any other way a project could classify its editions\n pub let name: String?\n\n /// The edition number of the object.\n ///\n /// For an \"24 of 100 (#24/100)\" item, the number is 24. \n ///\n pub let number: UInt64\n\n /// The max edition number of this type of objects.\n /// \n /// This field should only be provided for limited-editioned objects.\n /// For an \"24 of 100 (#24/100)\" item, max is 100.\n /// For an item with unlimited edition, max should be set to nil.\n /// \n pub let max: UInt64?\n\n init(name: String?, number: UInt64, max: UInt64?) {\n if max != nil {\n assert(number <= max!, message: \"The number cannot be greater than the max number!\")\n }\n self.name = name\n self.number = number\n self.max = max\n }\n }\n\n\n /// A view representing a project-defined serial number for a specific NFT\n /// Projects have different definitions for what a serial number should be\n /// Some may use the NFTs regular ID and some may use a different classification system\n /// The serial number is expected to be unique among other NFTs within that project\n ///\n pub struct Serial {\n pub let number: UInt64\n\n init(_ number: UInt64) {\n self.number = number\n }\n }\n\n /// A helper to get Serial in a typesafe way\n pub fun getSerial(_ viewResolver: &{Resolver}) : Serial? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? Serial {\n return v\n }\n }\n return nil\n }\n\n /*\n * Royalty Views\n * Defines the composable royalty standard that gives marketplaces a unified interface\n * to support NFT royalties.\n *\n * Marketplaces can query this `Royalties` struct from NFTs \n * and are expected to pay royalties based on these specifications.\n *\n */\n pub struct Royalties {\n\n /// Array that tracks the individual royalties\n access(self) let cutInfos: [Royalty]\n\n pub init(_ cutInfos: [Royalty]) {\n // Validate that sum of all cut multipliers should not be greater than 1.0\n var totalCut = 0.0\n for royalty in cutInfos {\n totalCut = totalCut + royalty.cut\n }\n assert(totalCut <= 1.0, message: \"Sum of cutInfos multipliers should not be greater than 1.0\")\n // Assign the cutInfos\n self.cutInfos = cutInfos\n }\n\n /// Return the cutInfos list\n pub fun getRoyalties(): [Royalty] {\n return self.cutInfos\n }\n }\n\n /// A helper to get Royalties in a typesafe way\n pub fun getRoyalties(_ viewResolver: &{Resolver}) : Royalties? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? Royalties {\n return v\n }\n }\n return nil\n }\n\n /// Struct to store details of a single royalty cut for a given NFT\n pub struct Royalty {\n\n /// Generic FungibleToken Receiver for the beneficiary of the royalty\n /// Can get the concrete type of the receiver with receiver.getType()\n /// Recommendation - Users should create a new link for a FlowToken receiver for this using `getRoyaltyReceiverPublicPath()`,\n /// and not use the default FlowToken receiver.\n /// This will allow users to update the capability in the future to use a more generic capability\n pub let receiver: Capability<&AnyResource{FungibleToken.Receiver}>\n\n /// Multiplier used to calculate the amount of sale value transferred to royalty receiver.\n /// Note - It should be between 0.0 and 1.0 \n /// Ex - If the sale value is x and multiplier is 0.56 then the royalty value would be 0.56 * x.\n ///\n /// Generally percentage get represented in terms of basis points\n /// in solidity based smart contracts while cadence offers `UFix64` that already supports\n /// the basis points use case because its operations\n /// are entirely deterministic integer operations and support up to 8 points of precision.\n pub let cut: UFix64\n\n /// Optional description: This can be the cause of paying the royalty,\n /// the relationship between the `wallet` and the NFT, or anything else that the owner might want to specify\n pub let description: String\n\n init(recepient: Capability<&AnyResource{FungibleToken.Receiver}>, cut: UFix64, description: String) {\n pre {\n cut >= 0.0 && cut <= 1.0 : \"Cut value should be in valid range i.e [0,1]\"\n }\n self.receiver = recepient\n self.cut = cut\n self.description = description\n }\n }\n\n /// Get the path that should be used for receiving royalties\n /// This is a path that will eventually be used for a generic switchboard receiver,\n /// hence the name but will only be used for royalties for now.\n pub fun getRoyaltyReceiverPublicPath(): PublicPath {\n return /public/GenericFTReceiver\n }\n\n /// Medias is an optional view for collections that issue objects with multiple Media sources in it\n ///\n pub struct Medias {\n\n /// An arbitrary-sized list for any number of Media items\n pub let items: [Media]\n\n init(_ items: [Media]) {\n self.items = items\n }\n }\n\n /// A helper to get Medias in a typesafe way\n pub fun getMedias(_ viewResolver: &{Resolver}) : Medias? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? Medias {\n return v\n }\n }\n return nil\n }\n\n /// A view to represent Media, a file with an correspoiding mediaType.\n pub struct Media {\n\n /// File for the media\n pub let file: AnyStruct{File}\n\n /// media-type comes on the form of type/subtype as described here https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types\n pub let mediaType: String\n\n init(file: AnyStruct{File}, mediaType: String) {\n self.file=file\n self.mediaType=mediaType\n }\n }\n\n /// A license according to https://spdx.org/licenses/\n ///\n /// This view can be used if the content of an NFT is licensed. \n pub struct License {\n pub let spdxIdentifier: String\n\n init(_ identifier: String) {\n self.spdxIdentifier = identifier\n }\n }\n\n /// A helper to get License in a typesafe way\n pub fun getLicense(_ viewResolver: &{Resolver}) : License? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? License {\n return v\n }\n }\n return nil\n }\n\n\n /// A view to expose a URL to this item on an external site.\n ///\n /// This can be used by applications like .find and Blocto to direct users to the original link for an NFT.\n pub struct ExternalURL {\n pub let url: String\n\n init(_ url: String) {\n self.url=url\n }\n }\n\n /// A helper to get ExternalURL in a typesafe way\n pub fun getExternalURL(_ viewResolver: &{Resolver}) : ExternalURL? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? ExternalURL {\n return v\n }\n }\n return nil\n }\n\n // A view to expose the information needed store and retrieve an NFT\n //\n // This can be used by applications to setup a NFT collection with proper storage and public capabilities.\n pub struct NFTCollectionData {\n /// Path in storage where this NFT is recommended to be stored.\n pub let storagePath: StoragePath\n\n /// Public path which must be linked to expose public capabilities of this NFT\n /// including standard NFT interfaces and metadataviews interfaces\n pub let publicPath: PublicPath\n\n /// Private path which should be linked to expose the provider\n /// capability to withdraw NFTs from the collection holding NFTs\n pub let providerPath: PrivatePath\n\n /// Public collection type that is expected to provide sufficient read-only access to standard\n /// functions (deposit + getIDs + borrowNFT)\n /// This field is for backwards compatibility with collections that have not used the standard\n /// NonFungibleToken.CollectionPublic interface when setting up collections. For new\n /// collections, this may be set to be equal to the type specified in `publicLinkedType`.\n pub let publicCollection: Type\n\n /// Type that should be linked at the aforementioned public path. This is normally a\n /// restricted type with many interfaces. Notably the `NFT.CollectionPublic`,\n /// `NFT.Receiver`, and `MetadataViews.ResolverCollection` interfaces are required.\n pub let publicLinkedType: Type\n\n /// Type that should be linked at the aforementioned private path. This is normally\n /// a restricted type with at a minimum the `NFT.Provider` interface\n pub let providerLinkedType: Type\n\n /// Function that allows creation of an empty NFT collection that is intended to store\n /// this NFT.\n pub let createEmptyCollection: ((): @NonFungibleToken.Collection)\n\n init(\n storagePath: StoragePath,\n publicPath: PublicPath,\n providerPath: PrivatePath,\n publicCollection: Type,\n publicLinkedType: Type,\n providerLinkedType: Type,\n createEmptyCollectionFunction: ((): @NonFungibleToken.Collection)\n ) {\n pre {\n publicLinkedType.isSubtype(of: Type<&{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>()): \"Public type must include NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, and MetadataViews.ResolverCollection interfaces.\"\n providerLinkedType.isSubtype(of: Type<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection}>()): \"Provider type must include NonFungibleToken.Provider, NonFungibleToken.CollectionPublic, and MetadataViews.ResolverCollection interface.\"\n }\n self.storagePath=storagePath\n self.publicPath=publicPath\n self.providerPath = providerPath\n self.publicCollection=publicCollection\n self.publicLinkedType=publicLinkedType\n self.providerLinkedType = providerLinkedType\n self.createEmptyCollection=createEmptyCollectionFunction\n }\n }\n\n /// A helper to get NFTCollectionData in a way that will return an typed Optional\n pub fun getNFTCollectionData(_ viewResolver: &{Resolver}) : NFTCollectionData? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? NFTCollectionData {\n return v\n }\n }\n return nil\n }\n\n // A view to expose the information needed to showcase this NFT's collection\n //\n // This can be used by applications to give an overview and graphics of the NFT collection\n // this NFT belongs to.\n pub struct NFTCollectionDisplay {\n // Name that should be used when displaying this NFT collection.\n pub let name: String\n\n // Description that should be used to give an overview of this collection.\n pub let description: String\n\n // External link to a URL to view more information about this collection.\n pub let externalURL: ExternalURL\n\n // Square-sized image to represent this collection.\n pub let squareImage: Media\n\n // Banner-sized image for this collection, recommended to have a size near 1200x630.\n pub let bannerImage: Media\n\n // Social links to reach this collection's social homepages.\n // Possible keys may be \"instagram\", \"twitter\", \"discord\", etc.\n pub let socials: {String: ExternalURL}\n\n init(\n name: String,\n description: String,\n externalURL: ExternalURL,\n squareImage: Media,\n bannerImage: Media,\n socials: {String: ExternalURL}\n ) {\n self.name = name\n self.description = description\n self.externalURL = externalURL\n self.squareImage = squareImage\n self.bannerImage = bannerImage\n self.socials = socials\n }\n }\n\n /// A helper to get NFTCollectionDisplay in a way that will return an typed Optional\n pub fun getNFTCollectionDisplay(_ viewResolver: &{Resolver}) : NFTCollectionDisplay? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? NFTCollectionDisplay {\n return v\n }\n }\n return nil\n }\n\n // A view to represent a single field of metadata on an NFT.\n //\n // This is used to get traits of individual key/value pairs along with some contextualized data about the trait\n pub struct Trait {\n // The name of the trait. Like Background, Eyes, Hair, etc.\n pub let name: String\n\n // The underlying value of the trait, the rest of the fields of a trait provide context to the value.\n pub let value: AnyStruct\n\n // displayType is used to show some context about what this name and value represent\n // for instance, you could set value to a unix timestamp, and specify displayType as \"Date\" to tell\n // platforms to consume this trait as a date and not a number\n pub let displayType: String?\n\n // Rarity can also be used directly on an attribute.\n //\n // This is optional because not all attributes need to contribute to the NFT's rarity.\n pub let rarity: Rarity?\n\n init(name: String, value: AnyStruct, displayType: String?, rarity: Rarity?) {\n self.name = name\n self.value = value\n self.displayType = displayType\n self.rarity = rarity\n }\n }\n\n // A view to return all the traits on an NFT.\n //\n // This is used to return traits as individual key/value pairs along with some contextualized data about each trait.\n pub struct Traits {\n pub let traits: [Trait]\n\n init(_ traits: [Trait]) {\n self.traits = traits\n }\n\n pub fun addTrait(_ t: Trait) {\n self.traits.append(t)\n }\n }\n\n /// A helper to get Traits view in a typesafe way\n pub fun getTraits(_ viewResolver: &{Resolver}) : Traits? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? Traits {\n return v\n }\n }\n return nil\n }\n\n // A helper function to easily convert a dictionary to traits. For NFT collections that do not need either of the\n // optional values of a Trait, this method should suffice to give them an array of valid traits.\n pub fun dictToTraits(dict: {String: AnyStruct}, excludedNames: [String]?): Traits {\n // Collection owners might not want all the fields in their metadata included.\n // They might want to handle some specially, or they might just not want them included at all.\n if excludedNames != nil {\n for k in excludedNames! {\n dict.remove(key: k)\n }\n }\n\n let traits: [Trait] = []\n for k in dict.keys {\n let trait = Trait(name: k, value: dict[k]!, displayType: nil, rarity: nil)\n traits.append(trait)\n }\n\n return Traits(traits)\n }\n\n /// Rarity information for a single rarity\n //\n /// Note that a rarity needs to have either score or description but it can have both\n pub struct Rarity {\n /// The score of the rarity as a number\n ///\n pub let score: UFix64?\n\n /// The maximum value of score\n ///\n pub let max: UFix64?\n\n /// The description of the rarity as a string.\n ///\n /// This could be Legendary, Epic, Rare, Uncommon, Common or any other string value\n pub let description: String?\n\n init(score: UFix64?, max: UFix64?, description: String?) {\n if score == nil && description == nil {\n panic(\"A Rarity needs to set score, description or both\")\n }\n\n self.score = score\n self.max = max\n self.description = description\n }\n }\n\n /// A helper to get Rarity view in a typesafe way\n pub fun getRarity(_ viewResolver: &{Resolver}) : Rarity? {\n if let view = viewResolver.resolveView(Type()) {\n if let v = view as? Rarity {\n return v\n }\n }\n return nil\n }\n\n}\n", + "NonFungibleToken": "/**\n\n## The Flow Non-Fungible Token standard\n\n## `NonFungibleToken` contract interface\n\nThe interface that all non-fungible token contracts could conform to.\nIf a user wants to deploy a new nft contract, their contract would need\nto implement the NonFungibleToken interface.\n\nTheir contract would have to follow all the rules and naming\nthat the interface specifies.\n\n## `NFT` resource\n\nThe core resource type that represents an NFT in the smart contract.\n\n## `Collection` Resource\n\nThe resource that stores a user's NFT collection.\nIt includes a few functions to allow the owner to easily\nmove tokens in and out of the collection.\n\n## `Provider` and `Receiver` resource interfaces\n\nThese interfaces declare functions with some pre and post conditions\nthat require the Collection to follow certain naming and behavior standards.\n\nThey are separate because it gives the user the ability to share a reference\nto their Collection that only exposes the fields and functions in one or more\nof the interfaces. It also gives users the ability to make custom resources\nthat implement these interfaces to do various things with the tokens.\n\nBy using resources and interfaces, users of NFT smart contracts can send\nand receive tokens peer-to-peer, without having to interact with a central ledger\nsmart contract.\n\nTo send an NFT to another user, a user would simply withdraw the NFT\nfrom their Collection, then call the deposit function on another user's\nCollection to complete the transfer.\n\n*/\n\n// The main NFT contract interface. Other NFT contracts will\n// import and implement this interface\n//\npub contract interface NonFungibleToken {\n\n // The total number of tokens of this type in existence\n pub var totalSupply: UInt64\n\n // Event that emitted when the NFT contract is initialized\n //\n pub event ContractInitialized()\n\n // Event that is emitted when a token is withdrawn,\n // indicating the owner of the collection that it was withdrawn from.\n //\n // If the collection is not in an account's storage, `from` will be `nil`.\n //\n pub event Withdraw(id: UInt64, from: Address?)\n\n // Event that emitted when a token is deposited to a collection.\n //\n // It indicates the owner of the collection that it was deposited to.\n //\n pub event Deposit(id: UInt64, to: Address?)\n\n // Interface that the NFTs have to conform to\n //\n pub resource interface INFT {\n // The unique ID that each NFT has\n pub let id: UInt64\n }\n\n // Requirement that all conforming NFT smart contracts have\n // to define a resource called NFT that conforms to INFT\n pub resource NFT: INFT {\n pub let id: UInt64\n }\n\n // Interface to mediate withdraws from the Collection\n //\n pub resource interface Provider {\n // withdraw removes an NFT from the collection and moves it to the caller\n pub fun withdraw(withdrawID: UInt64): @NFT {\n post {\n result.id == withdrawID: \"The ID of the withdrawn token must be the same as the requested ID\"\n }\n }\n }\n\n // Interface to mediate deposits to the Collection\n //\n pub resource interface Receiver {\n\n // deposit takes an NFT as an argument and adds it to the Collection\n //\n pub fun deposit(token: @NFT)\n }\n\n // Interface that an account would commonly \n // publish for their collection\n pub resource interface CollectionPublic {\n pub fun deposit(token: @NFT)\n pub fun getIDs(): [UInt64]\n pub fun borrowNFT(id: UInt64): &NFT\n }\n\n // Requirement for the the concrete resource type\n // to be declared in the implementing contract\n //\n pub resource Collection: Provider, Receiver, CollectionPublic {\n\n // Dictionary to hold the NFTs in the Collection\n pub var ownedNFTs: @{UInt64: NFT}\n\n // withdraw removes an NFT from the collection and moves it to the caller\n pub fun withdraw(withdrawID: UInt64): @NFT\n\n // deposit takes a NFT and adds it to the collections dictionary\n // and adds the ID to the id array\n pub fun deposit(token: @NFT)\n\n // getIDs returns an array of the IDs that are in the collection\n pub fun getIDs(): [UInt64]\n\n // Returns a borrowed reference to an NFT in the collection\n // so that the caller can read data and call methods from it\n pub fun borrowNFT(id: UInt64): &NFT {\n pre {\n self.ownedNFTs[id] != nil: \"NFT does not exist in the collection!\"\n }\n }\n }\n\n // createEmptyCollection creates an empty Collection\n // and returns it to the caller so that they can own NFTs\n pub fun createEmptyCollection(): @Collection {\n post {\n result.getIDs().length == 0: \"The created collection must be empty!\"\n }\n }\n}\n", }, }, "mainnet": overflow.OverflowSolutionMergedNetwork{ @@ -317,18 +318,9 @@ transaction(test:Address) { }, }, "create_nft_collection": overflow.OverflowCodeWithSpec{ - Code: `import NonFungibleToken from 0x1d7e57aa55817448 - -// This transaction creates an empty NFT Collection in the signer's account + Code: `// This transaction creates an empty NFT Collection in the signer's account transaction { prepare(acct: AuthAccount) { - // store an empty NFT Collection in account storage - acct.save<@NonFungibleToken.Collection>(<-NonFungibleToken.createEmptyCollection(), to: /storage/NFTCollection) - - // publish a capability to the Collection in storage - acct.link<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection) - - log("Created a new empty collection and published a reference") } }`, Spec: &overflow.OverflowDeclarationInfo{ @@ -396,6 +388,15 @@ transaction(test:String) { pub contract Debug { +pub struct FooBar { + pub let foo:Foo + pub let bar:String + + init(foo:Foo, bar:String) { + self.foo=foo + self.bar=bar + } +} pub struct Foo{ pub let bar: String @@ -526,18 +527,9 @@ transaction(test:Address) { }, }, "create_nft_collection": overflow.OverflowCodeWithSpec{ - Code: `import NonFungibleToken from 0x631e88ae7f1d7c20 - -// This transaction creates an empty NFT Collection in the signer's account + Code: `// This transaction creates an empty NFT Collection in the signer's account transaction { prepare(acct: AuthAccount) { - // store an empty NFT Collection in account storage - acct.save<@NonFungibleToken.Collection>(<-NonFungibleToken.createEmptyCollection(), to: /storage/NFTCollection) - - // publish a capability to the Collection in storage - acct.link<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection) - - log("Created a new empty collection and published a reference") } }`, Spec: &overflow.OverflowDeclarationInfo{ @@ -605,6 +597,15 @@ transaction(test:String) { pub contract Debug { +pub struct FooBar { + pub let foo:Foo + pub let bar:String + + init(foo:Foo, bar:String) { + self.foo=foo + self.bar=bar + } +} pub struct Foo{ pub let bar: String diff --git a/transaction_integration_old_test.go b/transaction_integration_old_test.go index 2a13cbf..64b5060 100644 --- a/transaction_integration_old_test.go +++ b/transaction_integration_old_test.go @@ -10,7 +10,7 @@ import ( ) /* - Tests must be in the same folder as flow.json with contracts and transactions/scripts in subdirectories in order for the path resolver to work correctly +Tests must be in the same folder as flow.json with contracts and transactions/scripts in subdirectories in order for the path resolver to work correctly */ func TestTransactionIntegrationLegacy(t *testing.T) { logNumName := "A.f8d6e0586b0a20c7.Debug.LogNum" @@ -38,14 +38,6 @@ func TestTransactionIntegrationLegacy(t *testing.T) { AssertFailure("Could not read interaction file from path=./transactions/create_nf_collection.cdc") //we assert that there is a failure }) - t.Run("Create NFT collection with different base path", func(t *testing.T) { - g.TransactionFromFile("create_nft_collection"). - SignProposeAndPayAs("first"). - TransactionPath("./tx"). - Test(t). //This method will return a TransactionResult that we can assert upon - AssertSuccess() //Assert that there are no errors and that the transactions succeeds - }) - t.Run("Mint tokens assert events", func(t *testing.T) { result := g.TransactionFromFile("mint_tokens"). SignProposeAndPayAsService(). diff --git a/transaction_integration_test.go b/transaction_integration_test.go index 6f66126..c35f405 100644 --- a/transaction_integration_test.go +++ b/transaction_integration_test.go @@ -11,7 +11,11 @@ import ( */ func TestTransactionIntegration(t *testing.T) { - o, err := OverflowTesting(WithLogFull()) + + customResolver := func(input string) (string, error) { + return "A.f8d6e0586b0a20c7.Debug.Foo", nil + } + o, err := OverflowTesting() o.Tx("mint_tokens", WithSignerServiceAccount(), WithArg("recipient", "first"), WithArg("amount", 1.0)).AssertSuccess(t) assert.NoError(t, err) @@ -26,11 +30,12 @@ func TestTransactionIntegration(t *testing.T) { AssertFailure(t, "💩 Could not read interaction file from path=./transactions/create_nft_collectio.cdc") }) - t.Run("Create NFT collection with different base path", func(t *testing.T) { - o.Tx("../tx/create_nft_collection", - WithSigner("first")). - AssertSuccess(t). - AssertNoEvents(t) + t.Run("mint tokens with different base path", func(t *testing.T) { + o.Tx("../tx/mint_tokens", + WithSignerServiceAccount(), + WithArg("recipient", "first"), + WithArg("amount", 100.1)). + AssertSuccess(t) }) t.Run("Mint tokens assert events", func(t *testing.T) { @@ -101,7 +106,176 @@ func TestTransactionIntegration(t *testing.T) { assert.NotNil(t, singleEvent) }) + t.Run("Send struct to transaction", func(t *testing.T) { + + o.Tx(` + import Debug from "../contracts/Debug.cdc" + transaction(foo: Debug.Foo) { + prepare(acct: AuthAccount) { + } + }`, + WithSigner("first"), + WithArg("foo", Debug_Foo{Bar: "baz"}), + ).AssertSuccess(t) + + }) + + t.Run("Send struct to transaction With Skip field", func(t *testing.T) { + + o.Tx(` + import Debug from "../contracts/Debug.cdc" + transaction(foo: Debug.Foo) { + prepare(acct: AuthAccount) { + } + }`, + WithSigner("first"), + WithArg("foo", Debug_Foo_Skip{Bar: "baz", Skip: "skip"}), + ).AssertSuccess(t) + + }) + + t.Run("Send list of struct to transaction custom qualifier", func(t *testing.T) { + + o.Tx(` + import Debug from "../contracts/Debug.cdc" + transaction(foo: [Debug.Foo]) { + prepare(acct: AuthAccount) { + } + }`, + WithSigner("first"), + WithStructArgsCustomQualifier("foo", customResolver, Foo{Bar: "baz"}, Foo{Bar: "baz2"}), + ).AssertSuccess(t) + + }) + + t.Run("Send struct to transaction custom qualifier", func(t *testing.T) { + + o.Tx(` + import Debug from "../contracts/Debug.cdc" + transaction(foo: Debug.Foo) { + prepare(acct: AuthAccount) { + } + }`, + WithSigner("first"), + WithStructArgCustomResolver("foo", customResolver, Foo{Bar: "baz"}), + ).AssertSuccess(t) + + }) + + t.Run("Send list of struct to transaction", func(t *testing.T) { + + o.Tx(` + import Debug from "../contracts/Debug.cdc" + transaction(foo: [Debug.Foo]) { + prepare(acct: AuthAccount) { + } + }`, + WithSigner("first"), + WithArgs("foo", []Debug_Foo{{Bar: "baz"}, {Bar: "baz2"}}), + ).AssertSuccess(t) + + }) + + t.Run("Send nestedstruct to transaction", func(t *testing.T) { + + o.Tx(` + import Debug from "../contracts/Debug.cdc" + transaction(foo: Debug.FooBar) { + prepare(acct: AuthAccount) { + } + }`, + WithSigner("first"), + WithArg("foo", Debug_FooBar{Bar: "bar", Foo: Debug_Foo{Bar: "baz"}}), + ).AssertSuccess(t) + + }) + + t.Run("Send HttpFile to transaction", func(t *testing.T) { + + o.Tx(` + import MetadataViews from "../contracts/MetadataViews.cdc" + transaction(foo: AnyStruct{MetadataViews.File}) { + prepare(acct: AuthAccount) { + } + }`, + WithSigner("first"), + WithArg("foo", MetadataViews_HTTPFile{Url: "foo"}), + ).AssertSuccess(t) + + }) + + t.Run("Send IpfsFile to transaction", func(t *testing.T) { + + o.Tx(` + import MetadataViews from "../contracts/MetadataViews.cdc" + transaction(foo: AnyStruct{MetadataViews.File}) { + prepare(acct: AuthAccount) { + } + }`, + WithSigner("first"), + WithArg("foo", MetadataViews_IPFSFile{Cid: "foo"}), + ).AssertSuccess(t) + + }) + + t.Run("Send IpfsFile with path to transaction", func(t *testing.T) { + + path := "/Foo" + o.Tx(` + import MetadataViews from "../contracts/MetadataViews.cdc" + transaction(foo: AnyStruct{MetadataViews.File}) { + prepare(acct: AuthAccount) { + } + }`, + WithSigner("first"), + WithArg("foo", MetadataViews_IPFSFile{Cid: "foo", Path: &path}), + ).AssertSuccess(t) + + }) + + t.Run("Send IpfsDisplay to transaction", func(t *testing.T) { + + o.Tx(` + import MetadataViews from "../contracts/MetadataViews.cdc" + transaction(display: MetadataViews.Display) { + prepare(acct: AuthAccount) { + } + }`, + WithSigner("first"), + WithArg("display", MetadataViews_Display_IPFS{Name: "foo", Description: "desc", Thumbnail: MetadataViews_IPFSFile{Cid: "foo"}}), + ).AssertSuccess(t) + + }) + + t.Run("Send HttpDisplay to transaction", func(t *testing.T) { + + o.Tx(` + import MetadataViews from "../contracts/MetadataViews.cdc" + transaction(display: MetadataViews.Display) { + prepare(acct: AuthAccount) { + } + }`, + WithSigner("first"), + WithArg("display", MetadataViews_Display_Http{Name: "foo", Description: "desc", Thumbnail: MetadataViews_HTTPFile{Url: "foo"}}), + ).AssertSuccess(t) + + }) + + t.Run("Send Trait to transaction", func(t *testing.T) { + + o.Tx(` + import MetadataViews from "../contracts/MetadataViews.cdc" + transaction(trait: MetadataViews.Trait) { + prepare(acct: AuthAccount) { + } + }`, + WithSigner("first"), + WithArg("trait", MetadataViews_Trait{Name: "foo", Value: "bar"}), + ).AssertSuccess(t) + + }) } + func TestTransactionEventFiltering(t *testing.T) { filter := OverflowEventFilter{ diff --git a/transaction_v3_test.go b/transaction_v3_test.go index 0fb8fe6..22a0e67 100644 --- a/transaction_v3_test.go +++ b/transaction_v3_test.go @@ -58,7 +58,7 @@ transaction(test:UInt64) { log(test) } } -`, WithArg("test", 1), WithSignerServiceAccount()) +`, WithArg("test", uint64(1)), WithSignerServiceAccount()) assert.NoError(t, res.Err) }) diff --git a/transactions/create_nft_collection.cdc b/transactions/create_nft_collection.cdc index 5063ce6..4dc860e 100644 --- a/transactions/create_nft_collection.cdc +++ b/transactions/create_nft_collection.cdc @@ -1,14 +1,6 @@ -import NonFungibleToken from "../contracts/NonFungibleToken.cdc" // This transaction creates an empty NFT Collection in the signer's account transaction { prepare(acct: AuthAccount) { - // store an empty NFT Collection in account storage - acct.save<@NonFungibleToken.Collection>(<-NonFungibleToken.createEmptyCollection(), to: /storage/NFTCollection) - - // publish a capability to the Collection in storage - acct.link<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection) - - log("Created a new empty collection and published a reference") } -} \ No newline at end of file +} diff --git a/tx/mint_tokens.cdc b/tx/mint_tokens.cdc new file mode 100644 index 0000000..f257bb9 --- /dev/null +++ b/tx/mint_tokens.cdc @@ -0,0 +1,28 @@ +import FungibleToken from 0xee82856bf20e2aa6 +import FlowToken from 0x0ae53cb6e3f42a79 + + +transaction(recipient: Address, amount: UFix64) { + let tokenAdmin: &FlowToken.Administrator + let tokenReceiver: &{FungibleToken.Receiver} + + prepare(signer: AuthAccount) { + self.tokenAdmin = signer + .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") + + self.tokenReceiver = getAccount(recipient) + .getCapability(/public/flowTokenReceiver) + .borrow<&{FungibleToken.Receiver}>() + ?? panic("Unable to borrow receiver reference") + } + + execute { + let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) + let mintedVault <- minter.mintTokens(amount: amount) + + self.tokenReceiver.deposit(from: <-mintedVault) + + destroy minter + } +} \ No newline at end of file