Skip to content

Commit

Permalink
Merge branch 'jer/fast-break' of github.com:dapperlabs/nba-smart-cont…
Browse files Browse the repository at this point in the history
…racts into jer/fast-break
  • Loading branch information
HouseOfHufflepuff committed Dec 11, 2023
2 parents 848338e + ef5be2c commit d1ec7c1
Show file tree
Hide file tree
Showing 51 changed files with 3,685 additions and 2,109 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @joshuahannan
*.cdc @dapperlabs/flow-smart-contracts
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-go@v1
with:
go-version: '1.16'
go-version: '1.18'
- run: make ci
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.idea
flow.json
flow.json
/imports
.vscode
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ smart contract programming language designed for the Flow Blockchain.

### What is NBA Top Shot

NBA Top Shot is the official digital collecitibles
NBA Top Shot is the official digital collectibles
game for the National Basketball Association. Players collect and trade
digital collectibles that represent highlights from the best players
in the world. See more at nbatopshot.com
Expand Down Expand Up @@ -89,11 +89,11 @@ The directories here are organized into contracts, scripts, and transactions.
Contracts contain the source code for the Top Shot contracts that are deployed to Flow.

Scripts contain read-only transactions to get information about
the state of someones Collection or about the state of the TopShot contract.
the state of someone's Collection or about the state of the TopShot contract.

Transactions contain the transactions that various admins and users can use
to perform actions in the smart contract like creating plays and sets,
minting Moments, and transfering Moments.
minting Moments, and transferring Moments.

