Skip to content
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
188 changes: 188 additions & 0 deletions cadence/contracts/Tidal.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import "FungibleToken"
import "Burner"
import "ViewResolver"

import "DFB"

///
/// THIS CONTRACT IS A MOCK AND IS NOT INTENDED FOR USE IN PRODUCTION
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
///
access(all) contract Tidal {

access(all) let TideManagerStoragePath: StoragePath
access(all) let TideManagerPublicPath: PublicPath

access(all) event CreatedTide(id: UInt64, idType: String, uuid: UInt64, initialAmount: UFix64, creator: Address?)
access(all) event DepositedToTide(id: UInt64, idType: String, amount: UFix64, owner: Address?, fromUUID: UInt64)
access(all) event WithdrawnFromTide(id: UInt64, idType: String, amount: UFix64, owner: Address?, toUUID: UInt64)
access(all) event AddedToManager(id: UInt64, idType: String, owner: Address?, managerUUID: UInt64)
access(all) event BurnedTide(id: UInt64, idType: String, remainingBalance: UFix64)

access(self) var currentIdentifier: UInt64

access(all) fun createTideManager(): @TideManager {
return <-create TideManager()
}

/* --- CONSTRUCTS --- */

access(all) struct UniqueID : DFB.UniqueIdentifier {
access(all) let id: UInt64
init() {
self.id = Tidal.currentIdentifier
Tidal.currentIdentifier = Tidal.currentIdentifier + 1
}
}

access(all) resource Tide : Burner.Burnable, FungibleToken.Receiver, FungibleToken.Provider, ViewResolver.Resolver {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it is cliche, but just wanted to float it, but would it make sense to make this an NFT? This looks a lot like an NFT and TideManager looks a lot like a NFT collection

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely very similar, but I've been told we want to avoid this. I think the concern is users may transfer Tides without realizing that the ownership of the Tide is inherent to the ownership of the NFT. Other protocols have implemented DeFi positions as NFTs and there have been instances of users selling their positions for far less than they're worth because they think the NFT is just a sort of commemorative token and not the thing itself.

access(contract) let uniqueID: {DFB.UniqueIdentifier}
access(self) let vault: @{FungibleToken.Vault}

init(_ vault: @{FungibleToken.Vault}) {
self.vault <- vault
self.uniqueID = UniqueID()
}

access(all) view fun id(): UInt64 {
return self.uniqueID.id
}

access(all) view fun getTideBalance(): UFix64 {
return self.vault.balance
}

access(contract) fun burnCallback() {
emit BurnedTide(id: self.uniqueID.id, idType: self.uniqueID.getType().identifier,remainingBalance: self.vault.balance)
}

access(all) view fun getViews(): [Type] {
return []
}

access(all) fun resolveView(_ view: Type): AnyStruct? {
return nil
}

access(all) fun deposit(from: @{FungibleToken.Vault}) {
pre {
self.isSupportedVaultType(type: from.getType()):
"Deposited vault of type \(from.getType().identifier) is not supported by this Tide"
}
emit DepositedToTide(id: self.uniqueID.id, idType: self.uniqueID.getType().identifier, amount: from.balance, owner: self.owner?.address, fromUUID: from.uuid)
self.vault.deposit(from: <-from)
}

access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
return { self.vault.getType() : true }
}


access(all) view fun isSupportedVaultType(type: Type): Bool {
return self.getSupportedVaultTypes()[type] ?? false
}

access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
return amount <= self.vault.balance
}

access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
pre {
self.isAvailableToWithdraw(amount: amount):
"Requested amount \(amount) is greater than withdrawable balance of \(self.vault.balance)"
}
let res <- self.vault.withdraw(amount: amount)
emit WithdrawnFromTide(id: self.uniqueID.id, idType: self.uniqueID.getType().identifier, amount: amount, owner: self.owner?.address, toUUID: res.uuid)
return <- res
}
}

access(all) entitlement Owner

