Skip to content

Commit

Permalink
Contract updates for cadence 1.0 (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
austinkline authored Sep 4, 2024
1 parent 9db39e2 commit e85dd28
Show file tree
Hide file tree
Showing 74 changed files with 6,496 additions and 2,662 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ jobs:
with:
node-version: '14'
- name: Install Flow CLI
run: bash -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)" -- v1.5.0
run: bash -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)"
- name: Run tests
run: sh ./test.sh
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Currently, the list includes:
| FTProviderFactory | 0x294e44e1ec6993c6 | 0xd8a7e05a7ac670c0 |
| FTReceiverFactory | 0x294e44e1ec6993c6 | 0xd8a7e05a7ac670c0 |
| NFTCollectionPublicFactory | 0x294e44e1ec6993c6 | 0xd8a7e05a7ac670c0 |
| NFTProviderAndCollectionPublicFactory | 0x294e44e1ec6993c6 | 0xd8a7e05a7ac670c0 |
| NFTProviderAndCollectionFactory | 0x294e44e1ec6993c6 | 0xd8a7e05a7ac670c0 |
| NFTProviderFactory | 0x294e44e1ec6993c6 | 0xd8a7e05a7ac670c0 |
| NFTCatalog | 0x49a7cda3a1eecc29 | 0x324c34e1c517e4db |
| NFTCatalogAdmin | 0x49a7cda3a1eecc29 | 0x324c34e1c517e4db |
Expand Down
44 changes: 44 additions & 0 deletions contracts/Burner.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/// Burner is a contract that can facilitate the destruction of any resource on flow.
///
/// Contributors
/// - Austin Kline - https://twitter.com/austin_flowty
/// - Deniz Edincik - https://twitter.com/bluesign
/// - Bastian Müller - https://twitter.com/turbolent
access(all) contract Burner {
/// When Crescendo (Cadence 1.0) is released, custom destructors will be removed from cadece.
/// Burnable is an interface meant to replace this lost feature, allowing anyone to add a callback
/// method to ensure they do not destroy something which is not meant to be,
/// or to add logic based on destruction such as tracking the supply of a FT Collection
///
/// NOTE: The only way to see benefit from this interface
/// is to always use the burn method in this contract. Anyone who owns a resource can always elect **not**
/// to destroy a resource this way
access(all) resource interface Burnable {
access(contract) fun burnCallback()
}

/// burn is a global method which will destroy any resource it is given.
/// If the provided resource implements the Burnable interface,
/// it will call the burnCallback method and then destroy afterwards.
access(all) fun burn(_ r: @AnyResource) {
if let s <- r as? @{Burnable} {
s.burnCallback()
destroy s
} else if let arr <- r as? @[AnyResource] {
while arr.length > 0 {
let item <- arr.removeFirst()
self.burn(<-item)
}
destroy arr
} else if let dict <- r as? @{HashableStruct: AnyResource} {
let keys = dict.keys
while keys.length > 0 {
let item <- dict.remove(key: keys.removeFirst())!
self.burn(<-item)
}
destroy dict
} else {
destroy r
}
}
}
76 changes: 37 additions & 39 deletions contracts/FlowStorageFees.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,29 @@
import "FungibleToken"
import "FlowToken"

