Skip to content

Commit

Permalink
add some basic tests for getting drop details (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
austinkline authored Apr 7, 2024
1 parent 7e62ce0 commit b718159
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 25 deletions.
51 changes: 44 additions & 7 deletions contracts/FlowtyDrops.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import "FungibleToken"
import "MetadataViews"

pub contract FlowtyDrops {

pub let ContainerStoragePath: StoragePath
pub let ContainerPublicPath: PublicPath

// TODO: Event definitions
// - DropAdded
// - Phase Started
// - Phase Ended
pub let MinterStoragePath: StoragePath
pub let MinterPrivatePath: PrivatePath

pub event DropAdded(address: Address, id: UInt64, name: String, description: String, imageUrl: String, start: UInt64?, end: UInt64?)

// - Minted
// Interface to expose all the components necessary to participate in a drop
Expand All @@ -19,6 +19,15 @@ pub contract FlowtyDrops {
pub fun borrowPhase(index: Int): &{PhasePublic}
pub fun borrowActivePhases(): [&{PhasePublic}]
pub fun borrowAllPhases(): [&{PhasePublic}]
pub fun mint(
payment: @FungibleToken.Vault,
amount: Int,
phaseIndex: Int,
expectedType: Type,
receiverCap: Capability<&{NonFungibleToken.CollectionPublic}>,
commissionReceiver: Capability<&{FungibleToken.Receiver}>
): @FungibleToken.Vault
pub fun getDetails(): DropDetails
}

pub resource Drop: DropPublic {
Expand Down Expand Up @@ -128,6 +137,10 @@ pub contract FlowtyDrops {
return <- self.phases.remove(at: index)
}

pub fun getDetails(): DropDetails {
return self.details
}

init(details: DropDetails, minterCap: Capability<&{Minter}>, phases: @[Phase]) {
pre {
minterCap.check(): "minter capability is not valid"
Expand Down Expand Up @@ -283,14 +296,30 @@ pub contract FlowtyDrops {

pub resource interface ContainerPublic {
pub fun borrowDropPublic(id: UInt64): &{DropPublic}?
pub fun getIDs(): [UInt64]
}

// Contains drops.
pub resource Container {
pub resource Container: ContainerPublic {
pub let drops: @{UInt64: Drop}

pub fun addDrop(_ drop: @Drop) {
// TODO: emit DropAdded event
let details = drop.getDetails()

let phases = drop.borrowAllPhases()
assert(phases.length > 0, message: "drops must have at least one phase to be added to a container")

let firstPhaseDetails = phases[0].getDetails()

emit DropAdded(
address: self.owner!.address,
id: drop.uuid,
name: details.display.name,
description: details.display.description,
imageUrl: details.display.thumbnail.uri(),
start: firstPhaseDetails.switch.getStart(),
end: firstPhaseDetails.switch.getEnd()
)
destroy self.drops.insert(key: drop.uuid, <-drop)
}

Expand All @@ -310,6 +339,10 @@ pub contract FlowtyDrops {
return &self.drops[id] as &{DropPublic}?
}

pub fun getIDs(): [UInt64] {
return self.drops.keys
}

init() {
self.drops <- {}
}
Expand All @@ -334,8 +367,12 @@ pub contract FlowtyDrops {
init() {
let identifier = "FlowtyDrops_".concat(self.account.address.toString())
let containerIdentifier = identifier.concat("_Container")
let minterIdentifier = identifier.concat("_Minter")

self.ContainerStoragePath = StoragePath(identifier: containerIdentifier)!
self.ContainerPublicPath = PublicPath(identifier: containerIdentifier)!

self.MinterPrivatePath = PrivatePath(identifier: minterIdentifier)!
self.MinterStoragePath = StoragePath(identifier: minterIdentifier)!
}
}
20 changes: 3 additions & 17 deletions contracts/nft/OpenEditionNFT.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ pub contract OpenEditionNFT: NonFungibleToken, ViewResolver {
/// Storage and Public Paths
pub let CollectionStoragePath: StoragePath
pub let CollectionPublicPath: PublicPath
pub let MinterStoragePath: StoragePath
pub let MinterPrivatePath: PrivatePath

/// The core resource that represents a Non Fungible Token.
/// New instances will be created using the NFTMinter resource
Expand Down Expand Up @@ -259,7 +257,7 @@ pub contract OpenEditionNFT: NonFungibleToken, ViewResolver {
}
)
case Type<FlowtyDrops.DropResolver>():
return FlowtyDrops.DropResolver(cap: OpenEditionNFT.account.getCapability<&{FlowtyDrops.ContainerPublic}>(OpenEditionNFT.MinterPrivatePath))
return FlowtyDrops.DropResolver(cap: OpenEditionNFT.account.getCapability<&{FlowtyDrops.ContainerPublic}>(FlowtyDrops.ContainerPublicPath))
}
return nil
}
Expand All @@ -283,8 +281,6 @@ pub contract OpenEditionNFT: NonFungibleToken, ViewResolver {
// Set the named paths
self.CollectionStoragePath = /storage/openEditionNFT
self.CollectionPublicPath = /public/openEditionNFT
self.MinterStoragePath = /storage/openEditionMinter
self.MinterPrivatePath = /private/openEditionMinter

// Create a Collection resource and save it to storage
let collection <- create Collection()
Expand All @@ -298,20 +294,10 @@ pub contract OpenEditionNFT: NonFungibleToken, ViewResolver {

// Create a Minter resource and save it to storage
let minter <- create NFTMinter()
self.account.save(<-minter, to: self.MinterStoragePath)
let minterCap = self.account.link<&NFTMinter{FlowtyDrops.Minter}>(self.MinterPrivatePath, target: self.MinterStoragePath)
self.account.save(<-minter, to: FlowtyDrops.MinterStoragePath)
let minterCap = self.account.link<&NFTMinter{FlowtyDrops.Minter}>(FlowtyDrops.MinterPrivatePath, target: FlowtyDrops.MinterStoragePath)
?? panic("unable to link minter capability")

emit ContractInitialized()

let dropDisplay = MetadataViews.Display(
name: "Sample Open Edition",
description: "This is a sample Open Edition NFT Drop utilizing flowty drops for minting",
thumbnail: MetadataViews.IPFSFile(cid: "QmNtDmxuyBeA6YJht3ADMJCCqLoG3SPf3S7DYavZTeFUy7", path: nil)
)
let drop <- DropFactory.createEndlessOpenEditionDrop(price: 1.0, paymentTokenType: Type<@FlowToken.Vault>(), dropDisplay: dropDisplay, minterCap: minterCap)
let container <- FlowtyDrops.createContainer()
container.addDrop(<- drop)
self.account.save(<-container, to: FlowtyDrops.ContainerStoragePath)
}
}
17 changes: 17 additions & 0 deletions scripts/get_drop_details.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import "FlowtyDrops"
import "ViewResolver"

pub fun main(contractAddress: Address, contractName: String, dropID: UInt64): FlowtyDrops.DropDetails {
let resolver = getAccount(contractAddress).contracts.borrow<&ViewResolver>(name: contractName)
?? panic("contract does not implement ViewResolver interface")

let dropResolver = resolver.resolveView(Type<FlowtyDrops.DropResolver>())! as! FlowtyDrops.DropResolver

let container = dropResolver.borrowContainer()
?? panic("drop container not found")

let drop = container.borrowDropPublic(id: dropID)
?? panic("drop not found")

return drop.getDetails()
}
22 changes: 22 additions & 0 deletions scripts/get_drop_ids.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import "FlowtyDrops"
import "ViewResolver"

pub fun main(contractAddress: Address, contractName: String): [UInt64] {
let resolver = getAccount(contractAddress).contracts.borrow<&ViewResolver>(name: contractName)
if resolver == nil {
return []
}

let tmp = resolver!.resolveView(Type<FlowtyDrops.DropResolver>())
if tmp == nil {
return []
}

let dropResolver = tmp! as! FlowtyDrops.DropResolver

if let dropContainer = dropResolver.borrowContainer() {
return dropContainer.getIDs()
}

return []
}
20 changes: 20 additions & 0 deletions scripts/get_price_at_phase.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import "FlowtyDrops"
import "ViewResolver"

pub fun main(contractAddress: Address, contractName: String, dropID: UInt64, phaseIndex: Int, minter: Address, numToMint: Int, paymentIdentifier: String): UFix64? {
let paymentTokenType = CompositeType(paymentIdentifier)!

let resolver = getAccount(contractAddress).contracts.borrow<&ViewResolver>(name: contractName)
?? panic("contract does not implement ViewResolver interface")

let dropResolver = resolver.resolveView(Type<FlowtyDrops.DropResolver>())! as! FlowtyDrops.DropResolver

let container = dropResolver.borrowContainer()
?? panic("drop container not found")

let drop = container.borrowDropPublic(id: dropID)
?? panic("drop not found")

let phase = drop.borrowPhase(index: phaseIndex)
return phase.getDetails().pricer.getPrice(num: numToMint, paymentTokenType: paymentTokenType, minter: minter)
}
52 changes: 52 additions & 0 deletions tests/FlowtyDrops_tests.cdc
Original file line number Diff line number Diff line change
@@ -1,11 +1,63 @@
import Test
import "test_helpers.cdc"
import "FlowToken"
import "FlowtyDrops"

pub let defaultEndlessOpenEditionName = "Default Endless Open Edition"

pub fun setup() {
deployAll()
}

pub fun afterEach() {
txExecutor("drops/remove_all_drops.cdc", [openEditionAccount], [], nil, nil)
}

pub fun testImports() {
Test.assert(scriptExecutor("import_all.cdc", [])! as! Bool, message: "failed to import all")
}

pub fun test_OpenEditionNFT_getPrice() {
let minter = Test.createAccount()

let dropID = createDefaultEndlessOpenEditionDrop()
let price = getPriceAtPhase(
contractAddress: openEditionAccount.address, contractName: "OpenEditionNFT", dropID: dropID, phaseIndex: 0, minter: minter.address, numToMint: 1, paymentIdentifier: Type<@FlowToken.Vault>().identifier
)
Test.assertEqual(1.0, price)

let priceMultiple = getPriceAtPhase(
contractAddress: openEditionAccount.address, contractName: "OpenEditionNFT", dropID: dropID, phaseIndex: 0, minter: minter.address, numToMint: 10, paymentIdentifier: Type<@FlowToken.Vault>().identifier
)
Test.assertEqual(10.0, priceMultiple)
}

pub fun test_OpenEditionNFT_getDetails() {
let dropID = createDefaultEndlessOpenEditionDrop()
let details = getDropDetails(contractAddress: openEditionAccount.address, contractName: "OpenEditionNFT", dropID: dropID)
Test.assertEqual(details.display.name, defaultEndlessOpenEditionName)
}

// ------------------------------------------------------------------------
// Helper functions section
pub fun createDefaultEndlessOpenEditionDrop(): UInt64 {
return createEndlessOpenEditionDrop(
acct: openEditionAccount,
name: "Default Endless Open Edition",
description: "This is a placeholder description",
ipfsCid: "1234",
ipfsPath: nil,
price: 1.0,
paymentIdentifier: Type<@FlowToken.Vault>().identifier,
minterPrivatePath: FlowtyDrops.MinterPrivatePath
)
}

pub fun getPriceAtPhase(contractAddress: Address, contractName: String, dropID: UInt64, phaseIndex: Int, minter: Address, numToMint: Int, paymentIdentifier: String): UFix64 {
return scriptExecutor("get_price_at_phase.cdc", [contractAddress, contractName, dropID, phaseIndex, minter, numToMint, paymentIdentifier])! as! UFix64
}

pub fun getDropDetails(contractAddress: Address, contractName: String, dropID: UInt64): FlowtyDrops.DropDetails {
return scriptExecutor("get_drop_details.cdc", [contractAddress, contractName, dropID])! as! FlowtyDrops.DropDetails
}
59 changes: 58 additions & 1 deletion tests/test_helpers.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Test

import "NonFungibleToken"
import "FlowToken"
import "FlowtyDrops"

// Helper functions. All of the following were taken from
// https://github.com/onflow/Offers/blob/fd380659f0836e5ce401aa99a2975166b2da5cb0/lib/cadence/test/Offers.cdc
Expand Down Expand Up @@ -145,7 +146,8 @@ pub let Account0xd = Address(0x000000000000000d)
pub let Account0xe = Address(0x000000000000000e)

pub let serviceAccount = Test.getAccount(Account0x5)
pub let dropsAccount = Test.getAccount(Account0x5)
pub let dropsAccount = Test.getAccount(Account0x6)
pub let openEditionAccount = Test.getAccount(Account0x7)

// Flow Token constants
pub let flowTokenStoragePath = /storage/flowTokenVault
Expand Down Expand Up @@ -173,4 +175,59 @@ pub fun heartbeat() {

pub fun getCurrentTime(): UFix64 {
return scriptExecutor("util/get_current_time.cdc", [])! as! UFix64
}

pub fun mintFromDrop(
minter: Test.Account,
contractAddress: Address,
contractName: String,
numToMint: Int,
totalCost: UFix64,
paymentIdentifier: String,
paymentStoragePath: StoragePath,
paymentReceiverPath: PublicPath,
dropID: UInt64,
dropPhaseIndex: Int,
nftIdentifier: String,
commissionReceiver: Address
) {
let args = [
contractAddress,
contractName,
numToMint,
totalCost,
paymentIdentifier,
paymentStoragePath,
paymentReceiverPath,
dropID,
dropPhaseIndex,
nftIdentifier,
commissionReceiver
]
txExecutor("drops/mint.cdc", [minter], args, nil, nil)
}

pub fun getDropIDs(
contractAddress: Address,
contractName: String
): [UInt64] {
return scriptExecutor("get_drop_ids.cdc", [contractAddress, contractName])! as! [UInt64]
}

pub fun createEndlessOpenEditionDrop(
acct: Test.Account,
name: String,
description: String,
ipfsCid: String,
ipfsPath: String?,
price: UFix64,
paymentIdentifier: String,
minterPrivatePath: PrivatePath
): UInt64 {
txExecutor("drops/add_endless_open_edition.cdc", [acct], [
name, description, ipfsCid, ipfsPath, price, paymentIdentifier, minterPrivatePath
], nil, nil)

let e = Test.eventsOfType(Type<FlowtyDrops.DropAdded>()).removeLast() as! FlowtyDrops.DropAdded
return e.id
}
Loading

0 comments on commit b718159

Please sign in to comment.