access(all) resource TideManager : ViewResolver.ResolverCollection {
access(self) let tides: @{UInt64: Tide}

init() {
self.tides <- {}
}

access(all) view fun borrowTide(id: UInt64): &Tide? {
return &self.tides[id]
}

access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
return &self.tides[id]
}

access(all) view fun getIDs(): [UInt64] {
return self.tides.keys
}

access(all) view fun getNumberOfTides(): Int {
return self.tides.length
}

access(all) fun createTide(withVault: @{FungibleToken.Vault}) {
let balance = withVault.balance
let tide <-create Tide(<-withVault)

emit CreatedTide(id: tide.uniqueID.id, idType: tide.uniqueID.getType().identifier, uuid: tide.uuid, initialAmount: balance, creator: self.owner?.address)

self.addTide(<-tide)
}

access(all) fun addTide(_ tide: @Tide) {
pre {
self.tides[tide.uniqueID.id] == nil:
"Collision with Tide ID \(tide.uniqueID.id) - a Tide with this ID already exists"
}
emit AddedToManager(id: tide.uniqueID.id, idType: tide.uniqueID.getType().identifier, owner: self.owner?.address, managerUUID: self.uuid)
self.tides[tide.uniqueID.id] <-! tide
}

access(all) fun depositToTide(_ id: UInt64, from: @{FungibleToken.Vault}) {
pre {
self.tides[id] != nil:
"No Tide with ID \(id) found"
}
let tide = (&self.tides[id] as &Tide?)!
tide.deposit(from: <-from)
}

access(Owner) fun withdrawTide(id: UInt64): @Tide {
pre {
self.tides[id] != nil:
"No Tide with ID \(id) found"
}
return <- self.tides.remove(key: id)!
}

access(Owner) fun withdrawFromTide(_ id: UInt64, amount: UFix64): @{FungibleToken.Vault} {
pre {
self.tides[id] != nil:
"No Tide with ID \(id) found"
}
let tide = (&self.tides[id] as auth(FungibleToken.Withdraw) &Tide?)!
return <- tide.withdraw(amount: amount)
}

access(Owner) fun closeTide(_ id: UInt64): @{FungibleToken.Vault} {
pre {
self.tides[id] != nil:
"No Tide with ID \(id) found"
}
let tide <- self.withdrawTide(id: id)
let res <- tide.withdraw(amount: tide.getTideBalance())
Burner.burn(<-tide)
return <-res
}
}

init() {
let pathIdentifier = "TidalTideManager_\(self.account.address)"
self.TideManagerStoragePath = StoragePath(identifier: pathIdentifier)!
self.TideManagerPublicPath = PublicPath(identifier: pathIdentifier)!

self.currentIdentifier = 0
}
}
12 changes: 12 additions & 0 deletions cadence/scripts/tidal/get_tide_ids.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import "Tidal"

/// Retrieves the IDs of Tides configured at the provided address or `nil` if a TideManager is not stored
///
/// @param address: The address of the Flow account in question
///
/// @return A UInt64 array of all Tide IDs stored in the account's TideManager
access(all)
fun main(address: Address): [UInt64]? {
return getAccount(address).capabilities.borrow<&Tidal.TideManager>(Tidal.TideManagerPublicPath)
?.getIDs()
}
15 changes: 15 additions & 0 deletions cadence/scripts/tokens/get_balance.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import "FungibleToken"

/// Returns the balance of the stored Vault at the given address if exists, otherwise nil
///
/// @param address: The address of the account that owns the vault
/// @param vaultPathIdentifier: The identifier of the vault's storage path
///
/// @returns The balance of the stored Vault at the given address
///
access(all) fun main(address: Address, vaultPathIdentifier: String): UFix64? {
let path = StoragePath(identifier: vaultPathIdentifier) ?? panic("Malformed StoragePath identifier")
return getAuthAccount<auth(BorrowValue) &Account>(address).storage.borrow<&{FungibleToken.Vault}>(
from: path
)?.balance ?? nil
}
24 changes: 24 additions & 0 deletions cadence/transactions/flow-token/transfer_flow.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import "FungibleToken"
import "FlowToken"

