Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds more descriptive and helpful error messages to the NFT contracts and txs #233

Merged
merged 5 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,4 @@ For contributor use:

- [ ] Targeted PR against `master` branch
- [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
- [ ] Code follows the [standards mentioned here](https://github.com/onflow/flow-nft/blob/master/CONTRIBUTING.md#styleguides).
- [ ] Updated relevant documentation
- [ ] Re-reviewed `Files changed` in the Github PR explorer
- [ ] Added appropriate labels
<!-- Please follow the below standard to update the version in package.json
- Major if there is a new smart contract introduced.
- Major if there is a breaking change that is introduced in the existing contract.
- Minor if there is a new feature addition within the existing smart contracts.
- Patch if there is a non breaking change in the existing smart contracts.
-->
- [ ] Update the version in package.json when there is a change in the smart contracts
- [ ] Re-reviewed `Files changed` in the Github PR explorer
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Install Flow CLI
run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)"
- name: Flow CLI Version
run: flow-c1 version
run: flow version
- name: Update PATH
run: echo "/root/.local/bin" >> $GITHUB_PATH
- name: Run tests
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
test:
$(MAKE) generate -C lib/go
$(MAKE) test -C lib/go
flow-c1 test --cover --covercode="contracts" tests/*.cdc
flow test --cover --covercode="contracts" tests/*.cdc

.PHONY: ci
ci:
$(MAKE) ci -C lib/go
flow-c1 test --cover --covercode="contracts" tests/*.cdc
flow test --cover --covercode="contracts" tests/*.cdc
37 changes: 21 additions & 16 deletions contracts/ExampleNFT.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -133,22 +133,25 @@ access(all) contract ExampleNFT: NonFungibleToken {
resourceType: nil,
viewType: Type<MetadataViews.EVMBridgedMetadata>()
) as! MetadataViews.EVMBridgedMetadata?
?? panic("Could not resolve contract-level EVMBridgedMetadata")
// Compose the token-level URI based on a base URI and the token ID, pointing to a JSON file. This
// would be a file you've uploaded and are hosting somewhere - in this case HTTP, but this could be
// IPFS, S3, a data URL containing the JSON directly, etc.
let baseURI = "https://example-nft.onflow.org/token-metadata/"
let uriValue = self.id.toString().concat(".json")

return MetadataViews.EVMBridgedMetadata(
name: contractLevel.name,
symbol: contractLevel.symbol,
uri: MetadataViews.URI(
baseURI: baseURI, // defining baseURI results in a concatenation of baseURI and value
value: self.id.toString().concat(".json")
)
)

if let contractMetadata = contractLevel {
// Compose the token-level URI based on a base URI and the token ID, pointing to a JSON file. This
// would be a file you've uploaded and are hosting somewhere - in this case HTTP, but this could be
// IPFS, S3, a data URL containing the JSON directly, etc.
let baseURI = "https://example-nft.onflow.org/token-metadata/"
let uriValue = self.id.toString().concat(".json")

return MetadataViews.EVMBridgedMetadata(
name: contractMetadata.name,
symbol: contractMetadata.symbol,
uri: MetadataViews.URI(
baseURI: baseURI, // defining baseURI results in a concatenation of baseURI and value
value: self.id.toString().concat(".json")
)
)
} else {
return nil
}
}
return nil
}
Expand Down Expand Up @@ -182,7 +185,9 @@ access(all) contract ExampleNFT: NonFungibleToken {
/// withdraw removes an NFT from the collection and moves it to the caller
access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
let token <- self.ownedNFTs.remove(key: withdrawID)
?? panic("Could not withdraw an NFT with the provided ID from the collection")
?? panic("ExampleNFT.Collection.withdraw: Could not withdraw an NFT with the ID="
.concat(withdrawID.toString())
.concat(". Check the submitted ID to make sure it is one that this collection owns"))

return <-token
}
Expand Down
32 changes: 27 additions & 5 deletions contracts/MetadataViews.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,11 @@ access(all) contract MetadataViews {

view init(receiver: Capability<&{FungibleToken.Receiver}>, cut: UFix64, description: String) {
pre {
cut >= 0.0 && cut <= 1.0 : "Cut value should be in valid range i.e [0,1]"
cut >= 0.0 && cut <= 1.0 :
"MetadataViews.Royalty.init: The provided royalty cut value ("
.concat(cut.toString())
.concat(") is invalid.")
.concat(" It should be within the valid range between 0 and 1. i.e [0,1]")
}
self.receiver = receiver
self.cut = cut
Expand All @@ -301,7 +305,13 @@ access(all) contract MetadataViews {
for royalty in cutInfos {
totalCut = totalCut + royalty.cut
}
assert(totalCut <= 1.0, message: "Sum of cutInfos multipliers should not be greater than 1.0")
assert(
totalCut <= 1.0,
message:
"MetadataViews.Royalties.init: Sum of cutInfos multipliers ("
.concat(totalCut.toString())
.concat(") should not be greater than 1.0")
)
// Assign the cutInfos
self.cutInfos = cutInfos
}
Expand Down Expand Up @@ -454,7 +464,15 @@ access(all) contract MetadataViews {

view init(name: String?, number: UInt64, max: UInt64?) {
if max != nil {
assert(number <= max!, message: "The number cannot be greater than the max number!")
assert(
number <= max!,
message:
"MetadataViews.Edition.init: The provided edition number for the NFT ("
.concat(number.toString())
.concat(") cannot be greater than the max edition number (")
.concat(max!.toString())
.concat(")!")
)
}
self.name = name
self.number = number
Expand Down Expand Up @@ -535,7 +553,8 @@ access(all) contract MetadataViews {

view init(score: UFix64?, max: UFix64?, description: String?) {
if score == nil && description == nil {
panic("A Rarity needs to set score, description or both")
panic("MetadataViews.Rarity.init: The provided score and description are both `nil`."
.concat(" A Rarity needs to set score, description, or both"))
}

self.score = score
Expand Down Expand Up @@ -648,7 +667,10 @@ access(all) contract MetadataViews {
createEmptyCollectionFunction: fun(): @{NonFungibleToken.Collection}
) {
pre {
publicLinkedType.isSubtype(of: Type<&{NonFungibleToken.Collection}>()): "Public type must be a subtype of NonFungibleToken.Collection interface."
publicLinkedType.isSubtype(of: Type<&{NonFungibleToken.Collection}>()):
"MetadataViews.NFTCollectionData.init: The Public linked type <"
.concat(publicLinkedType.identifier)
.concat("> is incorrect. It must be a subtype of the NonFungibleToken.Collection interface.")
}
self.storagePath=storagePath
self.publicPath=publicPath
Expand Down
39 changes: 32 additions & 7 deletions contracts/NonFungibleToken.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,14 @@ access(all) contract interface NonFungibleToken: ViewResolver {
///
access(all) fun createEmptyCollection(): @{Collection} {
post {
result.getLength() == 0: "The created collection must be empty!"
result.isSupportedNFTType(type: self.getType()): "The created collection must support this NFT type"
result.getLength() == 0:
"NonFungibleToken.NFT.createEmptyCollection: The created collection has a non-zero length."
.concat(" A newly created collection must be empty!")
result.isSupportedNFTType(type: self.getType()):
"NonFungibleToken.NFT.createEmptyCollection: The created collection does not support NFTs of type="
.concat(self.getType().identifier)
.concat(" The collection must support NFTs of type=")
.concat(self.getType().identifier)
}
}

Expand Down Expand Up @@ -156,7 +162,12 @@ access(all) contract interface NonFungibleToken: ViewResolver {
///
access(Withdraw) fun withdraw(withdrawID: UInt64): @{NFT} {
post {
result.id == withdrawID: "The ID of the withdrawn token must be the same as the requested ID"
result.id == withdrawID:
"NonFungibleToken.Provider.withdraw: The ID of the withdrawn token ("
.concat(result.id.toString())
.concat(") must be the same as the requested ID (")
.concat(withdrawID.toString())
.concat(")")
emit Withdrawn(type: result.getType().identifier, id: result.id, uuid: result.uuid, from: self.owner?.address, providerUUID: self.uuid)
}
}
Expand Down Expand Up @@ -235,7 +246,12 @@ access(all) contract interface NonFungibleToken: ViewResolver {
access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
post {
(result == nil) || (result?.id == id):
"Cannot borrow NFT reference: The ID of the returned reference does not match the ID that was specified"
"NonFungibleToken.Collection.borrowNFT: Cannot borrow NFT reference: "
.concat("The ID of the returned reference (")
.concat(result!.id.toString())
.concat(") does not match the ID that was specified (")
.concat(id.toString())
.concat(")")
}
}

Expand All @@ -244,8 +260,15 @@ access(all) contract interface NonFungibleToken: ViewResolver {
/// @return A an empty collection of the same type
access(all) fun createEmptyCollection(): @{Collection} {
post {
result.getType() == self.getType(): "The created collection does not have the same type as this collection"
result.getLength() == 0: "The created collection must be empty!"
result.getType() == self.getType():
"NonFungibleToken.Collection.createEmptyCollection: The created collection type <"
.concat(result.getType().identifier)
.concat("> does not have the same type as the collection that was used to create it <")
.concat(self.getType().identifier)
.concat(">")
result.getLength() == 0:
"NonFungibleToken.Collection.createEmptyCollection: The created collection has a non-zero length."
.concat(" A newly created collection must be empty!")
}
}
}
Expand All @@ -256,7 +279,9 @@ access(all) contract interface NonFungibleToken: ViewResolver {
/// @return An array of NFT Types that the implementing contract defines.
access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
post {
result.getIDs().length == 0: "The created collection must be empty!"
result.getIDs().length == 0:
"NonFungibleToken.createEmptyCollection: The created collection has a non-zero length."
.concat(" A newly created collection must be empty!")
}
}
}
4 changes: 3 additions & 1 deletion contracts/test/MaliciousNFT.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,9 @@ access(all) contract MaliciousNFT: NonFungibleToken {
/// withdraw removes an NFT from the collection and moves it to the caller
access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
let token <- self.ownedNFTs.remove(key: withdrawID)
?? panic("Could not withdraw an NFT with the provided ID from the collection")
?? panic("MaliciousNFT.Collection.withdraw: Could not withdraw an NFT with the ID="
.concat(withdrawID.toString())
.concat(". Check the submitted ID to make sure it is one that this collection owns"))

return <-token
}
Expand Down
10 changes: 9 additions & 1 deletion flow.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,15 @@
"aliases": {
"testing": "0x0000000000000007"
}
}
},
"Burner": {
"source": "./contracts/utility/Burner.cdc",
"aliases": {
"emulator": "0xf8d6e0586b0a20c7",
"testing": "0x0000000000000001",
"testnet": "0x9a0766d93b6608b7"
}
}
},
"networks": {
"emulator": "127.0.0.1:3569",
Expand Down
49 changes: 46 additions & 3 deletions lib/go/contracts/go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,53 @@
module github.com/onflow/flow-nft/lib/go/contracts

go 1.16
go 1.22

toolchain go1.22.4

require (
github.com/kevinburke/go-bindata v3.23.0+incompatible
github.com/onflow/flow-go-sdk v1.0.0-M1
github.com/stretchr/testify v1.8.4
github.com/onflow/flow-go-sdk v1.0.0-preview.54
github.com/stretchr/testify v1.9.0
)

require (
github.com/SaveTheRbtz/mph v0.1.1-0.20240117162131-4166ec7869bc // indirect
github.com/bits-and-blooms/bitset v1.10.0 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/fxamacker/cbor/v2 v2.4.1-0.20230228173756-c0c9f774e40c // indirect
github.com/fxamacker/circlehash v0.3.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/holiman/uint256 v1.2.4 // indirect
github.com/k0kubun/pp v3.0.1+incompatible // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/logrusorgru/aurora/v4 v4.0.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/onflow/atree v0.8.0-rc.6 // indirect
github.com/onflow/cadence v1.0.0-preview.51 // indirect
github.com/onflow/crypto v0.25.1 // indirect
github.com/onflow/flow/protobuf/go/flow v0.4.3 // indirect
github.com/onflow/go-ethereum v1.13.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c // indirect
github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.uber.org/goleak v1.2.1 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
gonum.org/v1/gonum v0.14.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading
Loading