- `contracts/` : Where the Top Shot related smart contracts live.
- `transactions/` : This directory contains all the transactions and scripts
Expand Down Expand Up @@ -137,7 +137,7 @@ pub resource Moment {
pub let playID: UInt32
// the place in the edition that this Moment was minted
// Otherwise know as the serial number
// Otherwise known as the serial number
pub let serialNumber: UInt32
}
```
Expand All @@ -162,7 +162,7 @@ The other types that are defined in `TopShot` are as follows:
withdraw and deposit from this collection and get information about the
contained Moments.
- `Admin`: This is a resource type that can be used by admins to perform
various acitions in the smart contract like starting a new series,
various actions in the smart contract like starting a new series,
creating a new play or set, and getting a reference to an existing set.
- `QuerySetData`: A struct that contains the metadata associated with a set.
This is currently the only way to access the metadata of a set.
Expand Down
550 changes: 539 additions & 11 deletions contracts/TopShot.cdc

Large diffs are not rendered by default.

28 changes: 27 additions & 1 deletion contracts/TopShotLocking.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ pub contract TopShotLocking {
return self.lockedNFTs.length
}

// The path to the TopShotLocking Admin resource belonging to the Account
// which the contract is deployed on
pub fun AdminStoragePath() : StoragePath { return /storage/TopShotLockingAdmin}

// Admin is a special authorization resource that
// allows the owner to override the lock on a moment
//
Expand All @@ -141,6 +145,28 @@ pub contract TopShotLocking {
TopShotLocking.unlockableNFTs[nftRef.id] = true
}

pub fun unlockByID(id: UInt64) {
if !TopShotLocking.lockedNFTs.containsKey(id) {
// nft is not locked, do nothing
return
}
TopShotLocking.lockedNFTs.remove(key: id)
emit MomentUnlocked(id: id)
}

// admin may alter the expiry of a lock on an NFT
pub fun setLockExpiryByID(id: UInt64, expiryTimestamp: UFix64) {
if expiryTimestamp < getCurrentBlock().timestamp {
panic("cannot set expiry in the past")
}

let duration = expiryTimestamp - getCurrentBlock().timestamp

TopShotLocking.lockedNFTs[id] = expiryTimestamp

emit MomentLocked(id: id, duration: duration, expiryTimestamp: expiryTimestamp)
}

// unlocks all NFTs
pub fun unlockAll() {
TopShotLocking.lockedNFTs = {}
Expand All @@ -160,6 +186,6 @@ pub contract TopShotLocking {
let admin <- create Admin()

// Store it in private account storage in `init` so only the admin can use it
self.account.save(<-admin, to: /storage/TopShotLockingAdmin)
self.account.save(<-admin, to: TopShotLocking.AdminStoragePath())
}
}
25 changes: 13 additions & 12 deletions contracts/TopShotMarketV3.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import TopShot from 0xTOPSHOTADDRESS
import Market from 0xMARKETADDRESS
import DapperUtilityCoin from 0xDUCADDRESS
import TopShotLocking from 0xTOPSHOTLOCKINGADDRESS
import MetadataViews from 0xMETADATAVIEWSADDRESS

pub contract TopShotMarketV3 {

Expand All @@ -53,7 +54,7 @@ pub contract TopShotMarketV3 {
/// emitted when the price of a listed moment has changed
pub event MomentPriceChanged(id: UInt64, newPrice: UFix64, seller: Address?)
/// emitted when a token is purchased from the market
pub event MomentPurchased(id: UInt64, price: UFix64, seller: Address?)
pub event MomentPurchased(id: UInt64, price: UFix64, seller: Address?, momentName: String, momentDescription: String, momentThumbnailURL: String)
/// emitted when a moment has been withdrawn from the sale
pub event MomentWithdrawn(id: UInt64, owner: Address?)

Expand Down Expand Up @@ -218,26 +219,25 @@ pub contract TopShotMarketV3 {
self.ownerCapability.borrow()!
.deposit(from: <-buyTokens)

emit MomentPurchased(id: tokenID, price: price, seller: self.owner?.address)

// Return the purchased token
let boughtMoment <- self.ownerCollection.borrow()!.withdraw(withdrawID: tokenID) as! @TopShot.NFT

let momentDisplay = boughtMoment.resolveView(Type<MetadataViews.Display>())! as! MetadataViews.Display
emit MomentPurchased(id: tokenID, price: price, seller: self.owner?.address, momentName: momentDisplay.name, momentDescription: momentDisplay.description, momentThumbnailURL: momentDisplay.thumbnail.uri())

return <-boughtMoment

// If not found in this SaleCollection, check V1
} else {
if let v1Market = self.marketV1Capability {
let v1MarketRef = v1Market.borrow()!
} else if let v1Market = self.marketV1Capability {
let v1MarketRef = v1Market.borrow()!

return <-v1MarketRef.purchase(tokenID: tokenID, buyTokens: <-buyTokens)
} else {
panic("No token matching this ID for sale!")
}
}
return <-v1MarketRef.purchase(tokenID: tokenID, buyTokens: <-buyTokens)
}

destroy buyTokens // This line can be removed when this issue is released: https://github.com/onflow/cadence/pull/1000
// Refactored to avoid dead code to resolve
// https://github.com/dapperlabs/nba-smart-contracts/issues/165
panic("No token matching this ID for sale!")

}

/// changeOwnerReceiver updates the capability for the sellers fungible token Vault
Expand Down Expand Up @@ -333,3 +333,4 @@ pub contract TopShotMarketV3 {
self.marketPublicPath = /public/topshotSalev3Collection
}
}

15 changes: 15 additions & 0 deletions contracts/TopShotShardedCollection.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,21 @@ pub contract TopShotShardedCollection {
return ids
}

// Safe way to borrow a reference to an NFT that does not panic
// Also now part of the NonFungibleToken.CollectionPublic interface
//
// Parameters: id: The ID of the NFT to get the reference for
//
// Returns: An optional reference to the desired NFT, will be nil if the passed ID does not exist
pub fun borrowNFTSafe(id: UInt64): &NonFungibleToken.NFT? {

// Get the bucket of the nft to be borrowed
let bucket = id % self.numBuckets

// Find NFT in the collections and borrow a reference
return self.collections[bucket]?.borrowNFTSafe(id: id) ?? nil
}

// borrowNFT Returns a borrowed reference to a Moment in the Collection
// so that the caller can read data and call methods from it
pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
Expand Down
19 changes: 14 additions & 5 deletions lib/go/contracts/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,29 @@ const (
defaultMarketAddress = "MARKETADDRESS"
defaultMetadataviewsAddress = "METADATAVIEWSADDRESS"
defaultTopShotLockingAddress = "TOPSHOTLOCKINGADDRESS"
defaultTopShotRoyaltyAddress = "TOPSHOTROYALTYADDRESS"
defaultNetwork = "${NETWORK}"
)

// GenerateTopShotContract returns a copy
// of the topshot contract with the import addresses updated
func GenerateTopShotContract(nftAddr string, metadataViewsAddr string, topShotLockingAddr string) []byte {
func GenerateTopShotContract(ftAddr string, nftAddr string, metadataViewsAddr string, topShotLockingAddr string, royaltyAddr string, network string) []byte {

topShotCode := assets.MustAssetString(topshotFile)

codeWithNFTAddr := strings.ReplaceAll(topShotCode, defaultNonFungibleTokenAddress, nftAddr)
codeWithFTAddr := strings.ReplaceAll(topShotCode, defaultFungibleTokenAddress, ftAddr)

codeWithNFTAddr := strings.ReplaceAll(codeWithFTAddr, defaultNonFungibleTokenAddress, nftAddr)

codeWithMetadataViewsAddr := strings.ReplaceAll(codeWithNFTAddr, defaultMetadataviewsAddress, metadataViewsAddr)

codeWithTopShotLockingAddr := strings.ReplaceAll(codeWithMetadataViewsAddr, defaultTopShotLockingAddress, topShotLockingAddr)

return []byte(codeWithTopShotLockingAddr)
codeWithTopShotRoyaltyAddr := strings.ReplaceAll(codeWithTopShotLockingAddr, defaultTopShotRoyaltyAddress, royaltyAddr)

codeWithNetwork := strings.ReplaceAll(codeWithTopShotRoyaltyAddr, defaultNetwork, network)

return []byte(codeWithNetwork)
}

// GenerateTopShotShardedCollectionContract returns a copy
Expand Down Expand Up @@ -79,7 +87,7 @@ func GenerateTopShotMarketContract(ftAddr, nftAddr, topshotAddr, ducTokenAddr st

// GenerateTopShotMarketV3Contract returns a copy
// of the third version TopShotMarketContract with the import addresses updated
func GenerateTopShotMarketV3Contract(ftAddr, nftAddr, topshotAddr, marketAddr, ducTokenAddr string, topShotLockingAddr string) []byte {
func GenerateTopShotMarketV3Contract(ftAddr, nftAddr, topshotAddr, marketAddr, ducTokenAddr, topShotLockingAddr, metadataViewsAddr string) []byte {

marketCode := assets.MustAssetString(marketV3File)
codeWithNFTAddr := strings.ReplaceAll(marketCode, defaultNonFungibleTokenAddress, nftAddr)
Expand All @@ -88,8 +96,9 @@ func GenerateTopShotMarketV3Contract(ftAddr, nftAddr, topshotAddr, marketAddr, d
codeWithMarketV3Addr := strings.ReplaceAll(codeWithFTAddr, defaultMarketAddress, marketAddr)
codeWithTokenAddr := strings.ReplaceAll(codeWithMarketV3Addr, "DUCADDRESS", ducTokenAddr)
codeWithTopShotLockingAddr := strings.ReplaceAll(codeWithTokenAddr, defaultTopShotLockingAddress, topShotLockingAddr)
codeWithMetadataViewAddr := strings.ReplaceAll(codeWithTopShotLockingAddr, defaultMetadataviewsAddress, metadataViewsAddr)

return []byte(codeWithTopShotLockingAddr)
return []byte(codeWithMetadataViewAddr)
}

// GenerateTopShotLockingContract returns a copy
Expand Down
6 changes: 4 additions & 2 deletions lib/go/contracts/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ var addrC = "0C"
var addrD = "0D"
var addrE = "0E"
var addrF = "0F"
var addrG = "0G"
var network = "mainnet"

func TestTopShotContract(t *testing.T) {
contract := contracts.GenerateTopShotContract(addrA, addrA, addrA)
contract := contracts.GenerateTopShotContract(addrA, addrA, addrA, addrA, addrA, network)
assert.NotNil(t, contract)
}

Expand All @@ -39,7 +41,7 @@ func TestTopShotMarketContract(t *testing.T) {
}

func TestTopShotMarketV3Contract(t *testing.T) {
contract := contracts.GenerateTopShotMarketV3Contract(addrA, addrB, addrC, addrD, addrE, addrF)
contract := contracts.GenerateTopShotMarketV3Contract(addrA, addrB, addrC, addrD, addrE, addrF, addrG)
assert.NotNil(t, contract)
assert.Contains(t, string(contract), addrA)
}
11 changes: 7 additions & 4 deletions lib/go/contracts/go.mod
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
module github.com/dapperlabs/nba-smart-contracts/lib/go/contracts

go 1.16
go 1.18

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kevinburke/go-bindata v3.22.0+incompatible
github.com/stretchr/testify v1.7.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/stretchr/testify v1.7.1
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect

)

replace github.com/dapperlabs/nba-smart-contracts/lib/go/contracts => ../contracts
24 changes: 12 additions & 12 deletions lib/go/contracts/internal/assets/assets.go

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions lib/go/events/moment.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type MomentMintedEvent interface {
PlayId() uint32
SetId() uint32
SerialNumber() uint32
SubeditionId() uint32
}

type momentMintedEvent cadence.Event
Expand All @@ -37,6 +38,13 @@ func (evt momentMintedEvent) SerialNumber() uint32 {
return uint32(evt.Fields[3].(cadence.UInt32))
}

func (evt momentMintedEvent) SubeditionId() uint32 {
if len(evt.Fields) < 5 {
return 0
}
return uint32(evt.Fields[4].(cadence.UInt32))
}

func (evt momentMintedEvent) validate() error {
if evt.EventType.QualifiedIdentifier != EventMomentMinted {
return fmt.Errorf("error validating event: event is not a valid moment minted event, expected type %s, got %s",
Expand Down
55 changes: 55 additions & 0 deletions lib/go/events/moment_locked.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package events

import (
"fmt"
"github.com/onflow/cadence"
jsoncdc "github.com/onflow/cadence/encoding/json"
)

var (
MomentLocked = "TopShotLocking.MomentLocked"
)

type MomentLockedEvent interface {
FlowID() uint64
Duration() cadence.UFix64
ExpiryTimestamp() cadence.UFix64
}

type momentLockedEvent cadence.Event

func (evt momentLockedEvent) FlowID() uint64 {
return uint64(evt.Fields[0].(cadence.UInt64))
}

func (evt momentLockedEvent) Duration() cadence.UFix64 {
return evt.Fields[1].(cadence.UFix64)
}

func (evt momentLockedEvent) ExpiryTimestamp() cadence.UFix64 {
return evt.Fields[2].(cadence.UFix64)
}

func (evt momentLockedEvent) validate() error {
if evt.EventType.QualifiedIdentifier != MomentLocked {
return fmt.Errorf("error validating event: event is not a valid moment locked event, expected type %s, got %s",
MomentLocked, evt.EventType.QualifiedIdentifier)
}
return nil
}

var _ MomentLockedEvent = (*momentLockedEvent)(nil)

func DecodeMomentLockedEvent(b []byte) (MomentLockedEvent, error) {
value, err := jsoncdc.Decode(nil, b)
if err != nil {
return nil, err
}

event := momentLockedEvent(value.(cadence.Event))
if err := event.validate(); err != nil {
return nil, fmt.Errorf("error decoding event: %w", err)
}

return event, nil
}
Loading

0 comments on commit d1ec7c1

Please sign in to comment.