diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 8e0f664..96cbdbe 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -26,6 +26,6 @@ jobs: - name: Install Flow dependencies run: npm i - name: Install Flow CLI - run: bash -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)" -- v1.8.0 + run: bash -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)" - name: Run tests run: sh ./run-tests.sh \ No newline at end of file diff --git a/CLI_SCRIPTS.md b/CLI_SCRIPTS.md index be5896e..b55bd4c 100644 --- a/CLI_SCRIPTS.md +++ b/CLI_SCRIPTS.md @@ -1,4 +1,4 @@ -Flow CLI ready to paste scripts for Flowty Wrapper transactions and scripts +Flow CLI ready to paste scripts for Flowty Wrapped transactions and scripts 1 - Create new emulator account (if you already made this step before but restarted emulator, remove emulator-1 object from flow.json) diff --git a/contracts/FlowtyWrapped.cdc b/contracts/FlowtyWrapped.cdc index ca4c52e..a62d192 100644 --- a/contracts/FlowtyWrapped.cdc +++ b/contracts/FlowtyWrapped.cdc @@ -6,35 +6,37 @@ import "FungibleToken" import "FlowtyRaffles" import "FlowtyRaffleSource" -pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { +access(all) contract FlowtyWrapped: NonFungibleToken, ViewResolver { // Total supply of FlowtyWrapped NFTs - pub var totalSupply: UInt64 - pub var collectionExternalUrl: String - pub var nftExternalBaseUrl: String + access(all) var totalSupply: UInt64 + access(all) var collectionExternalUrl: String + access(all) var nftExternalBaseUrl: String access(account) let editions: {String: {WrappedEdition}} + access(all) entitlement Owner + /// The event that is emitted when the contract is created - pub event ContractInitialized() + access(all) event ContractInitialized() - pub event CollectionCreated(uuid: UInt64) + access(all) event CollectionCreated(uuid: UInt64) /// The event that is emitted when an NFT is withdrawn from a Collection - pub event Withdraw(id: UInt64, from: Address?) + access(all) event Withdraw(id: UInt64, from: Address?) /// The event that is emitted when an NFT is deposited to a Collection - pub event Deposit(id: UInt64, to: Address?) + access(all) event Deposit(id: UInt64, to: Address?) /// Storage and Public Paths - pub let CollectionStoragePath: StoragePath - pub let CollectionPublicPath: PublicPath - pub let CollectionProviderPath: PrivatePath - pub let AdminStoragePath: StoragePath - pub let AdminPublicPath: PublicPath + access(all) let CollectionStoragePath: StoragePath + access(all) let CollectionPublicPath: PublicPath + access(all) let CollectionProviderPath: PrivatePath + access(all) let AdminStoragePath: StoragePath + access(all) let AdminPublicPath: PublicPath - pub struct interface WrappedEdition { - pub fun getName(): String - pub fun resolveView(_ t: Type, _ nft: &NFT): AnyStruct? - pub fun getEditionSupply(): UInt64 + access(all) struct interface WrappedEdition { + access(all) fun getName(): String + access(all) fun resolveView(_ t: Type, _ nft: &NFT): AnyStruct? + access(all) fun getEditionSupply(): UInt64 access(account) fun setStatus(_ s: String) access(account) fun mint(address: Address, data: {String: AnyStruct}): @NFT @@ -44,13 +46,13 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { /// New instances will be created using the NFTMinter resource /// and stored in the Collection resource /// - pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver { + access(all) resource NFT: NonFungibleToken.NFT { /// The unique ID that each NFT has - pub let id: UInt64 - pub let serial: UInt64 - pub let editionName: String - pub let address: Address - pub let data: {String: AnyStruct} + access(all) let id: UInt64 + access(all) let serial: UInt64 + access(all) let editionName: String + access(all) let address: Address + access(all) let data: {String: AnyStruct} init( id: UInt64, @@ -71,7 +73,7 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { /// @return An array of Types defining the implemented views. This value will be used by /// developers to know which parameter to pass to the resolveView() method. /// - pub fun getViews(): [Type] { + access(all) view fun getViews(): [Type] { return [ Type(), Type(), @@ -90,7 +92,7 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { /// @param view: The Type of the desired view. /// @return A structure representing the requested view. /// - pub fun resolveView(_ view: Type): AnyStruct? { + access(all) fun resolveView(_ view: Type): AnyStruct? { switch view { case Type(): let edition = FlowtyWrapped.getEditionRef(self.editionName) @@ -110,24 +112,25 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { case Type(): return MetadataViews.ExternalURL(FlowtyWrapped.nftExternalBaseUrl.concat("/").concat(self.id.toString())) case Type(): - return FlowtyWrapped.resolveView(view) + return FlowtyWrapped.resolveContractView(resourceType: Type<@NFT>(), viewType: view) case Type(): - return FlowtyWrapped.resolveView(view) + return FlowtyWrapped.resolveContractView(resourceType: Type<@NFT>(), viewType: view) case Type(): let edition = FlowtyWrapped.getEditionRef(self.editionName) return edition.resolveView(view, &self as &NFT) } return nil } + + access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} { + return <- create Collection() + } } /// Defines the methods that are particular to this NFT contract collection /// - pub resource interface FlowtyWrappedCollectionPublic { - pub fun deposit(token: @NonFungibleToken.NFT) - pub fun getIDs(): [UInt64] - pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT - pub fun borrowFlowtyWrapped(id: UInt64): &FlowtyWrapped.NFT? { + access(all) resource interface FlowtyWrappedCollectionPublic: NonFungibleToken.Collection { + access(all) fun borrowFlowtyWrapped(_ id: UInt64): &FlowtyWrapped.NFT? { post { (result == nil) || (result?.id == id): "Cannot borrow FlowtyWrapped reference: the ID of the returned reference is incorrect" @@ -139,10 +142,10 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { /// In order to be able to manage NFTs any account will need to create /// an empty collection first /// - pub resource Collection: FlowtyWrappedCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection { + access(all) resource Collection: FlowtyWrappedCollectionPublic { // dictionary of NFT conforming tokens // NFT is a resource type with an `UInt64` ID field - pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT} + access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}} init () { self.ownedNFTs <- {} @@ -153,7 +156,7 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { /// @param withdrawID: The ID of the NFT that wants to be withdrawn /// @return The NFT resource that has been taken out of the collection /// - pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT { + access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} { assert(false, message: "Flowty Wrapped is not transferrable.") let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT") @@ -167,7 +170,7 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { /// /// @param token: The NFT resource to be included in the collection /// - pub fun deposit(token: @NonFungibleToken.NFT) { + access(all) fun deposit(token: @{NonFungibleToken.NFT}) { let token <- token as! @FlowtyWrapped.NFT let nftOwnerAddress = token.address @@ -187,7 +190,7 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { /// /// @return An array containing the IDs of the NFTs in the collection /// - pub fun getIDs(): [UInt64] { + access(all) view fun getIDs(): [UInt64] { return self.ownedNFTs.keys } @@ -197,8 +200,8 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { /// @param id: The ID of the wanted NFT /// @return A reference to the wanted NFT resource /// - pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT { - return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)! + access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? { + return &self.ownedNFTs[id] } /// Gets a reference to an NFT in the collection so that @@ -207,30 +210,28 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { /// @param id: The ID of the wanted NFT /// @return A reference to the wanted NFT resource /// - pub fun borrowFlowtyWrapped(id: UInt64): &FlowtyWrapped.NFT? { + access(all) view fun borrowFlowtyWrapped(_ id: UInt64): &FlowtyWrapped.NFT? { if self.ownedNFTs[id] != nil { // Create an authorized reference to allow downcasting - let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)! + let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)! return ref as! &FlowtyWrapped.NFT } return nil } - /// Gets a reference to the NFT only conforming to the `{MetadataViews.Resolver}` - /// interface so that the caller can retrieve the views that the NFT - /// is implementing and resolve them - /// - /// @param id: The ID of the wanted NFT - /// @return The resource reference conforming to the Resolver interface - /// - pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} { - let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)! - return nft as! &FlowtyWrapped.NFT + access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} { + return <- create Collection() + } + + access(all) view fun getSupportedNFTTypes(): {Type: Bool} { + return { + Type<@NFT>(): true + } } - destroy() { - destroy self.ownedNFTs + access(all) view fun isSupportedNFTType(type: Type): Bool { + return type == Type<@NFT>() } } @@ -238,24 +239,28 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { /// /// @return The new Collection resource /// - pub fun createEmptyCollection(): @NonFungibleToken.Collection { + access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} { + pre { + nftType == Type<@NFT>() + } + let c <- create Collection() emit CollectionCreated(uuid: c.uuid) return <- c } - pub resource interface AdminPublic {} + access(all) resource interface AdminPublic {} /// Resource that an admin or something similar would own to be /// able to mint new NFTs /// - pub resource Admin: AdminPublic { + access(all) resource Admin: AdminPublic { /// Mints a new NFT with a new ID and deposit it in the /// recipients collection using their collection reference /// /// @param recipient: A capability to the collection where the new NFT will be deposited /// - pub fun mintNFT(editionName: String, address: Address, data: {String: AnyStruct}): @FlowtyWrapped.NFT { + access(Owner) fun mintNFT(editionName: String, address: Address, data: {String: AnyStruct}): @FlowtyWrapped.NFT { // we want IDs to start at 1, so we'll increment first FlowtyWrapped.totalSupply = FlowtyWrapped.totalSupply + 1 @@ -265,24 +270,21 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { return <- nft } - pub fun getEdition(editionName: String): AnyStruct{ + access(Owner) fun getEdition(editionName: String): AnyStruct{ let edition = FlowtyWrapped.getEditionRef(editionName) return edition } - pub fun registerEdition(_ edition: {WrappedEdition}) { - pre { - FlowtyWrapped.editions[edition.getName()] == nil: "edition name already exists" - } - + access(Owner) fun registerEdition(_ edition: {WrappedEdition}) { + assert(FlowtyWrapped.editions[edition.getName()] == nil, message: "edition name already exists") FlowtyWrapped.editions[edition.getName()] = edition } - pub fun setCollectionExternalUrl(_ s: String) { + access(Owner) fun setCollectionExternalUrl(_ s: String) { FlowtyWrapped.collectionExternalUrl = s } - pub fun createAdmin(): @Admin { + access(Owner) fun createAdmin(): @Admin { return <- create Admin() } } @@ -292,18 +294,16 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { /// @param view: The Type of the desired view. /// @return A structure representing the requested view. /// - pub fun resolveView(_ view: Type): AnyStruct? { - switch view { + access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? { + switch viewType { case Type(): return MetadataViews.NFTCollectionData( storagePath: FlowtyWrapped.CollectionStoragePath, publicPath: FlowtyWrapped.CollectionPublicPath, - providerPath: FlowtyWrapped.CollectionProviderPath, - publicCollection: Type<&FlowtyWrapped.Collection{FlowtyWrapped.FlowtyWrappedCollectionPublic}>(), - publicLinkedType: Type<&FlowtyWrapped.Collection{FlowtyWrapped.FlowtyWrappedCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver,MetadataViews.ResolverCollection}>(), - providerLinkedType: Type<&FlowtyWrapped.Collection{FlowtyWrapped.FlowtyWrappedCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Provider,MetadataViews.ResolverCollection}>(), - createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection { - return <-FlowtyWrapped.createEmptyCollection() + publicCollection: Type<&FlowtyWrapped.Collection>(), + publicLinkedType: Type<&FlowtyWrapped.Collection>(), + createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} { + return <-FlowtyWrapped.createEmptyCollection(nftType: Type<@NFT>()) }) ) case Type(): @@ -313,15 +313,15 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { externalURL: MetadataViews.ExternalURL(FlowtyWrapped.collectionExternalUrl), squareImage: MetadataViews.Media( file: MetadataViews.IPFSFile( - url: "QmdCiwwJ7z2gQecDr6hn4pJj91miWYnFC178o9p6JKftmi", - nil + cid: "QmdCiwwJ7z2gQecDr6hn4pJj91miWYnFC178o9p6JKftmi", + path: nil ), mediaType: "image/jpg" ), bannerImage: MetadataViews.Media( file: MetadataViews.IPFSFile( - url: "QmcLJhJh6yuLAoH6wWKMDS2zUv6myduXQc83zD5xv2V8tA", - nil + cid: "QmcLJhJh6yuLAoH6wWKMDS2zUv6myduXQc83zD5xv2V8tA", + path: nil ), mediaType: "image/jpg" ), @@ -338,19 +338,19 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { /// @return An array of Types defining the implemented views. This value will be used by /// developers to know which parameter to pass to the resolveView() method. /// - pub fun getViews(): [Type] { + access(all) view fun getContractViews(resourceType: Type?): [Type] { return [ Type(), Type() ] } - access(account) fun getRaffleManager(): &FlowtyRaffles.Manager { - return self.account.borrow<&FlowtyRaffles.Manager>(from: FlowtyRaffles.ManagerStoragePath)! + access(account) fun getRaffleManager(): auth(FlowtyRaffles.Manage) &FlowtyRaffles.Manager { + return self.account.storage.borrow(from: FlowtyRaffles.ManagerStoragePath)! } access(contract) fun borrowAdmin(): &Admin { - return self.account.borrow<&Admin>(from: self.AdminStoragePath)! + return self.account.storage.borrow<&Admin>(from: self.AdminStoragePath)! } access(account) fun mint( @@ -369,11 +369,11 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { return (&self.editions[name] as &{WrappedEdition}?)! } - pub fun getEdition(_ name: String): {WrappedEdition} { + access(all) fun getEdition(_ name: String): {WrappedEdition} { return self.editions[name] ?? panic("no edition found with given name") } - pub fun getAccountAddress(): Address { + access(all) fun getAccountAddress(): Address { return self.account.address } @@ -392,24 +392,30 @@ pub contract FlowtyWrapped: NonFungibleToken, ViewResolver { // Create a Collection resource and save it to storage let collection <- create Collection() - self.account.save(<-collection, to: self.CollectionStoragePath) + self.account.storage.save(<-collection, to: self.CollectionStoragePath) // create a public capability for the collection - self.account.link<&FlowtyWrapped.Collection{NonFungibleToken.CollectionPublic, FlowtyWrapped.FlowtyWrappedCollectionPublic, MetadataViews.ResolverCollection}>( - self.CollectionPublicPath, - target: self.CollectionStoragePath + self.account.capabilities.publish( + self.account.capabilities.storage.issue<&FlowtyWrapped.Collection>(self.CollectionStoragePath), + at: self.CollectionPublicPath ) // Create a Minter resource and save it to storage let minter <- create Admin() - self.account.save(<-minter, to: self.AdminStoragePath) - self.account.link<&Admin{AdminPublic}>(self.AdminPublicPath, target: self.AdminStoragePath) + self.account.storage.save(<-minter, to: self.AdminStoragePath) + self.account.capabilities.publish( + self.account.capabilities.storage.issue<&{AdminPublic}>(self.AdminStoragePath), + at: self.AdminPublicPath + ) emit ContractInitialized() let manager <- FlowtyRaffles.createManager() - self.account.save(<-manager, to: FlowtyRaffles.ManagerStoragePath) - self.account.link<&FlowtyRaffles.Manager{FlowtyRaffles.ManagerPublic}>(FlowtyRaffles.ManagerPublicPath, target: FlowtyRaffles.ManagerStoragePath) + self.account.storage.save(<-manager, to: FlowtyRaffles.ManagerStoragePath) + self.account.capabilities.publish( + self.account.capabilities.storage.issue<&FlowtyRaffles.Manager>(FlowtyRaffles.ManagerStoragePath), + at: FlowtyRaffles.ManagerPublicPath + ) self.collectionExternalUrl = "https://flowty.io/collection/".concat(self.account.address.toString()).concat("/FlowtyWrapped") self.nftExternalBaseUrl = "https://flowty.io/asset/".concat(self.account.address.toString()).concat("/FlowtyWrapped") diff --git a/contracts/WrappedEditions.cdc b/contracts/WrappedEditions.cdc index 6d15913..87398f1 100644 --- a/contracts/WrappedEditions.cdc +++ b/contracts/WrappedEditions.cdc @@ -2,17 +2,17 @@ import "MetadataViews" import "FlowtyWrapped" import "StringUtils" -pub contract WrappedEditions { - pub struct Wrapped2023Data { - pub let username: String? - pub let tickets: Int +access(all) contract WrappedEditions { + access(all) struct Wrapped2023Data { + access(all) let username: String? + access(all) let tickets: Int - pub let totalNftsOwned: Int - pub let floatCount: Int - pub let favoriteCollections: [String] // type identifier of each collection - pub let collections: [String] // type identifier of each collection + access(all) let totalNftsOwned: Int + access(all) let floatCount: Int + access(all) let favoriteCollections: [String] // type identifier of each collection + access(all) let collections: [String] // type identifier of each collection - pub fun toTraits(): MetadataViews.Traits { + access(all) fun toTraits(): MetadataViews.Traits { let traits: [MetadataViews.Trait] = [ WrappedEditions.buildTrait("username", self.username), WrappedEditions.buildTrait("tickets", self.tickets), @@ -25,7 +25,7 @@ pub contract WrappedEditions { return MetadataViews.Traits(traits) } - init(_ username: String?, _ tickets: Int, totalNftsOwned: Int, floatCount: Int, favoriteCollections: [String], collections: [String]) { + init(_ username: String?, _ tickets: Int, _ totalNftsOwned: Int, _ floatCount: Int, _ favoriteCollections: [String], _ collections: [String]) { self.username = username self.tickets = tickets self.totalNftsOwned = totalNftsOwned @@ -35,19 +35,19 @@ pub contract WrappedEditions { } } - pub struct Edition2023: FlowtyWrapped.WrappedEdition { - pub let name: String - pub var supply: UInt64 - pub var baseImageUrl: String - pub var baseHtmlUrl: String + access(all) struct Edition2023: FlowtyWrapped.WrappedEdition { + access(all) let name: String + access(all) var supply: UInt64 + access(all) var baseImageUrl: String + access(all) var baseHtmlUrl: String - pub let raffleID: UInt64 - pub var status: String + access(all) let raffleID: UInt64 + access(all) var status: String - pub let mintedAddresses: {Address: Bool} + access(all) let mintedAddresses: {Address: Bool} - pub fun resolveView(_ t: Type, _ nft: &FlowtyWrapped.NFT): AnyStruct? { - let wrapped = nft.data["wrapped"]! as! Wrapped2023Data + access(all) fun resolveView(_ t: Type, _ nft: &FlowtyWrapped.NFT): AnyStruct? { + let wrapped = nft.data["wrapped"]! as! &Wrapped2023Data switch t { case Type(): return MetadataViews.Display( @@ -71,7 +71,7 @@ pub contract WrappedEditions { let params = "?username=".concat(username).concat("&raffleTickets=").concat(wrapped.tickets.toString()) let htmlMedia = MetadataViews.Media( - file: MetadataViews.IPFSFile("QmRfVR98oe6qxeWFcnY9tfM2CLUJg3rvxbBPS5LjYwp69Z".concat(params), nil), mediaType: "text/html" + file: MetadataViews.IPFSFile(cid: "QmRfVR98oe6qxeWFcnY9tfM2CLUJg3rvxbBPS5LjYwp69Z".concat(params), path: nil), mediaType: "text/html" ) let imageMedia = MetadataViews.Media( file: MetadataViews.HTTPFile(url: self.baseImageUrl.concat(nft.serial.toString())), mediaType: "image/jpeg" @@ -84,7 +84,7 @@ pub contract WrappedEditions { return nil } - pub fun getEditionSupply(): UInt64 { + access(all) fun getEditionSupply(): UInt64 { return self.supply } @@ -115,19 +115,19 @@ pub contract WrappedEditions { return <- nft } - pub fun getName(): String { + access(all) fun getName(): String { return self.name } - pub fun setStatus(_ s: String) { + access(account) fun setStatus(_ s: String) { self.status = s } - pub fun setBaseImageUrl(_ s: String) { + access(FlowtyWrapped.Owner) fun setBaseImageUrl(_ s: String) { self.baseImageUrl = s } - pub fun setBaseHtmlUrl(_ s: String) { + access(FlowtyWrapped.Owner) fun setBaseHtmlUrl(_ s: String) { self.baseHtmlUrl = s } @@ -143,7 +143,7 @@ pub contract WrappedEditions { } } - pub fun buildTrait(_ name: String, _ value: AnyStruct): MetadataViews.Trait { + access(all) fun buildTrait(_ name: String, _ value: AnyStruct): MetadataViews.Trait { return MetadataViews.Trait(name: name, value: value, displayType: nil, rarity: nil) } } \ No newline at end of file diff --git a/contracts/raffle/FlowtyRaffleSource.cdc b/contracts/raffle/FlowtyRaffleSource.cdc index 2517d36..d6e8e66 100644 --- a/contracts/raffle/FlowtyRaffleSource.cdc +++ b/contracts/raffle/FlowtyRaffleSource.cdc @@ -13,29 +13,29 @@ In addition to entryType, a field called `removeAfterReveal` is also provided, w from the entries array any time a reveal is performed. This is useful for cases where you don't want the same entry to be able to be drawn multiple times. */ -pub contract FlowtyRaffleSource { - pub resource AnyStructRaffleSource: FlowtyRaffles.RaffleSourcePublic, FlowtyRaffles.RaffleSourcePrivate { - pub let entries: [AnyStruct] - pub let entryType: Type - pub let removeAfterReveal: Bool +access(all) contract FlowtyRaffleSource { + access(all) resource AnyStructRaffleSource: FlowtyRaffles.RaffleSourcePublic, FlowtyRaffles.RaffleSourcePrivate { + access(all) let entries: [AnyStruct] + access(all) let entryType: Type + access(all) let removeAfterReveal: Bool - pub fun getEntryType(): Type { + access(all) fun getEntryType(): Type { return self.entryType } - pub fun getEntryAt(index: Int): AnyStruct { + access(all) fun getEntryAt(index: Int): AnyStruct { return self.entries[index] } - pub fun getEntries(): [AnyStruct] { + access(all) fun getEntries(): [AnyStruct] { return self.entries } - pub fun getEntryCount(): Int { + access(all) fun getEntryCount(): Int { return self.entries.length } - pub fun addEntry(_ v: AnyStruct) { + access(FlowtyRaffles.Add) fun addEntry(_ v: AnyStruct) { pre { v.getType() == self.entryType: "incorrect entry type" } @@ -43,7 +43,7 @@ pub contract FlowtyRaffleSource { self.entries.append(v) } - pub fun addEntries(_ v: [AnyStruct]) { + access(FlowtyRaffles.Add) fun addEntries(_ v: [AnyStruct]) { pre { VariableSizedArrayType(self.entryType) == v.getType(): "incorrect array type" } @@ -51,7 +51,7 @@ pub contract FlowtyRaffleSource { self.entries.appendAll(v) } - pub fun revealCallback(drawingResult: FlowtyRaffles.DrawingResult) { + access(contract) fun revealCallback(drawingResult: FlowtyRaffles.DrawingResult) { if !self.removeAfterReveal { return } @@ -66,7 +66,7 @@ pub contract FlowtyRaffleSource { } } - pub fun createRaffleSource(entryType: Type, removeAfterReveal: Bool): @AnyStructRaffleSource { + access(all) fun createRaffleSource(entryType: Type, removeAfterReveal: Bool): @AnyStructRaffleSource { pre { entryType.isSubtype(of: Type()): "entry type must be a subtype of AnyStruct" } diff --git a/contracts/raffle/FlowtyRaffles.cdc b/contracts/raffle/FlowtyRaffles.cdc index 56e7b0f..524ca82 100644 --- a/contracts/raffle/FlowtyRaffles.cdc +++ b/contracts/raffle/FlowtyRaffles.cdc @@ -1,19 +1,23 @@ import "MetadataViews" -// import "RandomBeaconHistory" +import "RandomBeaconHistory" // import "Xorshift128plus" -pub contract FlowtyRaffles { - pub let ManagerStoragePath: StoragePath - pub let ManagerPublicPath: PublicPath +access(all) contract FlowtyRaffles { + access(all) let ManagerStoragePath: StoragePath + access(all) let ManagerPublicPath: PublicPath - pub event ManagerCreated(uuid: UInt64) - pub event RaffleCreated(address: Address?, raffleID: UInt64) - pub event RaffleReceiptCommitted(address: Address?, raffleID: UInt64, receiptID: UInt64, commitBlock: UInt64) - pub event RaffleReceiptRevealed(address: Address?, raffleID: UInt64, receiptID: UInt64, commitBlock: UInt64, revealHeight: UInt64, sourceType: Type, index: Int, value: String?, valueType: Type) + access(all) event ManagerCreated(uuid: UInt64) + access(all) event RaffleCreated(address: Address?, raffleID: UInt64) + access(all) event RaffleReceiptCommitted(address: Address?, raffleID: UInt64, receiptID: UInt64, commitBlock: UInt64) + access(all) event RaffleReceiptRevealed(address: Address?, raffleID: UInt64, receiptID: UInt64, commitBlock: UInt64, revealHeight: UInt64, sourceType: Type, index: Int, value: String?, valueType: Type) - pub struct DrawingResult { - pub let index: Int - pub let value: AnyStruct + access(all) entitlement Add + access(all) entitlement Reveal + access(all) entitlement Manage + + access(all) struct DrawingResult { + access(all) let index: Int + access(all) let value: AnyStruct init(_ index: Int, _ value: AnyStruct) { self.index = index @@ -34,62 +38,62 @@ pub contract FlowtyRaffles { there is not way for the raffle itself to know if a source is acting in good faith. Make sure you choose a raffle source implementation with care, as choosing one from an unknown party could result in unfair outcomes. */ - pub resource interface RaffleSourcePublic { + access(all) resource interface RaffleSourcePublic { /* getEntries - return all entries in this raffle. NOTE: This will not work if a raffle is so large that returning all its entries will exceed computation limits */ - pub fun getEntries(): [AnyStruct] + access(all) fun getEntries(): [AnyStruct] // getEntryCount - return the total number of entries in this raffle source - pub fun getEntryCount(): Int + access(all) fun getEntryCount(): Int // getEntryAt - return the entry at a specific index of a raffle source - pub fun getEntryAt(index: Int): AnyStruct + access(all) fun getEntryAt(index: Int): AnyStruct } - pub resource interface RaffleSourcePrivate { + access(all) resource interface RaffleSourcePrivate { /* revealCallback - a callback used when a raffle is revealed. This could be used to do things like remove an entry once it has been picked. As with similar notes above in the RaffleSourcePublic interface, make sure you trust the implementation of this callback so that it does not introduce unforeseen risk to your raffle */ - pub fun revealCallback(drawingResult: DrawingResult) + access(contract) fun revealCallback(drawingResult: DrawingResult) /* addEntries - adds an array of values into the raffle source, if it is permitted. NOTE: Some raffle source implementations might not permit this. As with other parts of this raffles implementation, be mindful of the source you are using and what it does */ - pub fun addEntries(_ v: [AnyStruct]) + access(Add) fun addEntries(_ v: [AnyStruct]) /* addEntry - adds value into the raffle source, if it is permitted. NOTE: Some raffle source implementations might not permit this. As with other parts of this raffles implementation, be mindful of the source you are using and what it does */ - pub fun addEntry(_ v: AnyStruct) + access(Add) fun addEntry(_ v: AnyStruct) } - pub resource interface RafflePublic { - pub fun borrowSourcePublic(): &{RaffleSourcePublic}? - pub fun getDetails(): Details + access(all) resource interface RafflePublic { + access(all) fun borrowSourcePublic(): &{RaffleSourcePublic}? + access(all) fun getDetails(): Details } - pub resource Receipt { + access(all) resource Receipt { // the block at which this receipt is allowed to be revealed - pub let commitBlock: UInt64 + access(all) let commitBlock: UInt64 // we record the uuid of the source used so that it cannot be swapped out // between stages - pub let sourceUuid: UInt64 + access(all) let sourceUuid: UInt64 // the block this receipt was revealed on - pub var revealBlock: UInt64? + access(all) var revealBlock: UInt64? // the result of this receipt once it has been reveald - pub var result: DrawingResult? + access(all) var result: DrawingResult? /* reveal - reveals the result of this receipt. A receipt can only be revealed if the current block is @@ -123,12 +127,12 @@ pub contract FlowtyRaffles { } } - pub struct Details { - pub let start: UInt64? - pub let end: UInt64? - pub let display: MetadataViews.Display? - pub let externalURL: MetadataViews.ExternalURL? - pub let commitBlocksAhead: UInt64 + access(all) struct Details { + access(all) let start: UInt64? + access(all) let end: UInt64? + access(all) let display: MetadataViews.Display? + access(all) let externalURL: MetadataViews.ExternalURL? + access(all) let commitBlocksAhead: UInt64 init(start: UInt64?, end: UInt64?, display: MetadataViews.Display?, externalURL: MetadataViews.ExternalURL?, commitBlocksAhead: UInt64) { self.start = start @@ -139,25 +143,25 @@ pub contract FlowtyRaffles { } } - pub resource Raffle: RafflePublic { + access(all) resource Raffle: RafflePublic { // Basic details about this raffle - pub let details: Details + access(all) let details: Details // a set of addresses which are allowed to perform reveals on a raffle. // set this to nil to allow anyone to reveal a drawing - pub var revealers: {Address: Bool}? + access(all) var revealers: {Address: Bool}? // The source of entries for this raffle. This allows a raffle to delegate out // what is being drawn. Some raffles might be for Addresses, others might be for // UInt64s or Strings - pub let source: @{RaffleSourcePublic, RaffleSourcePrivate} + access(contract) let source: @{RaffleSourcePublic, RaffleSourcePrivate} // Used to track all drawings done from this raffle. When a receipt is made, // it has no result until revealed, and can only be revealed if the current block is // equal to or greater than the block a receipt was made on + details.commitBlocksAhead - pub let receipts: @{UInt64: Receipt} + access(all) let receipts: @{UInt64: Receipt} - pub fun borrowSourcePublic(): &{RaffleSourcePublic}? { + access(all) fun borrowSourcePublic(): &{RaffleSourcePublic}? { return &self.source as &{RaffleSourcePublic, RaffleSourcePrivate} } @@ -190,15 +194,25 @@ pub contract FlowtyRaffles { return res } - pub fun addEntries(_ v: [AnyStruct]) { + access(Add) fun addEntries(_ v: [AnyStruct]) { + let blockTs = UInt64(getCurrentBlock().timestamp) + + assert(self.details.start == nil || self.details.start! <= blockTs, message: "cannot add entries to a raffle that has not started") + assert(self.details.end == nil || self.details.end! > blockTs, message: "cannot add entries to a raffle that has ended") + self.source.addEntries(v) } - pub fun addEntry(_ v: AnyStruct) { + access(Add) fun addEntry(_ v: AnyStruct) { + let blockTs = UInt64(getCurrentBlock().timestamp) + + assert(self.details.start == nil || self.details.start! <= blockTs, message: "cannot add entries to a raffle that has not started") + assert(self.details.end == nil || self.details.end! > blockTs, message: "cannot add entries to a raffle that has ended") + self.source.addEntry(v) } - pub fun getDetails(): Details { + access(all) fun getDetails(): Details { return self.details } @@ -224,11 +238,6 @@ pub contract FlowtyRaffles { } } } - - destroy () { - destroy self.source - destroy self.receipts - } } /* @@ -239,9 +248,9 @@ pub contract FlowtyRaffles { In addition to getting a raffle, the manager public also provides a way to reveal a raffle's outcome which is available for anyone to do based on the commit-reveal scheme underneath the raffle's management itself. */ - pub resource interface ManagerPublic { - pub fun borrowRafflePublic(id: UInt64): &{RafflePublic}? - pub fun revealDrawing(manager: &Manager{ManagerPublic}, raffleID: UInt64, receiptID: UInt64) + access(all) resource interface ManagerPublic { + access(all) fun borrowRafflePublic(id: UInt64): &{RafflePublic}? + access(all) fun revealDrawing(manager: &Manager, raffleID: UInt64, receiptID: UInt64) access(contract) fun _revealDrawing(raffleID: UInt64, receiptID: UInt64, drawer: &Manager) } @@ -250,28 +259,24 @@ pub contract FlowtyRaffles { This is made into its own interface so that a manager can be delegated out to others in the event that community run raffles are desired. One could make a capability to the private manager and share it with others they trust. */ - pub resource interface ManagerPrivate { - pub fun borrowRaffle(id: UInt64): &Raffle? - pub fun commitDrawing(raffleID: UInt64): UInt64 + access(all) resource interface ManagerPrivate { + access(Manage) fun borrowRaffle(id: UInt64): auth(Add) &Raffle? + access(Manage) fun commitDrawing(raffleID: UInt64): UInt64 } // This is an empty interface to give reveal requests the ability to vet whether or not // the calling manager is permitted to perform a reveal on a given drawing or not - pub resource interface ManagerIdentity {} + access(all) resource interface ManagerIdentity {} - pub resource Manager: ManagerPublic, ManagerPrivate { - pub let raffles: @{UInt64: Raffle} + access(all) resource Manager: ManagerPublic, ManagerPrivate { + access(self) let raffles: @{UInt64: Raffle} - pub fun borrowRafflePublic(id: UInt64): &{RafflePublic}? { + access(all) fun borrowRafflePublic(id: UInt64): &{RafflePublic}? { return self.borrowRaffle(id: id) } - pub fun borrowRaffle(id: UInt64): &Raffle? { - if self.raffles[id] == nil { - return nil - } - - return &self.raffles[id] as &Raffle? + access(Manage) fun borrowRaffle(id: UInt64): auth(Add) &Raffle? { + return &self.raffles[id] } /* @@ -281,7 +286,7 @@ pub contract FlowtyRaffles { 3. how many blocks ahead a result must be to be revealed. Setting commitBlocksAhead to 0 means a reveal can be done in the same block as the commit, and is subject to reversion risks discussed here: https://developers.flow.com/build/advanced-concepts/randomness#guidelines-for-safe-usage */ - pub fun createRaffle(source: @{RaffleSourcePublic, RaffleSourcePrivate}, details: Details, revealers: [Address]?): UInt64 { + access(Manage) fun createRaffle(source: @{RaffleSourcePublic, RaffleSourcePrivate}, details: Details, revealers: [Address]?): UInt64 { let raffle <- create Raffle(source: <-source, details: details, revealers: revealers) let uuid = raffle.uuid emit RaffleCreated(address: self.owner?.address, raffleID: uuid) @@ -294,14 +299,12 @@ pub contract FlowtyRaffles { commitDrawing - commits a new drawing for a raffle, creating a receipt resource with the specified commit block height to be revealed at a later date. */ - pub fun commitDrawing(raffleID: UInt64): UInt64 { + access(Manage) fun commitDrawing(raffleID: UInt64): UInt64 { let raffle = self.borrowRaffle(id: raffleID) ?? panic("raffle not found") let currentBlock = getCurrentBlock() let blockTs = UInt64(currentBlock.timestamp) - assert(raffle.details.start == nil || raffle.details.start! <= blockTs, message: "drawings for this raffle have not started yet") - assert(raffle.details.end == nil || raffle.details.end! > blockTs, message: "drawings for this raffle have ended") let commitBlock = raffle.details.commitBlocksAhead + currentBlock.height let receiptID = raffle.commitDrawing(commitBlock: commitBlock) @@ -314,7 +317,7 @@ pub contract FlowtyRaffles { revealDrawing - reveals the result of a drawing, taking the committed data in the commit stage and using it to generate a random number to draw an entry from our raffle source */ - pub fun revealDrawing(manager: &Manager{ManagerPublic}, raffleID: UInt64, receiptID: UInt64) { + access(all) fun revealDrawing(manager: &Manager, raffleID: UInt64, receiptID: UInt64) { let ref = &self as &Manager manager._revealDrawing(raffleID: raffleID, receiptID: receiptID, drawer: ref) } @@ -325,7 +328,7 @@ pub contract FlowtyRaffles { assert(raffle.revealers == nil || raffle.revealers![drawer.owner!.address] == true, message: "drawer is not permitted to perform reveals on this raffle") let drawingResult = raffle.revealDrawing(id: receiptID) - let receipt = (&raffle.receipts[receiptID] as &Receipt?)! + let receipt = (raffle.receipts[receiptID])! var v = FlowtyRaffles.extractString(drawingResult.value) emit RaffleReceiptRevealed(address: self.owner?.address, raffleID: raffleID, receiptID: receiptID, commitBlock: receipt.commitBlock, revealHeight: getCurrentBlock().height, sourceType: raffle.source.getType(), index: drawingResult.index, value: v, valueType: drawingResult.value.getType()) @@ -334,15 +337,11 @@ pub contract FlowtyRaffles { init() { self.raffles <- {} } - - destroy () { - destroy self.raffles - } } // taken from // https://github.com/onflow/random-coin-toss/blob/4271cd571b7761af36b0f1037767171aeca18387/contracts/CoinToss.cdc#L95 - pub fun randUInt64(atBlockHeight: UInt64, salt: UInt64): UInt64 { + access(all) fun randUInt64(atBlockHeight: UInt64, salt: UInt64): UInt64 { // // query the Random Beacon history core-contract - if `blockHeight` <= current block height, panic & revert // let sourceOfRandomness = RandomBeaconHistory.sourceOfRandomness(atBlockHeight: atBlockHeight) // assert(sourceOfRandomness.blockHeight == atBlockHeight, message: "RandomSource block height mismatch") @@ -356,10 +355,10 @@ pub contract FlowtyRaffles { // return prg.nextUInt64() // TODO: use commented-out implementation once we can test using the randomness beacon in the cadence testing framework - return revertibleRandom() + return revertibleRandom() } - pub fun extractString(_ value: AnyStruct?): String? { + access(all) fun extractString(_ value: AnyStruct?): String? { if value == nil { return nil } @@ -384,7 +383,7 @@ pub contract FlowtyRaffles { return nil } - pub fun createManager(): @Manager { + access(all) fun createManager(): @Manager { return <- create Manager() } diff --git a/contracts/raffle/RandomBeaconHistory.cdc b/contracts/raffle/RandomBeaconHistory.cdc new file mode 100644 index 0000000..0458526 --- /dev/null +++ b/contracts/raffle/RandomBeaconHistory.cdc @@ -0,0 +1,344 @@ +/// RandomBeaconHistory (FLIP 123) +/// +/// This contract stores the history of random sources generated by the Flow network. The defined Heartbeat resource is +/// updated by the Flow Service Account at the end of every block with that block's source of randomness. +/// +/// While the source values are safely generated by the Random Beacon (non-predictable, unbiasable, verifiable) and +/// transmitted into the execution environment via the committing transaction, using the raw values from this contract +/// does not guarantee non-revertible randomness. The Hearbeat is intended to be used in conjunction with a commit-reveal +/// mechanism to provide an onchain source of non-revertible randomness. +/// It is also recommended to use the source values with a pseudo-random number +/// generator (PRNG) to generate an arbitrary-long sequence of random values. +/// +/// For usage of randomness where result abortion is not an issue, it is recommended +/// to use the Cadence built-in function `revertibleRandom`, which is also based on +/// the safe Random Beacon. +/// +/// Read the full FLIP here: https://github.com/onflow/flips/pull/123 +/// +access(all) contract RandomBeaconHistory { + + /// The height at which the first source of randomness was recorded + access(contract) var lowestHeight: UInt64? + /// Sequence of random sources recorded by the Heartbeat, stored as an array over a mapping to reduce storage + access(contract) let randomSourceHistory: [[UInt8]] + + /// The path of the Heartbeat resource in the deployment account + access(all) let HeartbeatStoragePath: StoragePath + + // Event emitted when missing SoRs from past heartbeats are detected and will be backfilled: + // - `blockHeight` is the height where the gap is detected + // - `gapStartHeight` is the height of the first missing entry detected + access(all) event RandomHistoryMissing(blockHeight: UInt64, gapStartHeight: UInt64) + + // Event emitted when missing SoRs are backfilled on the current heartbeat: + // - `blockHeight` is the height where the backfill happened, it also defines the SoR used to backfill + // - `gapStartHeight` is the height of the first backfilled entry + // - `count` is the number of backfilled entries + // Note that in very rare cases, the backfilled gap may not be contiguous. This event does not + // fully define the backfilled entries in this case. + access(all) event RandomHistoryBackfilled(blockHeight: UInt64, gapStartHeight: UInt64, count: UInt64) + + /* --- Hearbeat --- */ + // + /// The Heartbeat resource containing each block's source of randomness in sequence + /// + access(all) resource Heartbeat { + + /// Callable by owner of the Heartbeat resource, Flow Service Account, records the provided random source + /// + /// @param randomSourceHistory The random source to record + /// + /// The Flow protocol makes sure to call this function once per block as a system call. The transaction + /// comes at the end of each block so that the current block's entry becomes available only in the child + /// block. + /// + access(all) fun heartbeat(randomSourceHistory: [UInt8]) { + + assert( + // random source must be at least 128 bits + randomSourceHistory.length >= 128 / 8, + message: "Random source must be at least 128 bits" + ) + + let currentBlockHeight = getCurrentBlock().height + // init lowestBlockHeight if it is not set yet + if RandomBeaconHistory.lowestHeight == nil { + RandomBeaconHistory.lowestHeight = currentBlockHeight + } + var maybeBackfiller = RandomBeaconHistory.borrowBackfiller() + // Create & save Backfiller if it is not saved yet + if maybeBackfiller == nil { + RandomBeaconHistory.account.storage.save(<-create Backfiller(), to: /storage/randomBeaconHistoryBackfiller) + maybeBackfiller = RandomBeaconHistory.borrowBackfiller() + } + let backfiller = maybeBackfiller ?? panic("Problem borrowing backfiller") + + // check for any existing gap and backfill using the input random source if needed. + backfiller.backfill(randomSource: randomSourceHistory) + + // we are now at the correct index to record the source of randomness + // created by the protocol for the current block + RandomBeaconHistory.randomSourceHistory.append(randomSourceHistory) + } + } + + /* --- Backfiller --- */ + // + /// A recovery mechanism designed to backfill missed sources of randomness in the event of a missed commitment due + /// to a system transaction failure. + /// + access(all) resource Backfiller { + /// Start index of the first gap in the `randomSourceHistory` array where random sources were not recorded, + /// because of a heartbeat failure. + /// There may be non contiguous gaps in the history, `gapStartIndex` is the start index of the lowest-height + /// gap. + /// If no gaps exist, `gapStartIndex` is equal to the `randomSourceHistory` array length. + access(contract) var gapStartIndex: UInt64 + /// BackFilling is limited to a maximum number of entries per call to limit the computation cost. + /// This means a large gap may need a few calls to get fully backfilled. + access(contract) var maxEntriesPerCall: UInt64 + + init() { + self.gapStartIndex = UInt64(RandomBeaconHistory.randomSourceHistory.length) + self.maxEntriesPerCall = 100 + } + + access(all) view fun getMaxEntriesPerCall() : UInt64 { + return self.maxEntriesPerCall + } + + access(all) fun setMaxEntriesPerCall(max: UInt64) { + assert( + max > 0, + message: "the maximum entry per call must be strictly positive" + ) + self.maxEntriesPerCall = max + } + + /// Finds the correct index to fill with the new random source. If a gap is detected, emits the + /// RandomHistoryMissing event. + /// + access(contract) view fun findGapAndReturnCorrectIndex(): UInt64 { + + let currentBlockHeight = getCurrentBlock().height + // correct index to fill with the new random source + // so that eventually randomSourceHistory[correctIndex] = inputRandom + let lowestHeight = RandomBeaconHistory.lowestHeight! + let correctIndex = currentBlockHeight - lowestHeight + + // if a new gap is detected, emit an event + var arrayLength = UInt64(RandomBeaconHistory.randomSourceHistory.length) + if correctIndex > arrayLength { + let gapStartHeight = lowestHeight + arrayLength + emit RandomHistoryMissing(blockHeight: currentBlockHeight, gapStartHeight: gapStartHeight) + } + return correctIndex + } + + /// Backfills possible empty entries (gaps) in the history array starting from the stored `gapStartIndex`, + /// using `randomSource` as a seed for all entries. + /// If there are no gaps, `gapStartIndex` is just updated to `RandomBeaconHistory`'s length. + // + /// When backfilling, all entries use the same entropy. Each entry is extracted from `randomSource` using + /// successive hashing. This makes sure the entries are all distinct although they provide + /// the same entropy. + // + /// gaps only occur in the rare event of a system transaction failure. In this case, entries are still + /// filled using a source not known at the time of block execution, which guarantees unpredicatability. + access(contract) fun backfill(randomSource: [UInt8]) { + + let correctIndex = self.findGapAndReturnCorrectIndex() + var arrayLength = UInt64(RandomBeaconHistory.randomSourceHistory.length) + // optional optimization for the happy common path: if no gaps are detected, + // backfilling isn't needed, return early + if correctIndex == self.gapStartIndex { + self.gapStartIndex = arrayLength + 1 + return + } + + // If a new gap is detected in the current transaction, fill the gap with empty entries. + // This happens in the rare case where a new gap occurs because of a system transaction failure. + while correctIndex > UInt64(RandomBeaconHistory.randomSourceHistory.length) { + RandomBeaconHistory.randomSourceHistory.append([]) + } + + arrayLength = UInt64(RandomBeaconHistory.randomSourceHistory.length) + var newEntry = randomSource + var index = self.gapStartIndex + var count = 0 as UInt64 + while count < self.maxEntriesPerCall { + // move to the next empty entry + while index < arrayLength && RandomBeaconHistory.randomSourceHistory[index].length > 0 { + index = index + 1 + } + // if we reach the end of the array then all existing gaps got filled + if index == arrayLength { + break + } + // back fill the empty entry + // It is guaranteed that sha3 output (256 bits) is larger than the minimum + // required size of an SoR (128 bits) + newEntry = HashAlgorithm.SHA3_256.hash(newEntry) + RandomBeaconHistory.randomSourceHistory[index] = newEntry + index = index + 1 + count = count + 1 + } + + // emit an event about backfilled entries + if count > 0 { + let gapStartHeight = RandomBeaconHistory.lowestHeight! + self.gapStartIndex + emit RandomHistoryBackfilled( + blockHeight: getCurrentBlock().height, + gapStartHeight: gapStartHeight, + count: count + ) + } + + // no more backfilling is possible but we need to update `gapStartIndex` + // to: + // - the next empty index if gaps still exist + // - the length of the array at the end of the transaction if there are no gaps + while index < arrayLength && RandomBeaconHistory.randomSourceHistory[index].length > 0 { + index = index + 1 + } + if index == arrayLength { + index = index + 1 // take into account the upcoming append of the SoR at the correct index + } + self.gapStartIndex = index + } + } + + /* --- RandomSourceHistory --- */ + // + /// Represents a random source value for a given block height + /// + access(all) struct RandomSource { + access(all) let blockHeight: UInt64 + access(all) let value: [UInt8] + + init(blockHeight: UInt64, value: [UInt8]) { + self.blockHeight = blockHeight + self.value = value + } + } + + /* --- RandomSourceHistoryPage --- */ + // + /// Contains RandomSource values ordered chronologically according to associated block height + /// + access(all) struct RandomSourceHistoryPage { + access(all) let page: UInt64 + access(all) let perPage: UInt64 + access(all) let totalLength: UInt64 + access(all) let values: [RandomSource] + + view init(page: UInt64, perPage: UInt64, totalLength: UInt64, values: [RandomSource]) { + self.page = page + self.perPage = perPage + self.totalLength = totalLength + self.values = values + } + } + + /* --- Contract Methods --- */ + // + /// Getter for the source of randomness at a given block height. Panics if the requested block height either + /// precedes or exceeds the recorded history. Note that a source of randomness for block n will not be accessible + /// until block n+1. + /// + /// @param atBlockHeight The block height at which to retrieve the source of randomness + /// + /// @return The source of randomness at the given block height as RandomSource struct + /// + access(all) fun sourceOfRandomness(atBlockHeight blockHeight: UInt64): RandomSource { + pre { + self.lowestHeight != nil: "History has not yet been initialized" + blockHeight >= self.lowestHeight!: "Requested block height precedes recorded history" + blockHeight < getCurrentBlock().height: "Source of randomness not yet recorded" + } + let index = blockHeight - self.lowestHeight! + assert( + index >= 0, + message: "Problem finding random source history index" + ) + assert( + index < UInt64(self.randomSourceHistory.length) && self.randomSourceHistory[index].length > 0, + message: "Source of randomness is currently not available but will be available soon" + ) + return RandomSource(blockHeight: blockHeight, value: self.randomSourceHistory[index]) + } + + /// Retrieves a page from the history of random sources recorded so far, ordered chronologically + /// + /// @param page: The page number to retrieve, 0-indexed + /// @param perPage: The number of random sources to include per page + /// + /// @return A RandomSourceHistoryPage containing RandomSource values in choronological order according to + /// associated block height + /// + access(all) fun getRandomSourceHistoryPage(_ page: UInt64, perPage: UInt64): RandomSourceHistoryPage { + pre { + self.lowestHeight != nil: "History has not yet been initialized" + } + let values: [RandomSource] = [] + let totalLength = UInt64(self.randomSourceHistory.length) + + var startIndex = page * perPage + if startIndex > totalLength { + startIndex = totalLength + } + var endIndex = startIndex + perPage + if endIndex > totalLength { + endIndex = totalLength + } + + // Return empty page if request exceeds last page + if startIndex == endIndex { + return RandomSourceHistoryPage(page: page, perPage: perPage, totalLength: totalLength, values: values) + } + + // Iterate over history and construct page RandomSource values + let lowestHeight = self.lowestHeight! + for i, value in self.randomSourceHistory.slice(from: Int(startIndex), upTo: Int(endIndex)) { + assert( + value.length > 0, + message: "Source of randomness is currently not available but will be available soon" + ) + values.append( + RandomSource( + blockHeight: lowestHeight + startIndex + UInt64(i), + value: value + ) + ) + } + + return RandomSourceHistoryPage( + page: page, + perPage: perPage, + totalLength: totalLength, + values: values + ) + } + + /// Getter for the block height at which the first source of randomness was recorded + /// + /// @return The block height at which the first source of randomness was recorded + /// + access(all) view fun getLowestHeight(): UInt64 { + return self.lowestHeight ?? panic("History has not yet been initialized") + } + + /// Getter for the contract's Backfiller resource + access(contract) fun borrowBackfiller(): &Backfiller? { + return self.account.storage.borrow<&Backfiller>(from: /storage/randomBeaconHistoryBackfiller) + } + + init() { + self.lowestHeight = nil + self.randomSourceHistory = [] + self.HeartbeatStoragePath = /storage/FlowRandomBeaconHistoryHeartbeat + + self.account.storage.save(<-create Heartbeat(), to: self.HeartbeatStoragePath) + } +} \ No newline at end of file diff --git a/flow.json b/flow.json index f8c792f..3d4e1f2 100644 --- a/flow.json +++ b/flow.json @@ -36,6 +36,15 @@ "mainnet": "0x2fb4614ede95ab2b" } }, + "RandomBeaconHistory": { + "source": "./contracts/raffle/RandomBeaconHistory.cdc", + "aliases": { + "emulator": "0xf8d6e0586b0a20c7", + "mainnet": "0xe467b9dd11fa00df", + "testnet": "0x8c5303eaa26202d6", + "testing": "0x0000000000000001" + } + }, "FungibleToken": { "source": "./node_modules/@flowtyio/flow-contracts/contracts/FungibleToken.cdc", "aliases": { @@ -85,6 +94,14 @@ "mainnet": "0xa340dc0a4ec828ab", "testing": "0x0000000000000006" } + }, + "Burner": { + "source": "./node_modules/@flowtyio/flow-contracts/contracts/Burner.cdc", + "aliases": { + "emulator": "0xf8d6e0586b0a20c7", + "testnet": "0x9a0766d93b6608b7", + "mainnet": "0xf233dcee88fe0abe" + } } }, "networks": { @@ -130,7 +147,9 @@ "FlowtyRaffles", "FlowtyRaffleSource", "ArrayUtils", - "StringUtils" + "StringUtils", + "Burner", + "RandomBeaconHistory" ], "emulator-ft": [ "FungibleToken" diff --git a/package-lock.json b/package-lock.json index cbfd049..03b386b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,13 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@flowtyio/flow-contracts": "^0.0.18" + "@flowtyio/flow-contracts": "^0.1.0-beta.25" } }, "node_modules/@flowtyio/flow-contracts": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/@flowtyio/flow-contracts/-/flow-contracts-0.0.18.tgz", - "integrity": "sha512-uQVbUOZegx8sdk/T+ypYBjdVmcw7AGHANzrg+PdQCtYXBGJ3iEh9+vxwf2kNk2j6hw8djmWdZy36iUMZC3pwmg==", + "version": "0.1.0-beta.25", + "resolved": "https://registry.npmjs.org/@flowtyio/flow-contracts/-/flow-contracts-0.1.0-beta.25.tgz", + "integrity": "sha512-573DTqFgNjN4P3vPXCiVf98y9gta5/5HxMetnenlRU/nz85QOJdMgb5JMvBvsYsCIvRQd2WKYerDgdmSEMxzpQ==", "dependencies": { "commander": "^11.0.0" }, @@ -34,9 +34,9 @@ }, "dependencies": { "@flowtyio/flow-contracts": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/@flowtyio/flow-contracts/-/flow-contracts-0.0.18.tgz", - "integrity": "sha512-uQVbUOZegx8sdk/T+ypYBjdVmcw7AGHANzrg+PdQCtYXBGJ3iEh9+vxwf2kNk2j6hw8djmWdZy36iUMZC3pwmg==", + "version": "0.1.0-beta.25", + "resolved": "https://registry.npmjs.org/@flowtyio/flow-contracts/-/flow-contracts-0.1.0-beta.25.tgz", + "integrity": "sha512-573DTqFgNjN4P3vPXCiVf98y9gta5/5HxMetnenlRU/nz85QOJdMgb5JMvBvsYsCIvRQd2WKYerDgdmSEMxzpQ==", "requires": { "commander": "^11.0.0" } diff --git a/package.json b/package.json index a92e2aa..477ce92 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,6 @@ }, "homepage": "https://github.com/Flowtyio/flowty-wrapped#readme", "dependencies": { - "@flowtyio/flow-contracts": "^0.0.18" + "@flowtyio/flow-contracts": "^0.1.0-beta.25" } } diff --git a/run-tests.sh b/run-tests.sh index acc7741..03faee0 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -2,4 +2,4 @@ set -e -flow test --cover --covercode="contracts" --coverprofile="coverage.lcov" test/*_tests.cdc \ No newline at end of file +flow-c1 test --cover --covercode="contracts" --coverprofile="coverage.lcov" test/*_tests.cdc \ No newline at end of file diff --git a/scripts/borrow_nft.cdc b/scripts/borrow_nft.cdc index d20fd9d..d6c3474 100644 --- a/scripts/borrow_nft.cdc +++ b/scripts/borrow_nft.cdc @@ -3,15 +3,10 @@ import "MetadataViews" import "FlowtyWrapped" -pub fun main(addr: Address, nftID: UInt64): Bool{ - let cp = getAccount(addr).getCapability<&{NonFungibleToken.CollectionPublic}>(FlowtyWrapped.CollectionPublicPath).borrow() +access(all) fun main(addr: Address, nftID: UInt64): Bool{ + let cp = getAccount(addr).capabilities.get<&{NonFungibleToken.CollectionPublic}>(FlowtyWrapped.CollectionPublicPath).borrow() ?? panic("collection not found") - let nft = cp.borrowNFT(id: nftID) - - if (nft != nil) { - return true - } else { - return false - } + let nft = cp.borrowNFT(nftID) + return nft != nil } \ No newline at end of file diff --git a/scripts/get_collection_external_url.cdc b/scripts/get_collection_external_url.cdc index defa7c6..4ff3b0a 100644 --- a/scripts/get_collection_external_url.cdc +++ b/scripts/get_collection_external_url.cdc @@ -1,5 +1,5 @@ import "FlowtyWrapped" -pub fun main(): String { +access(all) fun main(): String { return FlowtyWrapped.collectionExternalUrl } \ No newline at end of file diff --git a/scripts/get_editions_flowty_wrapped.cdc b/scripts/get_editions_flowty_wrapped.cdc index b9d83ee..082bbe8 100644 --- a/scripts/get_editions_flowty_wrapped.cdc +++ b/scripts/get_editions_flowty_wrapped.cdc @@ -3,10 +3,10 @@ import "MetadataViews" import "FlowtyWrapped" -pub fun main(addr: Address, nftID: UInt64): AnyStruct { - let cp = getAccount(addr).getCapability<&{NonFungibleToken.CollectionPublic}>(FlowtyWrapped.CollectionPublicPath).borrow() +access(all) fun main(addr: Address, nftID: UInt64): AnyStruct { + let cp = getAccount(addr).capabilities.get<&{NonFungibleToken.CollectionPublic}>(FlowtyWrapped.CollectionPublicPath).borrow() ?? panic("collection not found") - let nft = cp.borrowNFT(id: nftID) + let nft = cp.borrowNFT(nftID)! return nft.resolveView(Type())! } \ No newline at end of file diff --git a/scripts/get_medias.cdc b/scripts/get_medias.cdc index 30fb576..5e0bd3e 100644 --- a/scripts/get_medias.cdc +++ b/scripts/get_medias.cdc @@ -2,10 +2,10 @@ import "FlowtyWrapped" import "MetadataViews" import "NonFungibleToken" -pub fun main(addr: Address, nftID: UInt64): MetadataViews.Medias { - let acct = getAuthAccount(addr) - let col = acct.borrow<&FlowtyWrapped.Collection>(from: FlowtyWrapped.CollectionStoragePath) +access(all) fun main(addr: Address, nftID: UInt64): MetadataViews.Medias { + let acct = getAuthAccount (addr) + let col = acct.storage.borrow<&FlowtyWrapped.Collection>(from: FlowtyWrapped.CollectionStoragePath) ?? panic("collection not found") - let nft = col.borrowFlowtyWrapped(id: nftID) ?? panic("nft not found") + let nft = col.borrowFlowtyWrapped(nftID) ?? panic("nft not found") return nft.resolveView(Type())! as! MetadataViews.Medias } \ No newline at end of file diff --git a/scripts/get_nft_ids.cdc b/scripts/get_nft_ids.cdc index bb59168..2d82093 100644 --- a/scripts/get_nft_ids.cdc +++ b/scripts/get_nft_ids.cdc @@ -2,8 +2,8 @@ import "NonFungibleToken" import "FlowtyWrapped" -pub fun main(addr: Address): [UInt64] { - let cp = getAccount(addr).getCapability<&{NonFungibleToken.CollectionPublic}>(FlowtyWrapped.CollectionPublicPath).borrow() +access(all) fun main(addr: Address): [UInt64] { + let cp = getAccount(addr).capabilities.get<&{NonFungibleToken.CollectionPublic}>(FlowtyWrapped.CollectionPublicPath).borrow() ?? panic("collection not found") let nftIDs = cp.getIDs() diff --git a/scripts/get_total_edition_supply.cdc b/scripts/get_total_edition_supply.cdc index 699fae5..62a0421 100644 --- a/scripts/get_total_edition_supply.cdc +++ b/scripts/get_total_edition_supply.cdc @@ -1,13 +1,12 @@ import "FlowtyWrapped" import "WrappedEditions" -pub fun main(addr: Address, editionName: String): UInt64 { - - let acct = getAuthAccount(addr) - let admin = acct.borrow<&FlowtyWrapped.Admin>(from: FlowtyWrapped.AdminStoragePath)! +access(all) fun main(addr: Address, editionName: String): UInt64 { + let acct = getAuthAccount(addr) + let admin = acct.storage.borrow(from: FlowtyWrapped.AdminStoragePath)! - let edition = admin.getEdition(editionName: editionName) as! &AnyStruct{FlowtyWrapped.WrappedEdition} + let edition = admin.getEdition(editionName: editionName) as! &{FlowtyWrapped.WrappedEdition} let editionSupply = edition.getEditionSupply() diff --git a/scripts/raffle/borrow_raffle_manager.cdc b/scripts/raffle/borrow_raffle_manager.cdc index 43b013a..312fdaf 100644 --- a/scripts/raffle/borrow_raffle_manager.cdc +++ b/scripts/raffle/borrow_raffle_manager.cdc @@ -1,6 +1,6 @@ import "FlowtyRaffles" -pub fun main(addr: Address) { - getAccount(addr).getCapability<&FlowtyRaffles.Manager{FlowtyRaffles.ManagerPublic}>(FlowtyRaffles.ManagerPublicPath).borrow() +access(all) fun main(addr: Address) { + getAccount(addr).capabilities.get<&FlowtyRaffles.Manager>(FlowtyRaffles.ManagerPublicPath).borrow() ?? panic("unable to borrow manager") } \ No newline at end of file diff --git a/scripts/raffle/get_raffle_entries.cdc b/scripts/raffle/get_raffle_entries.cdc index d6cc58f..d32f707 100644 --- a/scripts/raffle/get_raffle_entries.cdc +++ b/scripts/raffle/get_raffle_entries.cdc @@ -1,11 +1,11 @@ import "FlowtyRaffles" -pub fun main(addr: Address, id: UInt64): [AnyStruct] { - let acct = getAuthAccount(addr) - let manager = acct.borrow<&FlowtyRaffles.Manager{FlowtyRaffles.ManagerPublic}>(from: FlowtyRaffles.ManagerStoragePath) +access(all) fun main(addr: Address, id: UInt64): [AnyStruct] { + let acct = getAuthAccount(addr) + let manager = acct.storage.borrow<&FlowtyRaffles.Manager>(from: FlowtyRaffles.ManagerStoragePath) ?? panic("raffles manager not found") let raffle = manager.borrowRafflePublic(id: id) ?? panic("raffle not found") - let source: &AnyResource{FlowtyRaffles.RaffleSourcePublic} = raffle.borrowSourcePublic() ?? panic("source is invalid") + let source: &{FlowtyRaffles.RaffleSourcePublic} = raffle.borrowSourcePublic() ?? panic("source is invalid") return source.getEntries() } \ No newline at end of file diff --git a/test/FlowtyWrapped_tests.cdc b/test/FlowtyWrapped_tests.cdc index 6ef6750..65af536 100644 --- a/test/FlowtyWrapped_tests.cdc +++ b/test/FlowtyWrapped_tests.cdc @@ -6,10 +6,10 @@ import "MetadataViews" import "WrappedEditions" import "FlowtyRaffles" -pub let rafflesAcct = Test.getAccount(Address(0x0000000000000007)) -pub let minterAccount = Test.getAccount(Address(0x0000000000000007)) +access(all) let rafflesAcct = Test.getAccount(Address(0x0000000000000007)) +access(all) let minterAccount = Test.getAccount(Address(0x0000000000000007)) -pub fun setup() { +access(all) fun setup() { var err = Test.deployContract(name: "FlowtyRaffles", path: "../contracts/raffle/FlowtyRaffles.cdc", arguments: []) Test.expect(err, Test.beNil()) @@ -38,16 +38,16 @@ pub fun setup() { } -pub fun testSetupManager() { +access(all) fun testSetupManager() { let acct = Test.createAccount() txExecutor("setup_flowty_wrapped.cdc", [acct], [], nil) } -pub fun testGetRaffleManager() { +access(all) fun testGetRaffleManager() { scriptExecutor("raffle/borrow_raffle_manager.cdc", [rafflesAcct.address]) } -pub fun testSetCollectionExternalUrl() { +access(all) fun testSetCollectionExternalUrl() { let baseHtmlUrl: String = "https://flowty.io/asset/0x0000000000000007/FlowtyWrappedTEST" txExecutor("set_collection_external_url.cdc", [rafflesAcct], [baseHtmlUrl], nil) @@ -57,13 +57,13 @@ pub fun testSetCollectionExternalUrl() { assert(castedResult == baseHtmlUrl, message: "baseHtmlUrl does not match expected") } -pub fun testMint() { +access(all) fun testMint() { let acct = Test.createAccount() let username: String = "user1" setupForMint(acct: acct, name: username) } -pub fun testGetEditions() { +access(all) fun testGetEditions() { let acct = Test.createAccount() let username: String = "user1" setupForMint(acct: acct, name: username) @@ -75,7 +75,7 @@ pub fun testGetEditions() { scriptExecutor("get_editions_flowty_wrapped.cdc", [acct.address, nftID1]) } -pub fun testEditionResolveView() { +access(all) fun testEditionResolveView() { let acct = Test.createAccount() let currentEditionNumber = getEditionNumber() @@ -105,7 +105,7 @@ pub fun testEditionResolveView() { assert(max == expectedEditionMax, message: "max should be nil") } -pub fun testDepositToWrongAddressFails() { +access(all) fun testDepositToWrongAddressFails() { let acct = Test.createAccount() let wrongAccount = Test.createAccount() @@ -127,7 +127,7 @@ pub fun testDepositToWrongAddressFails() { -pub fun testBorrowNFT() { +access(all) fun testBorrowNFT() { let acct = Test.createAccount() let username: String = "user1" setupForMint(acct: acct, name: username) @@ -140,7 +140,7 @@ pub fun testBorrowNFT() { scriptExecutor("borrow_nft.cdc", [acct.address, nftID1]) } -pub fun testSingleMint() { +access(all) fun testSingleMint() { let acct = Test.createAccount() txExecutor("setup_flowty_wrapped.cdc", [acct], [], nil) @@ -158,7 +158,7 @@ pub fun testSingleMint() { txExecutor("mint_flowty_wrapped.cdc", [minterAccount], [acct.address, username, ticket, totalNftsOwned, floatCount, favoriteCollections, collections], "address has already been minted") } -pub fun testWithdrawFails() { +access(all) fun testWithdrawFails() { let acct = Test.createAccount() let acct2 = Test.createAccount() let username: String = "user1" @@ -172,7 +172,7 @@ pub fun testWithdrawFails() { txExecutor("withdraw_nft.cdc", [acct], [acct.address, acct2.address, nftID1], "Flowty Wrapped is not transferrable") } -pub fun testMediasIpfsUrl() { +access(all) fun testMediasIpfsUrl() { let acct = Test.createAccount() let username: String = "user1" setupForMint(acct: acct, name: username) @@ -188,7 +188,7 @@ pub fun testMediasIpfsUrl() { assert(ipfsUrl == "ipfs://QmRfVR98oe6qxeWFcnY9tfM2CLUJg3rvxbBPS5LjYwp69Z?username=user1&raffleTickets=1", message: "unexpected ipfs url") } -pub fun testIpfsUrlNoName() { +access(all) fun testIpfsUrlNoName() { let acct = Test.createAccount() let username: String = "" setupForMint(acct: acct, name: username) @@ -204,7 +204,7 @@ pub fun testIpfsUrlNoName() { assert(ipfsUrl == "ipfs://QmRfVR98oe6qxeWFcnY9tfM2CLUJg3rvxbBPS5LjYwp69Z?username=".concat(acct.address.toString()).concat("&raffleTickets=1"), message: "unexpected ipfs url") } -pub fun testDrawRaffle() { +access(all) fun testDrawRaffle() { let acct = Test.createAccount() let username: String = "user1" @@ -231,24 +231,23 @@ pub fun testDrawRaffle() { assert(winnerIsFromEntryPool) } -pub fun registerEdition(rafflesAcct: Test.Account, removeAfterReveal: Bool, start: UInt64?, end: UInt64?, baseImageUrl: String, baseHtmlUrl: String) { +access(all) fun registerEdition(rafflesAcct: Test.TestAccount, removeAfterReveal: Bool, start: UInt64?, end: UInt64?, baseImageUrl: String, baseHtmlUrl: String) { txExecutor("register_edition.cdc", [rafflesAcct], [removeAfterReveal, start, end, baseImageUrl, baseHtmlUrl], nil) } -pub fun getMedias(addr: Address, nftID: UInt64): MetadataViews.Medias { +access(all) fun getMedias(addr: Address, nftID: UInt64): MetadataViews.Medias { return scriptExecutor("get_medias.cdc", [addr, nftID])! as! MetadataViews.Medias } -pub fun getEditionNumber(): UInt64{ +access(all) fun getEditionNumber(): UInt64{ let editionName = "Flowty Wrapped 2023" let res = scriptExecutor("get_total_edition_supply.cdc", [minterAccount.address, editionName]) let editionNumber = res! as! UInt64 return editionNumber - } -pub fun setupForMint(acct: Test.Account, name: String) { +access(all) fun setupForMint(acct: Test.TestAccount, name: String) { txExecutor("setup_flowty_wrapped.cdc", [acct], [], nil) @@ -261,7 +260,7 @@ pub fun setupForMint(acct: Test.Account, name: String) { txExecutor("mint_flowty_wrapped.cdc", [minterAccount], [acct.address, name, ticket, totalNftsOwned, floatCount, favoriteCollections, collections], nil) } -pub fun drawFromRaffle(_ signer: Test.Account, _ id: UInt64): String { +access(all) fun drawFromRaffle(_ signer: Test.TestAccount, _ id: UInt64): String { txExecutor("raffle/draw_from_raffle.cdc", [signer], [id], nil) let drawingEvent = Test.eventsOfType(Type()).removeLast() as! FlowtyRaffles.RaffleReceiptRevealed diff --git a/test/test_helpers.cdc b/test/test_helpers.cdc index 2936a80..3df8b64 100644 --- a/test/test_helpers.cdc +++ b/test/test_helpers.cdc @@ -5,11 +5,11 @@ import Test https://github.com/onflow/hybrid-custody/blob/main/test/test_helpers.cdc */ -pub fun loadCode(_ fileName: String, _ baseDirectory: String): String { +access(all) fun loadCode(_ fileName: String, _ baseDirectory: String): String { return Test.readFile("../".concat(baseDirectory).concat("/").concat(fileName)) } -pub fun scriptExecutor(_ scriptName: String, _ arguments: [AnyStruct]): AnyStruct? { +access(all) fun scriptExecutor(_ scriptName: String, _ arguments: [AnyStruct]): AnyStruct? { let scriptCode = loadCode(scriptName, "scripts") let scriptResult = Test.executeScript(scriptCode, arguments) @@ -22,9 +22,9 @@ pub fun scriptExecutor(_ scriptName: String, _ arguments: [AnyStruct]): AnyStruc return scriptResult.returnValue } -pub fun txExecutor( +access(all) fun txExecutor( _ txName: String, - _ signers: [Test.Account], + _ signers: [Test.TestAccount], _ arguments: [AnyStruct], _ expectedError: String? ): Bool { diff --git a/transactions/claim_admin.cdc b/transactions/claim_admin.cdc index 8f78696..f1efdeb 100644 --- a/transactions/claim_admin.cdc +++ b/transactions/claim_admin.cdc @@ -1,11 +1,11 @@ import "FlowtyWrapped" transaction(name: String, provider: Address) { - prepare(acct: AuthAccount) { - let providerAdmin = acct.inbox.claim<&FlowtyWrapped.Admin>(name, provider: provider) + prepare(acct: auth(Inbox, Storage) &Account) { + let providerAdmin = acct.inbox.claim(name, provider: provider) ?? panic("capabiltiy not found") let admin <- providerAdmin.borrow()!.createAdmin() - acct.save(<-admin, to: FlowtyWrapped.AdminStoragePath) + acct.storage.save(<-admin, to: FlowtyWrapped.AdminStoragePath) } } \ No newline at end of file diff --git a/transactions/destroy_admin.cdc b/transactions/destroy_admin.cdc index 19cb185..da1aab0 100644 --- a/transactions/destroy_admin.cdc +++ b/transactions/destroy_admin.cdc @@ -1,12 +1,12 @@ import "FlowtyWrapped" transaction { - prepare(acct: AuthAccount) { - let admin <- acct.load<@AnyResource>(from: FlowtyWrapped.AdminStoragePath) + prepare(acct: auth(Storage) &Account) { + let admin <- acct.storage.load<@AnyResource>(from: FlowtyWrapped.AdminStoragePath) destroy admin // borrow the contract admin resource to make sure we haven't destroyed the wrong admin - let publicAdmin = getAccount(FlowtyWrapped.getAccountAddress()).getCapability<&{FlowtyWrapped.AdminPublic}>(FlowtyWrapped.AdminPublicPath) + let publicAdmin = getAccount(FlowtyWrapped.getAccountAddress()).capabilities.get<&{FlowtyWrapped.AdminPublic}>(FlowtyWrapped.AdminPublicPath) assert(publicAdmin.check(), message: "admin public isn't configured anymore!") } } \ No newline at end of file diff --git a/transactions/fail_mint_to_wrong_account.cdc b/transactions/fail_mint_to_wrong_account.cdc index 810ff68..e9fabf8 100644 --- a/transactions/fail_mint_to_wrong_account.cdc +++ b/transactions/fail_mint_to_wrong_account.cdc @@ -6,11 +6,11 @@ import "WrappedEditions" transaction(address: Address, wrongAccount:Address, username: String, ticket: Int, totalNftsOwned: Int, floatCount: Int, favoriteCollections: [String], collections: [String]) { // local variable for storing the minter reference - let minter: &FlowtyWrapped.Admin + let minter: auth(FlowtyWrapped.Owner) &FlowtyWrapped.Admin - prepare(acct: AuthAccount) { + prepare(acct: auth(Storage) &Account) { //borrow a reference to the NFTMinter resource in storage - self.minter = acct.borrow<&FlowtyWrapped.Admin>(from: FlowtyWrapped.AdminStoragePath) + self.minter = acct.storage.borrow(from: FlowtyWrapped.AdminStoragePath) ?? panic("Could not borrow a reference to the NFT minter") } @@ -26,7 +26,7 @@ transaction(address: Address, wrongAccount:Address, username: String, ticket: In let data: {String: AnyStruct} = { "wrapped": wrapped2023Data } - let receiver = getAccount(wrongAccount).getCapability<&{NonFungibleToken.CollectionPublic}>(FlowtyWrapped.CollectionPublicPath).borrow()! + let receiver = getAccount(wrongAccount).capabilities.get<&{NonFungibleToken.CollectionPublic}>(FlowtyWrapped.CollectionPublicPath).borrow()! let nft <- self.minter.mintNFT(editionName: "Flowty Wrapped 2023", address: address, data: data ) receiver.deposit(token: <-nft) diff --git a/transactions/mint_flowty_wrapped.cdc b/transactions/mint_flowty_wrapped.cdc index d81856b..40006c3 100644 --- a/transactions/mint_flowty_wrapped.cdc +++ b/transactions/mint_flowty_wrapped.cdc @@ -6,11 +6,11 @@ import "WrappedEditions" transaction(address: Address, username: String, ticket: Int, totalNftsOwned: Int, floatCount: Int, favoriteCollections: [String], collections: [String]) { // local variable for storing the minter reference - let minter: &FlowtyWrapped.Admin + let minter: auth(FlowtyWrapped.Owner) &FlowtyWrapped.Admin - prepare(acct: AuthAccount) { + prepare(acct: auth(Storage) &Account) { //borrow a reference to the NFTMinter resource in storage - self.minter = acct.borrow<&FlowtyWrapped.Admin>(from: FlowtyWrapped.AdminStoragePath) + self.minter = acct.storage.borrow(from: FlowtyWrapped.AdminStoragePath) ?? panic("Could not borrow a reference to the NFT minter") } @@ -26,7 +26,7 @@ transaction(address: Address, username: String, ticket: Int, totalNftsOwned: Int let data: {String: AnyStruct} = { "wrapped": wrapped2023Data } - let receiver = getAccount(address).getCapability<&{NonFungibleToken.CollectionPublic}>(FlowtyWrapped.CollectionPublicPath).borrow()! + let receiver = getAccount(address).capabilities.get<&{NonFungibleToken.CollectionPublic}>(FlowtyWrapped.CollectionPublicPath).borrow()! let nft <- self.minter.mintNFT(editionName: "Flowty Wrapped 2023", address: address, data: data ) receiver.deposit(token: <-nft) diff --git a/transactions/publish_admin.cdc b/transactions/publish_admin.cdc index d8333b2..aa31a87 100644 --- a/transactions/publish_admin.cdc +++ b/transactions/publish_admin.cdc @@ -1,14 +1,10 @@ import "FlowtyWrapped" transaction(receiver: Address) { - prepare(acct: AuthAccount) { + prepare(acct: auth(Capabilities, Inbox) &Account) { let identifier = "FlowtyWrapped_Admin_".concat(receiver.toString()) let p = PrivatePath(identifier: identifier)! - - acct.unlink(p) - let cap = acct.link<&FlowtyWrapped.Admin>(p, target: FlowtyWrapped.AdminStoragePath) - ?? panic("failed to link admin capability") - + let cap = acct.capabilities.storage.issue<&FlowtyWrapped.Admin>(FlowtyWrapped.AdminStoragePath) acct.inbox.publish(cap, name: "flowty-wrapped-minter", recipient: receiver) } } \ No newline at end of file diff --git a/transactions/raffle/draw_from_raffle.cdc b/transactions/raffle/draw_from_raffle.cdc index aa40151..09fda30 100644 --- a/transactions/raffle/draw_from_raffle.cdc +++ b/transactions/raffle/draw_from_raffle.cdc @@ -1,12 +1,12 @@ import "FlowtyRaffles" transaction(id: UInt64) { - prepare(acct: AuthAccount) { - let manager = acct.borrow<&FlowtyRaffles.Manager>(from: FlowtyRaffles.ManagerStoragePath) + prepare(acct: auth(Storage) &Account) { + let manager = acct.storage.borrow(from: FlowtyRaffles.ManagerStoragePath) ?? panic("raffles manager not found") let receiptID = manager.commitDrawing(raffleID: id) - let ref = manager as &FlowtyRaffles.Manager{FlowtyRaffles.ManagerPublic} + let ref = manager as &FlowtyRaffles.Manager manager.revealDrawing(manager: ref, raffleID: id, receiptID: receiptID) } } \ No newline at end of file diff --git a/transactions/register_edition.cdc b/transactions/register_edition.cdc index bf228a0..aaf3f24 100644 --- a/transactions/register_edition.cdc +++ b/transactions/register_edition.cdc @@ -5,8 +5,8 @@ import "FlowtyRaffleSource" // flow transactions send ./transactions/register_edition.cdc false 1703035065 1705195052 "https://storage.googleapis.com/flowty-wrapped-2023-testnet/" QmcvXz8zZ8hZwgH95zVQ8ZEJUZ92oR9MVjCFVYePyCuvxB -n testnet --signer wrapped-testnet transaction(removeAfterReveal: Bool, start: UInt64?, end: UInt64?, baseImageUrl: String, baseHtmlUrl: String) { - prepare(acct: AuthAccount) { - let raffleManager = acct.borrow<&FlowtyRaffles.Manager>(from: FlowtyRaffles.ManagerStoragePath)! + prepare(acct: auth(Storage) &Account) { + let raffleManager = acct.storage.borrow(from: FlowtyRaffles.ManagerStoragePath)! // make a raffle source which is based on addresses let source <- FlowtyRaffleSource.createRaffleSource(entryType: Type
(), removeAfterReveal: removeAfterReveal) @@ -18,7 +18,7 @@ transaction(removeAfterReveal: Bool, start: UInt64?, end: UInt64?, baseImageUrl: let raffleID = raffleManager.createRaffle(source: <- source, details: details, revealers: []) // create and register the edition - let admin = acct.borrow<&FlowtyWrapped.Admin>(from: FlowtyWrapped.AdminStoragePath)! + let admin = acct.storage.borrow(from: FlowtyWrapped.AdminStoragePath)! let edition = WrappedEditions.Edition2023(raffleID: raffleID, baseImageUrl: baseImageUrl, baseHtmlUrl: baseHtmlUrl) admin.registerEdition(edition) } diff --git a/transactions/register_key.cdc b/transactions/register_key.cdc index b6a6e02..ed755d1 100644 --- a/transactions/register_key.cdc +++ b/transactions/register_key.cdc @@ -10,7 +10,7 @@ import Crypto */ transaction(publicKey: String, signatureAlgorithm: UInt8, hashAlgorithm: UInt8, weight: UFix64) { - prepare(signer: AuthAccount) { + prepare(signer: auth(Keys) &Account) { let key = PublicKey( publicKey: publicKey.decodeHex(), signatureAlgorithm: SignatureAlgorithm(rawValue: signatureAlgorithm)! diff --git a/transactions/set_collection_external_url.cdc b/transactions/set_collection_external_url.cdc index 81b0b5b..b2f6ad7 100644 --- a/transactions/set_collection_external_url.cdc +++ b/transactions/set_collection_external_url.cdc @@ -2,10 +2,10 @@ import "FlowtyWrapped" transaction(url: String) { - let admin: &FlowtyWrapped.Admin + let admin: auth(FlowtyWrapped.Owner) &FlowtyWrapped.Admin - prepare(acct: AuthAccount) { - self.admin = acct.borrow<&FlowtyWrapped.Admin>(from: FlowtyWrapped.AdminStoragePath) + prepare(acct: auth(Storage) &Account) { + self.admin = acct.storage.borrow(from: FlowtyWrapped.AdminStoragePath) ?? panic("Could not borrow a reference to the NFT minter") } diff --git a/transactions/setup_flowty_wrapped.cdc b/transactions/setup_flowty_wrapped.cdc index 45f059f..f93b782 100644 --- a/transactions/setup_flowty_wrapped.cdc +++ b/transactions/setup_flowty_wrapped.cdc @@ -3,22 +3,20 @@ import "FlowtyWrapped" import "MetadataViews" transaction { - - prepare(signer: AuthAccount) { - + prepare(signer: auth(Storage, Capabilities) &Account) { // Return early if the account already stores a FlowtyWrapped Collection - if signer.borrow<&FlowtyWrapped.Collection>(from: FlowtyWrapped.CollectionStoragePath) == nil { + if signer.storage.borrow<&FlowtyWrapped.Collection>(from: FlowtyWrapped.CollectionStoragePath) == nil { // Create a new FlowtyWrapped Collection and put it in storage - signer.save( - <-FlowtyWrapped.createEmptyCollection(), + signer.storage.save( + <-FlowtyWrapped.createEmptyCollection(nftType: Type<@FlowtyWrapped.NFT>()), to: FlowtyWrapped.CollectionStoragePath ) // Create a public capability to the Collection that only exposes // the balance field through the Balance interface - signer.link<&FlowtyWrapped.Collection{NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection}>( - FlowtyWrapped.CollectionPublicPath, - target: FlowtyWrapped.CollectionStoragePath + signer.capabilities.publish( + signer.capabilities.storage.issue<&FlowtyWrapped.Collection>(FlowtyWrapped.CollectionStoragePath), + at: FlowtyWrapped.CollectionPublicPath ) } } diff --git a/transactions/withdraw_nft.cdc b/transactions/withdraw_nft.cdc index 7082bc3..a970569 100644 --- a/transactions/withdraw_nft.cdc +++ b/transactions/withdraw_nft.cdc @@ -3,14 +3,14 @@ import "FlowtyWrapped" transaction(owner: Address, receiver: Address, withdrawID: UInt64) { - prepare(acct: AuthAccount){ + prepare(acct: auth(Storage) &Account){ - let collectionRef = acct.borrow<&FlowtyWrapped.Collection>(from: FlowtyWrapped.CollectionStoragePath) + let collectionRef = acct.storage.borrow(from: FlowtyWrapped.CollectionStoragePath) ?? panic("Could not borrow a reference to the owner's collection") let nft <- collectionRef.withdraw(withdrawID: 42) - let recipient = getAccount(receiver).getCapability<&{NonFungibleToken.CollectionPublic}>(FlowtyWrapped.CollectionPublicPath).borrow() ?? panic("invalid receiver collection") + let recipient = getAccount(receiver).capabilities.get<&{NonFungibleToken.CollectionPublic}>(FlowtyWrapped.CollectionPublicPath).borrow() ?? panic("invalid receiver collection") recipient.deposit(token: <-nft) }