Skip to content

Commit

Permalink
add descriptive error messages to contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuahannan committed Sep 10, 2024
1 parent 1bbb412 commit 0256cd7
Show file tree
Hide file tree
Showing 11 changed files with 123 additions and 1,109 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules/*
.env
coverage.lcov
coverage.json
*.pkey
71 changes: 60 additions & 11 deletions contracts/FungibleToken.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ access(all) contract interface FungibleToken: ViewResolver {
post {
// `result` refers to the return value
result.balance == amount:
"Withdrawal amount must be the same as the balance of the withdrawn Vault"
"FungibleToken.Provider.withdraw: Cannot withdraw tokens!"
.concat("The balance of the withdrawn tokens (").concat(result.balance.toString())
.concat(") is not equal to the amount requested to be withdrawn (")
.concat(amount.toString()).concat(")")
}
}
}
Expand Down Expand Up @@ -178,7 +181,9 @@ access(all) contract interface FungibleToken: ViewResolver {
emit Burned(type: self.getType().identifier, amount: self.balance, fromUUID: self.uuid)
}
post {
self.balance == 0.0: "The balance must be set to zero during the burnCallback method so that it cannot be spammed"
self.balance == 0.0:
"FungibleToken.Vault.burnCallback: Cannot burn this Vault with Burner.burn()."
.concat("The balance must be set to zero during the burnCallback method so that it cannot be spammed")
}
self.balance = 0.0
}
Expand Down Expand Up @@ -211,15 +216,29 @@ access(all) contract interface FungibleToken: ViewResolver {
access(Withdraw) fun withdraw(amount: UFix64): @{Vault} {
pre {
self.balance >= amount:
"Amount withdrawn must be less than or equal than the balance of the Vault"
"FungibleToken.Vault.withdraw: Cannot withdraw tokens! "
.concat("The amount requested to be withdrawn (").concat(amount.toString())
.concat(") is greater than the balance of the Vault (")
.concat(self.balance.toString()).concat(").")
}
post {
result.getType() == self.getType(): "Must return the same vault type as self"
result.getType() == self.getType():
"FungibleToken.Vault.withdraw: Cannot withdraw tokens!"
.concat("The withdraw method tried to return an incompatible Vault type <")
.concat(result.getType().identifier).concat(">. ")
.concat("It must return a Vault with the same type as self (")
.concat(self.getType().identifier).concat(">.")

// use the special function `before` to get the value of the `balance` field
// at the beginning of the function execution
//
self.balance == before(self.balance) - amount:
"New Vault balance must be the difference of the previous balance and the withdrawn Vault balance"
"FungibleToken.Vault.withdraw: Cannot withdraw tokens!"
.concat("The sender's balance after the withdrawal (")
.concat(self.balance.toString())
.concat(") must be the difference of the previous balance (").concat(before(self.balance.toString()))
.concat(") and the amount withdrawn (").concat(amount.toString())

emit Withdrawn(
type: result.getType().identifier,
amount: amount,
Expand All @@ -238,7 +257,13 @@ access(all) contract interface FungibleToken: ViewResolver {
// as the vault that is accepting the deposit
pre {
from.isInstance(self.getType()):
"Cannot deposit an incompatible token type"
"FungibleToken.Vault.deposit: Cannot deposit tokens!"
.concat("The type of the deposited tokens <")
.concat(from.getType().identifier)
.concat("> has to be the same type as the Vault being deposited into <")
.concat(self.getType().identifier)
.concat(">. Check that you are withdrawing and depositing to the correct paths in the sender and receiver accounts")
.concat("and that those paths hold the same Vault types.")
}
post {
emit Deposited(
Expand All @@ -250,7 +275,11 @@ access(all) contract interface FungibleToken: ViewResolver {
balanceAfter: self.balance
)
self.balance == before(self.balance) + before(from.balance):
"New Vault balance must be the sum of the previous balance and the deposited Vault"
"FungibleToken.Vault.deposit: Cannot deposit tokens!"
.concat("The receiver's balance after the deposit (")
.concat(self.balance.toString())
.concat(") must be the sum of the previous balance (").concat(before(self.balance.toString()))
.concat(") and the amount deposited (").concat(before(from.balance).toString())
}
}

Expand All @@ -259,8 +288,18 @@ access(all) contract interface FungibleToken: ViewResolver {
/// @return A Vault of the same type that has a balance of zero
access(all) fun createEmptyVault(): @{Vault} {
post {
result.balance == 0.0: "The newly created Vault must have zero balance"
result.getType() == self.getType(): "The newly created Vault must have the same type as the creating vault"
result.balance == 0.0:
"FungibleToken.Vault.createEmptyVault: Empty Vault creation failed!"
.concat("The newly created Vault must have zero balance but it has a balance of ")
.concat(result.balance.toString())

result.getType() == self.getType():
"FungibleToken.Vault.createEmptyVault: Empty Vault creation failed!"
.concat("The type of the new Vault <")
.concat(result.getType().identifier)
.concat("> has to be the same type as the Vault that created it <")
.concat(self.getType().identifier)
.concat(">.")
}
}
}
Expand All @@ -270,8 +309,18 @@ access(all) contract interface FungibleToken: ViewResolver {
/// @return A Vault of the requested type that has a balance of zero
access(all) fun createEmptyVault(vaultType: Type): @{FungibleToken.Vault} {
post {
result.getType() == vaultType: "The returned vault does not match the desired type"
result.balance == 0.0: "The newly created Vault must have zero balance"
result.balance == 0.0:
"FungibleToken.createEmptyVault: Empty Vault creation failed!"
.concat("The newly created Vault must have zero balance but it has a balance of ")
.concat(result.balance.toString())

result.getType() == vaultType:
"FungibleToken.Vault.createEmptyVault: Empty Vault creation failed!"
.concat("The type of the new Vault <")
.concat(result.getType().identifier)
.concat("> has to be the same as the type that was requested <")
.concat(vaultType.identifier)
.concat(">.")
}
}
}
10 changes: 8 additions & 2 deletions contracts/FungibleTokenMetadataViews.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,14 @@ access(all) contract FungibleTokenMetadataViews {
createEmptyVaultFunction: fun(): @{FungibleToken.Vault}
) {
pre {
receiverLinkedType.isSubtype(of: Type<&{FungibleToken.Receiver}>()): "Receiver public type must include FungibleToken.Receiver."
metadataLinkedType.isSubtype(of: Type<&{FungibleToken.Vault}>()): "Metadata linked type must be a fungible token vault"
receiverLinkedType.isSubtype(of: Type<&{FungibleToken.Receiver}>()):
"Receiver public type <".concat(receiverLinkedType.identifier)
.concat("> must be a subtype of <").concat(Type<&{FungibleToken.Receiver}>().identifier)
.concat(">.")
metadataLinkedType.isSubtype(of: Type<&{FungibleToken.Vault}>()):
"Metadata linked type <".concat(metadataLinkedType.identifier)
.concat("> must be a subtype of <").concat(Type<&{FungibleToken.Vault}>().identifier)
.concat(">.")
}
self.storagePath = storagePath
self.receiverPath = receiverPath
Expand Down
27 changes: 19 additions & 8 deletions contracts/FungibleTokenSwitchboard.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ access(all) contract FungibleTokenSwitchboard {
// Borrow a reference to the vault pointed to by the capability we
// want to store inside the switchboard
let vaultRef = capability.borrow()
?? panic ("Cannot borrow reference to vault from capability")
?? panic("FungibleTokenSwitchboard.Switchboard.addNewVault: Cannot borrow reference to vault from capability"
.concat("Make sure that the capability path points to a Vault that has been properly initialized"))

// Check if there is a previous capability for this token, if not
if (self.receiverCapabilities[vaultRef.getType()] == nil) {
// use the vault reference type as key for storing the
Expand All @@ -78,9 +80,6 @@ access(all) contract FungibleTokenSwitchboard {
emit VaultCapabilityAdded(type: vaultRef.getType(),
switchboardOwner: self.owner?.address,
capabilityOwner: capability.address)
} else {
// If there was already a capability for that token, panic
panic("There is already a vault in the Switchboard for this token")
}
}

Expand Down Expand Up @@ -135,7 +134,12 @@ access(all) contract FungibleTokenSwitchboard {
access(Owner) fun addNewVaultWrapper(capability: Capability<&{FungibleToken.Receiver}>,
type: Type) {
// Check if the capability is working
assert(capability.check(), message: "The passed capability is not valid")
assert (
capability.check(),
message:
"FungibleTokenSwitchboard.Switchboard.addNewVaultWrapper: Cannot borrow reference to a vault from the provided capability"
.concat("Make sure that the capability path points to a Vault that has been properly initialized")
)
// Use the type parameter as key for the capability
self.receiverCapabilities[type] = capability
// emit the event that indicates that a new capability has been
Expand Down Expand Up @@ -192,7 +196,9 @@ access(all) contract FungibleTokenSwitchboard {
// Borrow a reference to the vault pointed to by the capability we
// want to remove from the switchboard
let vaultRef = capability.borrow()
?? panic ("Cannot borrow reference to vault from capability")
?? panic ("FungibleTokenSwitchboard.Switchboard.addNewVaultWrapper: Cannot borrow reference to a vault from the provided capability"
.concat("Make sure that the capability path points to a Vault that has been properly initialized"))

// Use the vault reference to find the capability to remove
self.receiverCapabilities.remove(key: vaultRef.getType())
// Emit the event that indicates that a new capability has been
Expand All @@ -212,11 +218,16 @@ access(all) contract FungibleTokenSwitchboard {
access(all) fun deposit(from: @{FungibleToken.Vault}) {
// Get the capability from the ones stored at the switchboard
let depositedVaultCapability = self.receiverCapabilities[from.getType()]
?? panic ("The deposited vault is not available on this switchboard")
?? panic ("FungibleTokenSwitchboard.Switchboard.deposit: Cannot deposit Vault! "
.concat("The deposited vault of type <").concat(from.getType().identifier)
.concat("> is not available on this Fungible Token switchboard. ")
.concat("The recipient needs to initialize their account and switchboard to hold and receive the deposited vault type."))

// Borrow the reference to the desired vault
let vaultRef = depositedVaultCapability.borrow()
?? panic ("Can not borrow a reference to the the vault")
?? panic ("FungibleTokenSwitchboard.Switchboard.deposit: Cannot borrow reference to a vault"
.concat("from the type of the deposited Vault <").concat(from.getType().identifier)
.concat(">. Make sure that the capability path points to a Vault that has been properly initialized"))

vaultRef.deposit(from: <-from)
}
Expand Down
Loading

0 comments on commit 0256cd7

Please sign in to comment.