From df2f483660133ce38efa4bcf4d8dc294ce3813fa Mon Sep 17 00:00:00 2001 From: Austin Kline Date: Wed, 10 Apr 2024 10:45:48 -0500 Subject: [PATCH 1/4] add type definition for DropSummary --- contracts/DropFactory.cdc | 13 +- contracts/DropTypes.cdc | 141 ++++++++++++++++++ contracts/FlowtyDrops.cdc | 4 +- flow.json | 7 + scripts/get_drop_summary.cdc | 5 + tests/FlowtyDrops_tests.cdc | 9 ++ .../drops/add_time_based_open_edition.cdc | 3 +- 7 files changed, 176 insertions(+), 6 deletions(-) create mode 100644 contracts/DropTypes.cdc create mode 100644 scripts/get_drop_summary.cdc diff --git a/contracts/DropFactory.cdc b/contracts/DropFactory.cdc index c9b154d..244fe55 100644 --- a/contracts/DropFactory.cdc +++ b/contracts/DropFactory.cdc @@ -14,7 +14,8 @@ pub contract DropFactory { price: UFix64, paymentTokenType: Type, dropDisplay: MetadataViews.Display, - minterCap: Capability<&{FlowtyDrops.Minter}> + minterCap: Capability<&{FlowtyDrops.Minter}>, + nftTypeIdentifier: String ): @FlowtyDrops.Drop { pre { paymentTokenType.isSubtype(of: Type<@FungibleToken.Vault>()): "paymentTokenType must be a FungibleToken" @@ -32,7 +33,9 @@ pub contract DropFactory { let phaseDetails = FlowtyDrops.PhaseDetails(switcher: switcher, display: nil, pricer: pricer, addressVerifier: addressVerifier) let phase <- FlowtyDrops.createPhase(details: phaseDetails) - let dropDetails = FlowtyDrops.DropDetails(display: dropDisplay, medias: nil, commissionRate: 0.05) + + let nftType = CompositeType(nftTypeIdentifier) ?? panic("invalid nft type identifier") + let dropDetails = FlowtyDrops.DropDetails(display: dropDisplay, medias: nil, commissionRate: 0.05, nftType: nftType) let drop <- FlowtyDrops.createDrop(details: dropDetails, minterCap: minterCap, phases: <- [<-phase]) return <- drop @@ -44,7 +47,8 @@ pub contract DropFactory { dropDisplay: MetadataViews.Display, minterCap: Capability<&{FlowtyDrops.Minter}>, startUnix: UInt64?, - endUnix: UInt64? + endUnix: UInt64?, + nftTypeIdentifier: String ): @FlowtyDrops.Drop { pre { paymentTokenType.isSubtype(of: Type<@FungibleToken.Vault>()): "paymentTokenType must be a FungibleToken" @@ -62,7 +66,8 @@ pub contract DropFactory { let phaseDetails = FlowtyDrops.PhaseDetails(switcher: switcher, display: nil, pricer: pricer, addressVerifier: addressVerifier) let phase <- FlowtyDrops.createPhase(details: phaseDetails) - let dropDetails = FlowtyDrops.DropDetails(display: dropDisplay, medias: nil, commissionRate: 0.05) + let nftType = CompositeType(nftTypeIdentifier) ?? panic("invalid nft type identifier") + let dropDetails = FlowtyDrops.DropDetails(display: dropDisplay, medias: nil, commissionRate: 0.05, nftType: nftType) let drop <- FlowtyDrops.createDrop(details: dropDetails, minterCap: minterCap, phases: <- [<-phase]) return <- drop diff --git a/contracts/DropTypes.cdc b/contracts/DropTypes.cdc new file mode 100644 index 0000000..0fbb4b3 --- /dev/null +++ b/contracts/DropTypes.cdc @@ -0,0 +1,141 @@ +import "FlowtyDrops" +import "MetadataViews" +import "ViewResolver" + +pub contract DropTypes { + pub struct DropSummary { + pub let id: UInt64 + pub let display: MetadataViews.Display + pub let medias: MetadataViews.Medias? + pub let totalMinted: Int + pub let minterCount: Int + pub let commissionRate: UFix64 + pub let nftType: String + + pub let address: Address? + + pub let phases: [PhaseSummary] + + init( + id: UInt64, + display: MetadataViews.Display, + medias: MetadataViews.Medias?, + totalMinted: Int, + minterCount: Int, + commissionRate: UFix64, + nftType: Type, + address: Address?, + phases: [PhaseSummary] + ) { + self.id = id + self.display = display + self.medias = medias + self.totalMinted = totalMinted + self.commissionRate = commissionRate + self.minterCount = minterCount + self.nftType = nftType.identifier + self.address = address + self.phases = phases + } + } + + pub struct PhaseSummary { + pub let id: UInt64 + pub let index: Int + + pub let switcherType: String + pub let pricerType: String + pub let addressVerifierType: String + + pub let hasStarted: Bool + pub let hasEnded: Bool + pub let start: UInt64? + pub let end: UInt64? + + pub let paymentTypes: [String] + + pub let address: Address? + pub let remainingForAddress: Int? + + init( + index: Int, + phase: &{FlowtyDrops.PhasePublic}, + address: Address?, + totalMinted: Int? + ) { + self.index = index + self.id = phase.uuid + + let d = phase.getDetails() + self.switcherType = d.switcher.getType().identifier + self.pricerType = d.pricer.getType().identifier + self.addressVerifierType = d.addressVerifier.getType().identifier + + self.hasStarted = d.switcher.hasStarted() + self.hasEnded = d.switcher.hasEnded() + self.start = d.switcher.getStart() + self.end = d.switcher.getEnd() + + self.paymentTypes = [] + for pt in d.pricer.getPaymentTypes() { + self.paymentTypes.append(pt.identifier) + } + + if let addr = address { + self.address = address + self.remainingForAddress = d.addressVerifier.remainingForAddress(addr: addr, totalMinted: totalMinted ?? 0) + } else { + self.address = nil + self.remainingForAddress = nil + } + } + } + + pub fun getDropSummary(contractAddress: Address, contractName: String, dropID: UInt64, minter: Address?): DropSummary? { + let resolver = getAccount(contractAddress).contracts.borrow<&ViewResolver>(name: contractName) + if resolver == nil { + return nil + } + + let dropResolver = resolver!.resolveView(Type()) as! FlowtyDrops.DropResolver? + if dropResolver == nil { + return nil + } + + let container = dropResolver!.borrowContainer() + if container == nil { + return nil + } + + let drop = container!.borrowDropPublic(id: dropID) + if drop == nil { + return nil + } + + let dropDetails = drop!.getDetails() + + let phaseSummaries: [PhaseSummary] = [] + for index, phase in drop!.borrowAllPhases() { + let summary = PhaseSummary( + index: 0, + phase: phase, + address: minter, + totalMinted: minter != nil ? dropDetails.minters[minter!] : nil + ) + } + + let dropSummary = DropSummary( + id: drop!.uuid, + display: dropDetails.display, + medias: dropDetails.medias, + totalMinted: dropDetails.totalMinted, + minterCount: dropDetails.minters.keys.length, + commissionRate: dropDetails.commissionRate, + nftType: dropDetails.nftType, + address: minter, + phases: phaseSummaries + ) + + return nil + } +} \ No newline at end of file diff --git a/contracts/FlowtyDrops.cdc b/contracts/FlowtyDrops.cdc index 8967e67..d1eb1ad 100644 --- a/contracts/FlowtyDrops.cdc +++ b/contracts/FlowtyDrops.cdc @@ -184,6 +184,7 @@ pub contract FlowtyDrops { pub var totalMinted: Int pub var minters: {Address: Int} pub let commissionRate: UFix64 + pub let nftType: Type access(contract) fun addMinted(num: Int, addr: Address) { self.totalMinted = self.totalMinted + num @@ -194,12 +195,13 @@ pub contract FlowtyDrops { self.minters[addr] = self.minters[addr]! + num } - init(display: MetadataViews.Display, medias: MetadataViews.Medias?, commissionRate: UFix64) { + init(display: MetadataViews.Display, medias: MetadataViews.Medias?, commissionRate: UFix64, nftType: Type) { self.display = display self.medias = medias self.totalMinted = 0 self.commissionRate = commissionRate self.minters = {} + self.nftType = nftType } } diff --git a/flow.json b/flow.json index a3b21b9..b5a658a 100644 --- a/flow.json +++ b/flow.json @@ -79,6 +79,12 @@ "testing": "0x0000000000000006" } }, + "DropTypes": { + "source": "./contracts/DropTypes.cdc", + "aliases": { + "testing": "0x0000000000000008" + } + }, "OpenEditionNFT": { "source": "./contracts/nft/OpenEditionNFT.cdc", "aliases": { @@ -148,6 +154,7 @@ "FlowtyAddressVerifiers", "FlowtyPricers", "DropFactory", + "DropTypes", "OpenEditionNFT", "NonFungibleToken", "ViewResolver", diff --git a/scripts/get_drop_summary.cdc b/scripts/get_drop_summary.cdc new file mode 100644 index 0000000..16d6d10 --- /dev/null +++ b/scripts/get_drop_summary.cdc @@ -0,0 +1,5 @@ +import "DropTypes" + +pub fun main(contractAddress: Address, contractName: String, dropID: UInt64, minter: Address?): DropTypes.DropSummary? { + return DropTypes.getDropSummary(contractAddress: contractAddress, contractName: contractName, dropID: dropID, minter: minter) +} \ No newline at end of file diff --git a/tests/FlowtyDrops_tests.cdc b/tests/FlowtyDrops_tests.cdc index e085438..f926e8a 100644 --- a/tests/FlowtyDrops_tests.cdc +++ b/tests/FlowtyDrops_tests.cdc @@ -4,6 +4,7 @@ import "FlowToken" import "FlowtyDrops" import "OpenEditionNFT" import "ExampleToken" +import "DropTypes" pub let defaultEndlessOpenEditionName = "Default Endless Open Edition" @@ -144,6 +145,14 @@ pub fun test_OpenEditionNFT_addPhase() { Test.assert(!activePhaseIDs.contains(phaseEvent.id), message: "unexpected active phase length") } +pub fun test_OpenEditionNFT_getDropSummary() { + let dropID = createDefaultTimeBasedOpenEditionDrop() + + let minter = Test.createAccount() + + let summary = scriptExecutor("get_drop_summary.cdc", [openEditionAccount.address, "OpenEditionNFT", dropID, minter.address])! as! DropTypes.DropSummary +} + // ------------------------------------------------------------------------ // Helper functions section diff --git a/transactions/drops/add_time_based_open_edition.cdc b/transactions/drops/add_time_based_open_edition.cdc index 06e9c7a..59a7e76 100644 --- a/transactions/drops/add_time_based_open_edition.cdc +++ b/transactions/drops/add_time_based_open_edition.cdc @@ -12,7 +12,8 @@ transaction( paymentIdentifier: String, startUnix: UInt64?, endUnix: UInt64?, - minterPrivatePath: PrivatePath + minterPrivatePath: PrivatePath, + nftTypeIdentifier: String ) { prepare(acct: AuthAccount) { if acct.borrow<&AnyResource>(from: FlowtyDrops.ContainerStoragePath) == nil { From 92486c1b03813a8a5100988ede80352f88458d9e Mon Sep 17 00:00:00 2001 From: Austin Kline Date: Wed, 10 Apr 2024 11:06:30 -0500 Subject: [PATCH 2/4] fix tests --- contracts/DropTypes.cdc | 2 +- tests/FlowtyDrops_tests.cdc | 31 +++++++++++++++++-- tests/test_helpers.cdc | 20 +++++++++--- .../drops/add_endless_open_edition.cdc | 5 +-- .../drops/add_time_based_open_edition.cdc | 3 +- 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/contracts/DropTypes.cdc b/contracts/DropTypes.cdc index 0fbb4b3..570d910 100644 --- a/contracts/DropTypes.cdc +++ b/contracts/DropTypes.cdc @@ -136,6 +136,6 @@ pub contract DropTypes { phases: phaseSummaries ) - return nil + return dropSummary } } \ No newline at end of file diff --git a/tests/FlowtyDrops_tests.cdc b/tests/FlowtyDrops_tests.cdc index f926e8a..1d8426a 100644 --- a/tests/FlowtyDrops_tests.cdc +++ b/tests/FlowtyDrops_tests.cdc @@ -149,8 +149,33 @@ pub fun test_OpenEditionNFT_getDropSummary() { let dropID = createDefaultTimeBasedOpenEditionDrop() let minter = Test.createAccount() + setupExampleToken(minter) + mintExampleTokens(minter, 100.0) let summary = scriptExecutor("get_drop_summary.cdc", [openEditionAccount.address, "OpenEditionNFT", dropID, minter.address])! as! DropTypes.DropSummary + Test.assertEqual(minter.address, summary.address!) + + let numToMint = 5 + let totalCost = 5.0 + + mintDrop( + minter: minter, + contractAddress: openEditionAccount.address, + contractName: "OpenEditionNFT", + numToMint: numToMint, + totalCost: totalCost, + paymentIdentifier: exampleTokenIdentifier(), + paymentStoragePath: exampleTokenStoragePath, + paymentReceiverPath: exampleTokenReceiverPath, + dropID: dropID, + dropPhaseIndex: 0, + nftIdentifier: Type<@OpenEditionNFT.NFT>().identifier, + commissionAddress: flowtyDropsAccount.address + ) + + let summaryAfter = scriptExecutor("get_drop_summary.cdc", [openEditionAccount.address, "OpenEditionNFT", dropID, minter.address])! as! DropTypes.DropSummary + + Test.assertEqual(summaryAfter.totalMinted, numToMint) } // ------------------------------------------------------------------------ @@ -165,7 +190,8 @@ pub fun createDefaultEndlessOpenEditionDrop(): UInt64 { ipfsPath: nil, price: 1.0, paymentIdentifier: exampleTokenIdentifier(), - minterPrivatePath: FlowtyDrops.MinterPrivatePath + minterPrivatePath: FlowtyDrops.MinterPrivatePath, + nftTypeIdentifier: openEditionNftIdentifier() ) } @@ -182,7 +208,8 @@ pub fun createDefaultTimeBasedOpenEditionDrop(): UInt64 { paymentIdentifier: exampleTokenIdentifier(), startUnix: UInt64(getCurrentTime()), endUnix: UInt64(currentTime + 5.0), - minterPrivatePath: FlowtyDrops.MinterPrivatePath + minterPrivatePath: FlowtyDrops.MinterPrivatePath, + nftTypeIdentifier: openEditionNftIdentifier() ) } diff --git a/tests/test_helpers.cdc b/tests/test_helpers.cdc index 4ec24ff..4acd92d 100644 --- a/tests/test_helpers.cdc +++ b/tests/test_helpers.cdc @@ -4,6 +4,7 @@ import "NonFungibleToken" import "FlowToken" import "FlowtyDrops" import "ExampleToken" +import "OpenEditionNFT" // Helper functions. All of the following were taken from // https://github.com/onflow/Offers/blob/fd380659f0836e5ce401aa99a2975166b2da5cb0/lib/cadence/test/Offers.cdc @@ -164,12 +165,17 @@ pub let flowTokenReceiverPath = /public/flowTokenReceiver pub fun deployAll() { deploy("ExampleToken", "../contracts/standard/ExampleToken.cdc", []) + // 0x6 deploy("FlowtyDrops", "../contracts/FlowtyDrops.cdc", []) deploy("FlowtySwitchers", "../contracts/FlowtySwitchers.cdc", []) deploy("FlowtyPricers", "../contracts/FlowtyPricers.cdc", []) deploy("FlowtyAddressVerifiers", "../contracts/FlowtyAddressVerifiers.cdc", []) deploy("DropFactory", "../contracts/DropFactory.cdc", []) + // 0x8 + deploy("DropTypes", "../contracts/DropTypes.cdc", []) + + // 0x7 deploy("OpenEditionNFT", "../contracts/nft/OpenEditionNFT.cdc", []) @@ -235,10 +241,11 @@ pub fun createEndlessOpenEditionDrop( ipfsPath: String?, price: UFix64, paymentIdentifier: String, - minterPrivatePath: PrivatePath + minterPrivatePath: PrivatePath, + nftTypeIdentifier: String ): UInt64 { txExecutor("drops/add_endless_open_edition.cdc", [acct], [ - name, description, ipfsCid, ipfsPath, price, paymentIdentifier, minterPrivatePath + name, description, ipfsCid, ipfsPath, price, paymentIdentifier, minterPrivatePath, nftTypeIdentifier ], nil, nil) let e = Test.eventsOfType(Type()).removeLast() as! FlowtyDrops.DropAdded @@ -255,10 +262,11 @@ pub fun createTimebasedOpenEditionDrop( paymentIdentifier: String, startUnix: UInt64?, endUnix: UInt64?, - minterPrivatePath: PrivatePath + minterPrivatePath: PrivatePath, + nftTypeIdentifier: String ): UInt64 { txExecutor("drops/add_time_based_open_edition.cdc", [acct], [ - name, description, ipfsCid, ipfsPath, price, paymentIdentifier, startUnix, endUnix, minterPrivatePath + name, description, ipfsCid, ipfsPath, price, paymentIdentifier, startUnix, endUnix, minterPrivatePath, nftTypeIdentifier ], nil, nil) let e = Test.eventsOfType(Type()).removeLast() as! FlowtyDrops.DropAdded @@ -281,6 +289,10 @@ pub fun exampleTokenIdentifier(): String { return Type<@ExampleToken.Vault>().identifier } +pub fun openEditionNftIdentifier(): String { + return Type<@OpenEditionNFT.NFT>().identifier +} + pub fun hasDropPhaseStarted(contractAddress: Address, contractName: String, dropID: UInt64, phaseIndex: Int): Bool { return scriptExecutor("has_phase_started.cdc", [contractAddress, contractName, dropID, phaseIndex])! as! Bool } diff --git a/transactions/drops/add_endless_open_edition.cdc b/transactions/drops/add_endless_open_edition.cdc index 9c66d28..ad7da59 100644 --- a/transactions/drops/add_endless_open_edition.cdc +++ b/transactions/drops/add_endless_open_edition.cdc @@ -10,7 +10,8 @@ transaction( ipfsPath: String?, price: UFix64, paymentIdentifier: String, - minterPrivatePath: PrivatePath + minterPrivatePath: PrivatePath, + nftTypeIdentifier: String ) { prepare(acct: AuthAccount) { if acct.borrow<&AnyResource>(from: FlowtyDrops.ContainerStoragePath) == nil { @@ -33,7 +34,7 @@ transaction( description: description, thumbnail: MetadataViews.IPFSFile(cid: ipfsCid, path: ipfsPath) ) - let drop <- DropFactory.createEndlessOpenEditionDrop(price: 1.0, paymentTokenType: paymentType, dropDisplay: dropDisplay, minterCap: minter) + let drop <- DropFactory.createEndlessOpenEditionDrop(price: 1.0, paymentTokenType: paymentType, dropDisplay: dropDisplay, minterCap: minter, nftTypeIdentifier: nftTypeIdentifier) container.addDrop(<- drop) } } diff --git a/transactions/drops/add_time_based_open_edition.cdc b/transactions/drops/add_time_based_open_edition.cdc index 59a7e76..74de7df 100644 --- a/transactions/drops/add_time_based_open_edition.cdc +++ b/transactions/drops/add_time_based_open_edition.cdc @@ -36,7 +36,8 @@ transaction( description: description, thumbnail: MetadataViews.IPFSFile(cid: ipfsCid, path: ipfsPath) ) - let drop <- DropFactory.createTimeBasedOpenEditionDrop(price: price, paymentTokenType: paymentType, dropDisplay: dropDisplay, minterCap: minter, startUnix: startUnix, endUnix: endUnix) + + let drop <- DropFactory.createTimeBasedOpenEditionDrop(price: price, paymentTokenType: paymentType, dropDisplay: dropDisplay, minterCap: minter, startUnix: startUnix, endUnix: endUnix, nftTypeIdentifier: nftTypeIdentifier) container.addDrop(<- drop) } } From f2bffc54f4ed53199a10d998fb6a982fb3f842b0 Mon Sep 17 00:00:00 2001 From: Austin Kline Date: Wed, 10 Apr 2024 11:28:23 -0500 Subject: [PATCH 3/4] add block height and timestamp to summary --- contracts/DropTypes.cdc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contracts/DropTypes.cdc b/contracts/DropTypes.cdc index 570d910..0d5ea33 100644 --- a/contracts/DropTypes.cdc +++ b/contracts/DropTypes.cdc @@ -16,6 +16,9 @@ pub contract DropTypes { pub let phases: [PhaseSummary] + pub let blockTimestamp: UInt64 + pub let blockHeight: UInt64 + init( id: UInt64, display: MetadataViews.Display, @@ -36,6 +39,10 @@ pub contract DropTypes { self.nftType = nftType.identifier self.address = address self.phases = phases + + let b = getCurrentBlock() + self.blockHeight = b.height + self.blockTimestamp = UInt64(b.timestamp) } } From d871d61166e4ba7b6c05d0a9f24886ad385ab260 Mon Sep 17 00:00:00 2001 From: Austin Kline Date: Wed, 10 Apr 2024 11:35:09 -0500 Subject: [PATCH 4/4] add block height and timestamp --- contracts/DropTypes.cdc | 4 ++++ tests/FlowtyDrops_tests.cdc | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/DropTypes.cdc b/contracts/DropTypes.cdc index 0d5ea33..0a2c70f 100644 --- a/contracts/DropTypes.cdc +++ b/contracts/DropTypes.cdc @@ -13,6 +13,7 @@ pub contract DropTypes { pub let nftType: String pub let address: Address? + pub let mintedByAddress: Int? pub let phases: [PhaseSummary] @@ -25,6 +26,7 @@ pub contract DropTypes { medias: MetadataViews.Medias?, totalMinted: Int, minterCount: Int, + mintedByAddress: Int?, commissionRate: UFix64, nftType: Type, address: Address?, @@ -36,6 +38,7 @@ pub contract DropTypes { self.totalMinted = totalMinted self.commissionRate = commissionRate self.minterCount = minterCount + self.mintedByAddress = mintedByAddress self.nftType = nftType.identifier self.address = address self.phases = phases @@ -137,6 +140,7 @@ pub contract DropTypes { medias: dropDetails.medias, totalMinted: dropDetails.totalMinted, minterCount: dropDetails.minters.keys.length, + mintedByAddress: minter != nil ? dropDetails.minters[minter!] : nil, commissionRate: dropDetails.commissionRate, nftType: dropDetails.nftType, address: minter, diff --git a/tests/FlowtyDrops_tests.cdc b/tests/FlowtyDrops_tests.cdc index 1d8426a..c2d9b0e 100644 --- a/tests/FlowtyDrops_tests.cdc +++ b/tests/FlowtyDrops_tests.cdc @@ -174,8 +174,9 @@ pub fun test_OpenEditionNFT_getDropSummary() { ) let summaryAfter = scriptExecutor("get_drop_summary.cdc", [openEditionAccount.address, "OpenEditionNFT", dropID, minter.address])! as! DropTypes.DropSummary - Test.assertEqual(summaryAfter.totalMinted, numToMint) + Test.assertEqual(summaryAfter.mintedByAddress!, numToMint) + } // ------------------------------------------------------------------------