Skip to content

Commit

Permalink
add some comments (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
austinkline authored Sep 23, 2024
1 parent 1e0964a commit 32eafee
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 12 deletions.
24 changes: 21 additions & 3 deletions contracts/FlowtyDrops.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import "AddressUtils"
import "FungibleTokenMetadataViews"
import "FungibleTokenRouter"

// FlowtyDrops is a contract to help collections manage their primary sale needs on flow.
// Multiple drops can be made for a single contract (like how TopShot has had lots of pack drops),
// and can be split into phases to represent different behaviors over the course of a drop
access(all) contract FlowtyDrops {
access(all) let ContainerStoragePath: StoragePath
access(all) let ContainerPublicPath: PublicPath

access(all) event DropAdded(address: Address, id: UInt64, name: String, description: String, imageUrl: String, start: UInt64?, end: UInt64?, nftType: String)
access(all) event DropRemoved(address: Address, id: UInt64)
access(all) event Minted(address: Address, dropID: UInt64, phaseID: UInt64, nftID: UInt64, nftType: String)
access(all) event PhaseAdded(dropID: UInt64, dropAddress: Address, id: UInt64, index: Int, activeCheckerType: String, pricerType: String, addressVerifierType: String)
access(all) event PhaseRemoved(dropID: UInt64, dropAddress: Address, id: UInt64)
Expand Down Expand Up @@ -75,6 +79,8 @@ access(all) contract FlowtyDrops {
}
}

// The primary resource of this contract. A drop has some top-level details, and some phase-specific details which are encapsulated
// by each phase.
access(all) resource Drop: DropPublic {
access(all) event ResourceDestroyed(
uuid: UInt64 = self.uuid,
Expand All @@ -87,8 +93,11 @@ access(all) contract FlowtyDrops {
access(self) let phases: @[Phase]
// the details of a drop. This includes things like display information and total number of mints
access(self) let details: DropDetails
// capability to mint nfts with. Regardless of where a drop is hosted, the minter itself is what is responsible for creating nfts
// and is used by the drop's mint method.
access(self) let minterCap: Capability<&{Minter}>

// general-purpose property bags which are needed to ensure extensibility of this resource
access(all) let data: {String: AnyStruct}
access(all) let resources: @{String: AnyResource}

Expand Down Expand Up @@ -116,6 +125,7 @@ access(all) contract FlowtyDrops {
)

let paymentAmount = phase.details.pricer.getPrice(num: amount, paymentTokenType: payment.getType(), minter: receiverCap.address)
assert(payment.balance >= paymentAmount, message: "payment balance is lower than payment amount")
let withdrawn <- payment.withdraw(amount: paymentAmount) // make sure that we have a fresh vault resource
// take commission
Expand All @@ -124,13 +134,14 @@ access(all) contract FlowtyDrops {
commissionReceiver!.borrow()!.deposit(from: <-commission)
}

assert(phase.details.pricer.getPrice(num: amount, paymentTokenType: withdrawn.getType(), minter: receiverCap.address) * (1.0 - self.details.commissionRate) == withdrawn.balance, message: "incorrect payment amount")
// The balance of the payment sent to the creator is equal to the paymentAmount - fees
assert(paymentAmount * (1.0 - self.details.commissionRate) == withdrawn.balance, message: "incorrect payment amount")
assert(phase.details.pricer.getPaymentTypes().contains(withdrawn.getType()), message: "unsupported payment type")
assert(phase.details.activeChecker.hasStarted() && !phase.details.activeChecker.hasEnded(), message: "phase is not active")

// mint the nfts
let minter = self.minterCap.borrow() ?? panic("minter capability could not be borrowed")
let mintedNFTs: @[{NonFungibleToken.NFT}] <- minter.mint(payment: <-withdrawn, amount: amount, phase: phase, data: data)
assert(phase.details.activeChecker.hasStarted() && !phase.details.activeChecker.hasEnded(), message: "phase is not active")
assert(mintedNFTs.length == amount, message: "incorrect number of items returned")

// distribute to receiver
Expand Down Expand Up @@ -319,6 +330,7 @@ access(all) contract FlowtyDrops {
}
}

// The AddressVerifier interface is responsible for determining whether an address is permitted to mint or not
access(all) struct interface AddressVerifier {
access(all) fun canMint(addr: Address, num: Int, totalMinted: Int, data: {String: AnyStruct}): Bool {
return true
Expand All @@ -333,12 +345,15 @@ access(all) contract FlowtyDrops {
}
}

// The pricer interface is responsible for the cost of a mint. It can vary by phase
access(all) struct interface Pricer {
access(all) fun getPrice(num: Int, paymentTokenType: Type, minter: Address?): UFix64
access(all) fun getPaymentTypes(): [Type]
}

access(all) resource interface Minter {
// mint is only able to be called either by this contract (FlowtyDrops) or the implementing contract.
// In its default implementation, it is assumed that the receiver capability for payment is the FungibleTokenRouter
access(contract) fun mint(payment: @{FungibleToken.Vault}, amount: Int, phase: &FlowtyDrops.Phase, data: {String: AnyStruct}): @[{NonFungibleToken.NFT}] {
let resourceAddress = AddressUtils.parseAddress(self.getType())!
let receiver = getAccount(resourceAddress).capabilities.get<&{FungibleToken.Receiver}>(FungibleTokenRouter.PublicPath).borrow()
Expand All @@ -356,9 +371,11 @@ access(all) contract FlowtyDrops {
return <- nfts
}

// required so that the minter interface has a way to create NFTs on its implementing resource
access(contract) fun createNextNFT(): @{NonFungibleToken.NFT}
}

// Struct to wrap obtaining a Drop container. Intended for use with the ViewResolver contract interface
access(all) struct DropResolver {
access(self) let cap: Capability<&{ContainerPublic}>

Expand All @@ -380,7 +397,7 @@ access(all) contract FlowtyDrops {
access(all) fun getIDs(): [UInt64]
}

// Contains drops.
// Container holds drops so that one address can host more than one drop at once
access(all) resource Container: ContainerPublic {
access(self) let drops: @{UInt64: Drop}

Expand Down Expand Up @@ -413,6 +430,7 @@ access(all) contract FlowtyDrops {
self.drops.containsKey(id): "drop was not found"
}

emit DropRemoved(address: self.owner!.address, id: id)
return <- self.drops.remove(key: id)!
}

Expand Down
7 changes: 7 additions & 0 deletions contracts/nft/BaseNFT.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@ access(all) contract interface BaseNFT: ViewResolver {
]
}

// In case the implementor of `NFT` wants to override resolved views,
// the actual logic to perform view resolution is done in another method,
// with this one calling directly into it.
access(all) fun resolveView(_ view: Type): AnyStruct? {
return self._resolveView(view)
}

access(all) fun _resolveView(_ view: Type): AnyStruct? {
if view == Type<MetadataViews.Serial>() {
return MetadataViews.Serial(self.id)
}
Expand Down
1 change: 1 addition & 0 deletions contracts/nft/NFTMetadata.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ access(all) contract NFTMetadata {
access(Owner) fun addMetadata(id: UInt64, data: Metadata) {
pre {
self.metadata[id] == nil: "id already has metadata assigned"
!self.frozen: "metadata is frozen and cannot be updated"
}

self.metadata[id] = data
Expand Down
5 changes: 1 addition & 4 deletions tests/FlowtyDrops_tests.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ access(all) fun test_OpenEditionNFT_mint() {
paymentReceiverPath: flowTokenReceiverPath,
dropID: dropID,
dropPhaseIndex: 0,
nftIdentifier: Type<@OpenEditionNFT.NFT>().identifier,
commissionAddress: flowtyDropsAccount.address
)

Expand Down Expand Up @@ -164,7 +163,6 @@ access(all) fun test_OpenEditionNFT_getDropSummary() {
paymentReceiverPath: flowTokenReceiverPath,
dropID: dropID,
dropPhaseIndex: 0,
nftIdentifier: Type<@OpenEditionNFT.NFT>().identifier,
commissionAddress: flowtyDropsAccount.address
)

Expand Down Expand Up @@ -246,11 +244,10 @@ access(all) fun mintDrop(
paymentReceiverPath: PublicPath,
dropID: UInt64,
dropPhaseIndex: Int,
nftIdentifier: String,
commissionAddress: Address
): [UInt64] {
txExecutor("drops/mint.cdc", [minter], [
nftTypeIdentifier, numToMint, totalCost, paymentIdentifier, paymentStoragePath, paymentReceiverPath, dropID, dropPhaseIndex, nftIdentifier, commissionAddress
nftTypeIdentifier, numToMint, totalCost, paymentIdentifier, paymentStoragePath, paymentReceiverPath, dropID, dropPhaseIndex, commissionAddress
])

let ids: [UInt64] = []
Expand Down
1 change: 0 additions & 1 deletion transactions/contract-manager/setup.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ transaction(flowTokenAmount: UFix64) {
let tokens <- v.withdraw(amount: flowTokenAmount) as! @FlowToken.Vault

acct.storage.save(<- ContractManager.createManager(tokens: <-tokens, defaultRouterAddress: acct.address), to: ContractManager.StoragePath)

acct.storage.borrow<auth(ContractManager.Manage) &ContractManager.Manager>(from: ContractManager.StoragePath)!.onSave()
}
}
21 changes: 21 additions & 0 deletions transactions/contract-manager/transfer_ownership.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import "ContractManager"

transaction {
prepare(acct: auth(Storage, Capabilities) &Account, receiver: auth(Storage, Capabilities) &Account) {
if let cap = acct.capabilities.unpublish(ContractManager.PublicPath) {
acct.capabilities.storage.getController(byCapabilityID: cap.id)?.delete()
}

let manager <- acct.storage.load<@ContractManager.Manager>(from: ContractManager.StoragePath)
?? panic("manager not found")

receiver.storage.save(<- manager, to: ContractManager.StoragePath)
receiver.storage.borrow<auth(ContractManager.Manage) &ContractManager.Manager>(from: ContractManager.StoragePath)!.onSave()
receiver.capabilities.publish(
receiver.capabilities.storage.issue<&ContractManager.Manager>(ContractManager.StoragePath),
at: ContractManager.PublicPath
)

receiver.storage.borrow<auth(ContractManager.Manage) &ContractManager.Manager>(from: ContractManager.StoragePath)!.onSave()
}
}
5 changes: 1 addition & 4 deletions transactions/drops/mint.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ transaction(
paymentReceiverPath: PublicPath,
dropID: UInt64,
dropPhaseIndex: Int,
nftIdentifier: String,
commissionAddress: Address
) {
prepare(acct: auth(Capabilities, Storage) &Account) {
Expand All @@ -41,8 +40,6 @@ transaction(
let receiverCap = acct.capabilities.get<&{NonFungibleToken.CollectionPublic}>(collectionData.publicPath)
assert(receiverCap.check(), message: "invalid receiver capability")

let expectedNftType = CompositeType(nftIdentifier) ?? panic("invalid nft identifier")

let vault = acct.storage.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>(from: paymentStoragePath)
?? panic("could not borrow token provider")

Expand All @@ -59,7 +56,7 @@ transaction(
payment: <-paymentVault,
amount: numToMint,
phaseIndex: dropPhaseIndex,
expectedType: expectedNftType,
expectedType: nftType,
receiverCap: receiverCap,
commissionReceiver: commissionReceiver,
data: {}
Expand Down

0 comments on commit 32eafee

Please sign in to comment.