diff --git a/contracts/DropTypes.cdc b/contracts/DropTypes.cdc index 09801e6..73cdd92 100644 --- a/contracts/DropTypes.cdc +++ b/contracts/DropTypes.cdc @@ -2,6 +2,7 @@ import "FlowtyDrops" import "MetadataViews" import "ViewResolver" import "AddressUtils" +import "ContractManager" access(all) contract DropTypes { access(all) struct Display { @@ -34,6 +35,7 @@ access(all) contract DropTypes { access(all) let minterCount: Int access(all) let commissionRate: UFix64 access(all) let nftType: String + access(all) let creator: Address? access(all) let address: Address? access(all) let mintedByAddress: Int? @@ -55,10 +57,12 @@ access(all) contract DropTypes { nftType: Type, address: Address?, phases: [PhaseSummary], - royaltyRate: UFix64 + royaltyRate: UFix64, + creator: Address? ) { self.id = id self.display = Display(display) + self.creator = creator self.medias = [] for m in medias?.items ?? [] { @@ -169,6 +173,8 @@ access(all) contract DropTypes { let contractAddress = AddressUtils.parseAddress(nftType)! let contractName = segments[2] + let creator = self.getCreatorAddress(contractAddress) + let resolver = getAccount(contractAddress).contracts.borrow<&{ViewResolver}>(name: contractName) if resolver == nil { return nil @@ -224,7 +230,8 @@ access(all) contract DropTypes { nftType: CompositeType(dropDetails.nftType)!, address: minter, phases: phaseSummaries, - royaltyRate: royaltyRate + royaltyRate: royaltyRate, + creator: creator ) return dropSummary @@ -235,6 +242,8 @@ access(all) contract DropTypes { let segments = nftTypeIdentifier.split(separator: ".") let contractAddress = AddressUtils.parseAddress(nftType)! let contractName = segments[2] + + let creator = self.getCreatorAddress(contractAddress) let resolver = getAccount(contractAddress).contracts.borrow<&{ViewResolver}>(name: contractName) if resolver == nil { @@ -297,10 +306,37 @@ access(all) contract DropTypes { nftType: CompositeType(dropDetails.nftType)!, address: minter, phases: phaseSummaries, - royaltyRate: royaltyRate + royaltyRate: royaltyRate, + creator: creator )) } return summaries } + + access(all) fun getCreatorAddress(_ contractAddress: Address): Address? { + // We look for a two-way relationship between creator and collection. A contract can expose an address at ContractManager.OwnerPublicPath + // specifying the owning account. If found, we will check that same account for a &ContractManager.Manager resource at ContractManager.PublicPath, + // which, when borrowed, can return its underlying account address using &Manager.getAccount(). + // + // If the addresses match, we consider this account to be the creator of a drop + let tmp = getAccount(contractAddress).capabilities.borrow<&Address>(ContractManager.OwnerPublicPath) + if tmp == nil { + return nil + } + + let creator = *(tmp!) + let manager = getAccount(creator).capabilities.borrow<&ContractManager.Manager>(ContractManager.PublicPath) + if manager == nil { + return nil + } + + let contractManagerAccount = manager!.getAccount() + + if contractManagerAccount.address != contractAddress { + return nil + } + + return creator + } } \ No newline at end of file diff --git a/flow.json b/flow.json index 8f81d60..000e344 100644 --- a/flow.json +++ b/flow.json @@ -12,27 +12,18 @@ }, "emulator-account": { "address": "f8d6e0586b0a20c7", - "key": { - "type": "file", - "location": "emulator-account.pkey" - } + "key": "f2e846bd4c1fbf17839ae59e111c6b1c98579eda7a841412f102d6621ec671cb" }, "emulator-ft": { "address": "ee82856bf20e2aa6", - "key": { - "type": "file", - "location": "emulator-account.pkey" - } + "key": "f2e846bd4c1fbf17839ae59e111c6b1c98579eda7a841412f102d6621ec671cb" }, "emulator-flowtoken": { "address": "0ae53cb6e3f42a79", - "key": { - "type": "file", - "location": "emulator-account.pkey" - } + "key": "f2e846bd4c1fbf17839ae59e111c6b1c98579eda7a841412f102d6621ec671cb" }, "flowty-drops-testnet": { - "address": "0x772a10c786851a1b", + "address": "0x155bce6ac93f3e00", "key": { "type": "google-kms", "index": 0, @@ -42,7 +33,7 @@ } }, "droptypes-testnet": { - "address": "0x22f23883bf122007", + "address": "0x53ff16309e318ae2", "key": { "type": "google-kms", "index": 0, diff --git a/tests/ContractManager_tests.cdc b/tests/ContractManager_tests.cdc new file mode 100644 index 0000000..e4b0816 --- /dev/null +++ b/tests/ContractManager_tests.cdc @@ -0,0 +1,17 @@ +import Test +import "./test_helpers.cdc" +import "ContractManager" + +access(all) fun setup() { + deployAll() +} + +access(all) fun test_SetupContractManager() { + let acct = Test.createAccount() + mintFlowTokens(acct, 10.0) + + txExecutor("contract-manager/setup.cdc", [acct], [1.0]) + + let savedEvent = Test.eventsOfType(Type()).removeLast() as! ContractManager.ManagerSaved + Test.assertEqual(acct.address, savedEvent.ownerAddress) +} \ No newline at end of file diff --git a/tests/FlowtyDrops_tests.cdc b/tests/FlowtyDrops_tests.cdc index 434bfe7..25e98b9 100644 --- a/tests/FlowtyDrops_tests.cdc +++ b/tests/FlowtyDrops_tests.cdc @@ -12,7 +12,7 @@ access(all) fun setup() { } access(all) fun afterEach() { - txExecutor("drops/remove_all_drops.cdc", [openEditionAccount], [], nil, nil) + txExecutor("drops/remove_all_drops.cdc", [openEditionAccount], []) } access(all) fun testImports() { @@ -91,7 +91,7 @@ access(all) fun test_OpenEditionNFT_EditPhaseDetails() { let currentTime = getCurrentTime() let newStart = UInt64(currentTime + 5.0) - txExecutor("drops/edit_timebased_phase_start_and_end.cdc", [openEditionAccount], [dropID, 0, newStart, newStart + 5], nil, nil) + txExecutor("drops/edit_timebased_phase_start_and_end.cdc", [openEditionAccount], [dropID, 0, newStart, newStart + 5]) Test.assertEqual(false, hasDropPhaseStarted(nftTypeIdentifier: openEditionNftIdentifier(), dropID: dropID, phaseIndex: 0)) Test.assertEqual(false, hasDropPhaseEnded(nftTypeIdentifier: openEditionNftIdentifier(), dropID: dropID, phaseIndex: 0)) @@ -109,14 +109,14 @@ access(all) fun test_OpenEditionNFT_EditPrice() { let dropID = createDefaultTimeBasedOpenEditionDrop() Test.assertEqual(1.0, getPriceAtPhase(nftTypeIdentifier: openEditionNftIdentifier(), dropID: dropID, phaseIndex: 0, minter: openEditionAccount.address, numToMint: 1, paymentIdentifier: flowTokenIdentifier())) - txExecutor("drops/edit_flat_price.cdc", [openEditionAccount], [dropID, 0, 2.0], nil, nil) + txExecutor("drops/edit_flat_price.cdc", [openEditionAccount], [dropID, 0, 2.0]) Test.assertEqual(2.0, getPriceAtPhase(nftTypeIdentifier: openEditionNftIdentifier(), dropID: dropID, phaseIndex: 0, minter: openEditionAccount.address, numToMint: 1, paymentIdentifier: flowTokenIdentifier())) } access(all) fun test_OpenEditionNFT_EditMaxPerMint() { let dropID = createDefaultTimeBasedOpenEditionDrop() Test.assertEqual(true, canMintAtPhase(nftTypeIdentifier: openEditionNftIdentifier(), dropID: dropID, phaseIndex: 0, minter: openEditionAccount.address, numToMint: 10, totalMinted: 0, paymentIdentifier: flowTokenIdentifier())) - txExecutor("drops/edit_address_verifier_max_per_mint.cdc", [openEditionAccount], [dropID, 0, 5], nil, nil) + txExecutor("drops/edit_address_verifier_max_per_mint.cdc", [openEditionAccount], [dropID, 0, 5]) Test.assertEqual(false, canMintAtPhase(nftTypeIdentifier: openEditionNftIdentifier(), dropID: dropID, phaseIndex: 0, minter: openEditionAccount.address, numToMint: 10, totalMinted: 0, paymentIdentifier: flowTokenIdentifier())) Test.assertEqual(true, canMintAtPhase(nftTypeIdentifier: openEditionNftIdentifier(), dropID: dropID, phaseIndex: 0, minter: openEditionAccount.address, numToMint: 5, totalMinted: 0, paymentIdentifier: flowTokenIdentifier())) @@ -131,13 +131,13 @@ access(all) fun test_OpenEditionNFT_GetActivePhases() { access(all) fun test_OpenEditionNFT_addPhase() { let dropID = createDefaultTimeBasedOpenEditionDrop() - txExecutor("drops/add_free_phase.cdc", [openEditionAccount], [dropID, nil, nil, "https://example.com/fake_image.jpg"], nil, nil) + txExecutor("drops/add_free_phase.cdc", [openEditionAccount], [dropID, nil, nil, "https://example.com/fake_image.jpg"]) let phaseEvent = Test.eventsOfType(Type()).removeLast() as! FlowtyDrops.PhaseAdded var activePhaseIDs = scriptExecutor("get_active_phases.cdc", [openEditionNftIdentifier(), dropID])! as! [UInt64] Test.assert(activePhaseIDs.contains(phaseEvent.id), message: "unexpected active phase length") - txExecutor("drops/remove_last_phase.cdc", [openEditionAccount], [dropID, nil, nil], nil, nil) + txExecutor("drops/remove_last_phase.cdc", [openEditionAccount], [dropID, nil, nil]) activePhaseIDs = scriptExecutor("get_active_phases.cdc", [openEditionNftIdentifier(), dropID])! as! [UInt64] Test.assert(!activePhaseIDs.contains(phaseEvent.id), message: "unexpected active phase length") } @@ -251,7 +251,7 @@ access(all) fun mintDrop( ): [UInt64] { txExecutor("drops/mint.cdc", [minter], [ nftTypeIdentifier, numToMint, totalCost, paymentIdentifier, paymentStoragePath, paymentReceiverPath, dropID, dropPhaseIndex, nftIdentifier, commissionAddress - ], nil, nil) + ]) let ids: [UInt64] = [] let events = Test.eventsOfType(Type()) diff --git a/tests/test_helpers.cdc b/tests/test_helpers.cdc index 98feadd..549d130 100644 --- a/tests/test_helpers.cdc +++ b/tests/test_helpers.cdc @@ -35,7 +35,7 @@ access(all) fun expectScriptFailure(_ scriptName: String, _ arguments: [AnyStruc return scriptResult.error!.message } -access(all) fun txExecutor(_ txName: String, _ signers: [Test.TestAccount], _ arguments: [AnyStruct], _ expectedError: String?, _ expectedErrorType: ErrorType?): Test.TransactionResult { +access(all) fun txExecutor(_ txName: String, _ signers: [Test.TestAccount], _ arguments: [AnyStruct]): Test.TransactionResult { let txCode = loadCode(txName, "transactions") let authorizers: [Address] = [] @@ -173,7 +173,7 @@ access(all) fun deploy(_ name: String, _ path: String, _ arguments: [AnyStruct]) } access(all) fun heartbeat() { - txExecutor("util/heartbeat.cdc", [serviceAccount], [], nil, nil) + txExecutor("util/heartbeat.cdc", [serviceAccount], []) } access(all) fun getCurrentTime(): UFix64 { @@ -205,7 +205,7 @@ access(all) fun mintFromDrop( nftIdentifier, commissionReceiver ] - txExecutor("drops/mint.cdc", [minter], args, nil, nil) + txExecutor("drops/mint.cdc", [minter], args) } access(all) fun getDropIDs( @@ -227,7 +227,7 @@ access(all) fun createEndlessOpenEditionDrop( ): UInt64 { txExecutor("drops/add_endless_open_edition.cdc", [acct], [ name, description, ipfsCid, ipfsPath, price, paymentIdentifier, minterControllerID, nftTypeIdentifier - ], nil, nil) + ]) let e = Test.eventsOfType(Type()).removeLast() as! FlowtyDrops.DropAdded return e.id @@ -248,18 +248,18 @@ access(all) fun createTimebasedOpenEditionDrop( ): UInt64 { txExecutor("drops/add_time_based_open_edition.cdc", [acct], [ name, description, ipfsCid, ipfsPath, price, paymentIdentifier, startUnix, endUnix, minterControllerID, nftTypeIdentifier - ], nil, nil) + ]) let e = Test.eventsOfType(Type()).removeLast() as! FlowtyDrops.DropAdded return e.id } access(all) fun sendFlowTokens(fromAccount: Test.TestAccount, toAccount: Test.TestAccount, amount: UFix64) { - txExecutor("util/send_flow_tokens.cdc", [fromAccount], [toAccount.address, amount], nil, nil) + txExecutor("util/send_flow_tokens.cdc", [fromAccount], [toAccount.address, amount]) } access(all) fun mintFlowTokens(_ acct: Test.TestAccount, _ amount: UFix64) { - txExecutor("flow-token/mint.cdc", [serviceAccount], [acct.address, amount], nil, nil) + txExecutor("flow-token/mint.cdc", [serviceAccount], [acct.address, amount]) } access(all) fun flowTokenIdentifier(): String {