pub contract FlowStorageFees {
access(all) contract FlowStorageFees {

// Emitted when the amount of storage capacity an account has per reserved Flow token changes
pub event StorageMegaBytesPerReservedFLOWChanged(_ storageMegaBytesPerReservedFLOW: UFix64)
access(all) event StorageMegaBytesPerReservedFLOWChanged(_ storageMegaBytesPerReservedFLOW: UFix64)

// Emitted when the minimum amount of Flow tokens that an account needs to have reserved for storage capacity changes.
pub event MinimumStorageReservationChanged(_ minimumStorageReservation: UFix64)
access(all) event MinimumStorageReservationChanged(_ minimumStorageReservation: UFix64)

// Defines how much storage capacity every account has per reserved Flow token.
// definition is written per unit of flow instead of the inverse,
// so there is no loss of precision calculating storage from flow,
// but there is loss of precision when calculating flow per storage.
pub var storageMegaBytesPerReservedFLOW: UFix64
access(all) var storageMegaBytesPerReservedFLOW: UFix64

// Defines the minimum amount of Flow tokens that every account needs to have reserved for storage capacity.
// If an account has less then this amount reserved by the end of any transaction it participated in, the transaction will fail.
pub var minimumStorageReservation: UFix64
access(all) var minimumStorageReservation: UFix64

// An administrator resource that can change the parameters of the FlowStorageFees smart contract.
pub resource Administrator {
access(all) resource Administrator {

// Changes the amount of storage capacity an account has per accounts' reserved storage FLOW.
pub fun setStorageMegaBytesPerReservedFLOW(_ storageMegaBytesPerReservedFLOW: UFix64) {
access(all) fun setStorageMegaBytesPerReservedFLOW(_ storageMegaBytesPerReservedFLOW: UFix64) {
if FlowStorageFees.storageMegaBytesPerReservedFLOW == storageMegaBytesPerReservedFLOW {
return
}
Expand All @@ -48,7 +48,7 @@ pub contract FlowStorageFees {
}

// Changes the minimum amount of FLOW an account has to have reserved.
pub fun setMinimumStorageReservation(_ minimumStorageReservation: UFix64) {
access(all) fun setMinimumStorageReservation(_ minimumStorageReservation: UFix64) {
if FlowStorageFees.minimumStorageReservation == minimumStorageReservation {
return
}
Expand All @@ -63,19 +63,19 @@ pub contract FlowStorageFees {
///
/// Returns megabytes
/// If the account has no default balance it is counted as a balance of 0.0 FLOW
pub fun calculateAccountCapacity(_ accountAddress: Address): UFix64 {
access(all) fun calculateAccountCapacity(_ accountAddress: Address): UFix64 {
var balance = 0.0
if let balanceRef = getAccount(accountAddress)
.getCapability<&FlowToken.Vault{FungibleToken.Balance}>(/public/flowTokenBalance)!
.borrow() {
balance = balanceRef.balance
let acct = getAccount(accountAddress)

if let balanceRef = acct.capabilities.borrow<&FlowToken.Vault>(/public/flowTokenBalance) {
balance = balanceRef.balance
}

return self.accountBalanceToAccountStorageCapacity(balance)
}

/// calculateAccountsCapacity returns the storage capacity of a batch of accounts
pub fun calculateAccountsCapacity(_ accountAddresses: [Address]): [UFix64] {
access(all) fun calculateAccountsCapacity(_ accountAddresses: [Address]): [UFix64] {
let capacities: [UFix64] = []
for accountAddress in accountAddresses {
let capacity = self.calculateAccountCapacity(accountAddress)
Expand All @@ -88,19 +88,19 @@ pub contract FlowStorageFees {
// This is used to check if a transaction will fail because of any account being over the storage capacity
// The payer is an exception as its storage capacity is derived from its balance minus the maximum possible transaction fees
// (transaction fees if the execution effort is at the execution efort limit, a.k.a.: computation limit, a.k.a.: gas limit)
pub fun getAccountsCapacityForTransactionStorageCheck(accountAddresses: [Address], payer: Address, maxTxFees: UFix64): [UFix64] {
access(all) fun getAccountsCapacityForTransactionStorageCheck(accountAddresses: [Address], payer: Address, maxTxFees: UFix64): [UFix64] {
let capacities: [UFix64] = []
for accountAddress in accountAddresses {
var balance = 0.0
if let balanceRef = getAccount(accountAddress)
.getCapability<&FlowToken.Vault{FungibleToken.Balance}>(/public/flowTokenBalance)!
.borrow() {
if accountAddress == payer {
// if the account is the payer, deduct the maximum possible transaction fees from the balance
balance = balanceRef.balance.saturatingSubtract(maxTxFees)
} else {
balance = balanceRef.balance
}
let acct = getAccount(accountAddress)

if let balanceRef = acct.capabilities.borrow<&FlowToken.Vault>(/public/flowTokenBalance) {
if accountAddress == payer {
// if the account is the payer, deduct the maximum possible transaction fees from the balance
balance = balanceRef.balance.saturatingSubtract(maxTxFees)
} else {
balance = balanceRef.balance
}
}

capacities.append(self.accountBalanceToAccountStorageCapacity(balance))
Expand All @@ -110,7 +110,7 @@ pub contract FlowStorageFees {

// accountBalanceToAccountStorageCapacity returns the storage capacity
// an account would have with given the flow balance of the account.
pub fun accountBalanceToAccountStorageCapacity(_ balance: UFix64): UFix64 {
access(all) view fun accountBalanceToAccountStorageCapacity(_ balance: UFix64): UFix64 {
// get address token balance
if balance < self.minimumStorageReservation {
// if < then minimum return 0
Expand All @@ -123,25 +123,25 @@ pub contract FlowStorageFees {

// Amount in Flow tokens
// Returns megabytes
pub fun flowToStorageCapacity(_ amount: UFix64): UFix64 {
access(all) view fun flowToStorageCapacity(_ amount: UFix64): UFix64 {
return amount.saturatingMultiply(FlowStorageFees.storageMegaBytesPerReservedFLOW)
}

// Amount in megabytes
// Returns Flow tokens
pub fun storageCapacityToFlow(_ amount: UFix64): UFix64 {
if FlowStorageFees.storageMegaBytesPerReservedFLOW == 0.0 as UFix64 {
return 0.0 as UFix64
access(all) view fun storageCapacityToFlow(_ amount: UFix64): UFix64 {
if FlowStorageFees.storageMegaBytesPerReservedFLOW == 0.0 {
return 0.0
}
// possible loss of precision
// putting the result back into `flowToStorageCapacity` might not yield the same result
return amount / FlowStorageFees.storageMegaBytesPerReservedFLOW
}

// converts storage used from UInt64 Bytes to UFix64 Megabytes.
pub fun convertUInt64StorageBytesToUFix64Megabytes(_ storage: UInt64): UFix64 {
access(all) view fun convertUInt64StorageBytesToUFix64Megabytes(_ storage: UInt64): UFix64 {
// safe convert UInt64 to UFix64 (without overflow)
let f = UFix64(storage % 100000000 as UInt64) * 0.00000001 as UFix64 + UFix64(storage / 100000000 as UInt64)
let f = UFix64(storage % 100000000) * 0.00000001 + UFix64(storage / 100000000)
// decimal point correction. Megabytes to bytes have a conversion of 10^-6 while UFix64 minimum value is 10^-8
let storageMb = f.saturatingMultiply(100.0)
return storageMb
Expand All @@ -151,13 +151,12 @@ pub contract FlowStorageFees {
///
/// The available balance of an account is its default token balance minus what is reserved for storage.
/// If the account has no default balance it is counted as a balance of 0.0 FLOW
pub fun defaultTokenAvailableBalance(_ accountAddress: Address): UFix64 {
access(all) fun defaultTokenAvailableBalance(_ accountAddress: Address): UFix64 {
//get balance of account
let acct = getAccount(accountAddress)
var balance = 0.0
if let balanceRef = acct
.getCapability(/public/flowTokenBalance)
.borrow<&FlowToken.Vault{FungibleToken.Balance}>() {

if let balanceRef = acct.capabilities.borrow<&FlowToken.Vault>(/public/flowTokenBalance) {
balance = balanceRef.balance
}

Expand All @@ -171,9 +170,9 @@ pub contract FlowStorageFees {
///
/// The reserved balance of an account is its storage used multiplied by the storage cost per flow token.
/// The reserved balance is at least the minimum storage reservation.
pub fun defaultTokenReservedBalance(_ accountAddress: Address): UFix64 {
access(all) view fun defaultTokenReservedBalance(_ accountAddress: Address): UFix64 {
let acct = getAccount(accountAddress)
var reserved = self.storageCapacityToFlow(self.convertUInt64StorageBytesToUFix64Megabytes(acct.storageUsed))
var reserved = self.storageCapacityToFlow(self.convertUInt64StorageBytesToUFix64Megabytes(acct.storage.used))
// at least self.minimumStorageReservation should be reserved
if reserved < self.minimumStorageReservation {
reserved = self.minimumStorageReservation
Expand All @@ -187,7 +186,6 @@ pub contract FlowStorageFees {
self.minimumStorageReservation = 0.0 // or 0 kb of minimum storage reservation
let admin <- create Administrator()
self.account.save(<-admin, to: /storage/storageFeesAdmin)
self.account.storage.save(<-admin, to: /storage/storageFeesAdmin)
}
}

Loading

0 comments on commit e85dd28

Please sign in to comment.