transaction(recipient: Address, amount: UFix64) {

let providerVault: auth(FungibleToken.Withdraw) &FlowToken.Vault
let receiver: &{FungibleToken.Receiver}

prepare(signer: auth(BorrowValue) &Account) {
self.providerVault = signer.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(
from: /storage/flowTokenVault
)!
self.receiver = getAccount(recipient).capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
?? panic("Could not borrow receiver reference")
}

execute {
self.receiver.deposit(
from: <-self.providerVault.withdraw(
amount: amount
)
)
}
}
51 changes: 51 additions & 0 deletions cadence/transactions/tidal/close_tide.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import "FungibleToken"
import "FungibleTokenMetadataViews"
import "ViewResolver"

import "Tidal"

/// Withdraws the full balance from an existing Tide stored in the signer's TideManager and closes the Tide. If the
/// signer does not yet have a Vault of the withdrawn Type, one is configured.
///
/// @param id: The Tide.id() of the Tide from which the full balance will be withdrawn
///
transaction(id: UInt64) {
let manager: auth(Tidal.Owner) &Tidal.TideManager
let receiver: &{FungibleToken.Vault}

prepare(signer: auth(BorrowValue, SaveValue, StorageCapabilities, PublishCapability) &Account) {
// reference the signer's TideManager & underlying Tide
self.manager = signer.storage.borrow<auth(Tidal.Owner) &Tidal.TideManager>(from: Tidal.TideManagerStoragePath)
?? panic("Signer does not have a TideManager stored at path \(Tidal.TideManagerStoragePath) - configure and retry")
let tide = self.manager.borrowTide(id: id) ?? panic("Tide with ID \(id) was not found")

// get the data for where the vault type is canoncially stored
let vaultType = tide.getSupportedVaultTypes().keys[0]
let tokenContract = getAccount(vaultType.address!).contracts.borrow<&{FungibleToken}>(name: vaultType.contractName!)
?? panic("Vault type \(vaultType.identifier) is not defined by a FungibleToken contract")
let vaultData = tokenContract.resolveContractView(
resourceType: vaultType,
viewType: Type<FungibleTokenMetadataViews.FTVaultData>()
) as? FungibleTokenMetadataViews.FTVaultData
?? panic("Could not resolve FTVaultData for vault type \(vaultType.identifier)")

// configure a receiving Vault if none exists
if signer.storage.type(at: vaultData.storagePath) == nil {
signer.storage.save(<-vaultData.createEmptyVault(), to: vaultData.storagePath)
let vaultCap = signer.capabilities.storage.issue<&{FungibleToken.Vault}>(vaultData.storagePath)
let receiverCap = signer.capabilities.storage.issue<&{FungibleToken.Vault}>(vaultData.storagePath)
signer.capabilities.publish(vaultCap, at: vaultData.metadataPath)
signer.capabilities.publish(vaultCap, at: vaultData.receiverPath)
}

// reference the signer's receiver
self.receiver = signer.storage.borrow<&{FungibleToken.Vault}>(from: vaultData.storagePath)
?? panic("Signer does not have a vault of type \(vaultType.identifier) at path \(vaultData.storagePath) from which to source funds")
}

execute {
self.receiver.deposit(
from: <-self.manager.closeTide(id)
)
}
}
41 changes: 41 additions & 0 deletions cadence/transactions/tidal/deposit_to_tide.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import "FungibleToken"
import "FungibleTokenMetadataViews"
import "ViewResolver"

import "Tidal"

/// Deposits to an existing Tide stored in the signer's TideManager
///
/// @param id: The Tide.id() of the Tide to which the amount will be deposited
/// @param amount: The amount to deposit into the new Tide, denominated in the Tide's Vault type
///
transaction(id: UInt64, amount: UFix64) {
let manager: &Tidal.TideManager
let depositVault: @{FungibleToken.Vault}

prepare(signer: auth(BorrowValue) &Account) {
// reference the signer's TideManager & underlying Tide
self.manager = signer.storage.borrow<&Tidal.TideManager>(from: Tidal.TideManagerStoragePath)
?? panic("Signer does not have a TideManager stored at path \(Tidal.TideManagerStoragePath) - configure and retry")
let tide = self.manager.borrowTide(id: id) ?? panic("Tide with ID \(id) was not found")

// get the data for where the vault type is canoncially stored
let vaultType = tide.getSupportedVaultTypes().keys[0]
let tokenContract = getAccount(vaultType.address!).contracts.borrow<&{FungibleToken}>(name: vaultType.contractName!)
?? panic("Vault type \(vaultType.identifier) is not defined by a FungibleToken contract")
let vaultData = tokenContract.resolveContractView(
resourceType: vaultType,
viewType: Type<FungibleTokenMetadataViews.FTVaultData>()
) as? FungibleTokenMetadataViews.FTVaultData
?? panic("Could not resolve FTVaultData for vault type \(vaultType.identifier)")

// withdraw the amount to deposit into the new Tide
let sourceVault = signer.storage.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(from: vaultData.storagePath)
?? panic("Signer does not have a vault of type \(vaultType.identifier) at path \(vaultData.storagePath) from which to source funds")
self.depositVault <- sourceVault.withdraw(amount: amount)
}

execute {
self.manager.depositToTide(id, from: <-self.depositVault)
}
}
51 changes: 51 additions & 0 deletions cadence/transactions/tidal/open_tide.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import "FungibleToken"
import "FungibleTokenMetadataViews"
import "ViewResolver"

import "Tidal"

/// Opens a new Tide in the Tidal platform, funding the Tide with the specified Vault and amount
///
/// @param vaultIdentifier: The Vault's Type identifier
/// e.g. vault.getType().identifier == 'A.0ae53cb6e3f42a79.FlowToken.Vault'
/// @param amount: The amount to deposit into the new Tide
///
transaction(vaultIdentifier: String, amount: UFix64) {
let manager: &Tidal.TideManager
let depositVault: @{FungibleToken.Vault}

prepare(signer: auth(BorrowValue, SaveValue, StorageCapabilities, PublishCapability) &Account) {
// get the data for where the vault type is canoncially stored
let vaultType = CompositeType(vaultIdentifier)
?? panic("Vault identifier \(vaultIdentifier) is not associated with a valid Type")
let tokenContract = getAccount(vaultType.address!).contracts.borrow<&{FungibleToken}>(name: vaultType.contractName!)
?? panic("Vault type \(vaultIdentifier) is not defined by a FungibleToken contract")
let vaultData = tokenContract.resolveContractView(
resourceType: vaultType,
viewType: Type<FungibleTokenMetadataViews.FTVaultData>()
) as? FungibleTokenMetadataViews.FTVaultData
?? panic("Could not resolve FTVaultData for vault type \(vaultIdentifier)")

// withdraw the amount to deposit into the new Tide
let sourceVault = signer.storage.borrow<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(from: vaultData.storagePath)
?? panic("Signer does not have a vault of type \(vaultIdentifier) at path \(vaultData.storagePath) from which to source funds")
self.depositVault <- sourceVault.withdraw(amount: amount)

// configure the TideManager if needed
if signer.storage.type(at: Tidal.TideManagerStoragePath) == nil {
signer.storage.save(<-Tidal.createTideManager(), to: Tidal.TideManagerStoragePath)
let cap = signer.capabilities.storage.issue<&Tidal.TideManager>(Tidal.TideManagerStoragePath)
signer.capabilities.publish(cap, at: Tidal.TideManagerPublicPath)
// issue an authorized capability for later access via Capability controller if needed (e.g. via HybridCustody)
signer.capabilities.storage.issue<auth(Tidal.Owner) &Tidal.TideManager>(
Tidal.TideManagerStoragePath
)
}
self.manager = signer.storage.borrow<&Tidal.TideManager>(from: Tidal.TideManagerStoragePath)
?? panic("Signer does not have a TideManager stored at path \(Tidal.TideManagerStoragePath) - configure and retry")
}

execute {
self.manager.createTide(withVault: <-self.depositVault)
}
}
Loading