-
Notifications
You must be signed in to change notification settings - Fork 0
Add initial Tidal contract & supporting scripts & transactions #1
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
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 { | ||
| 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 | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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() | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| ) | ||
| ) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||
| ) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
TideManagerlooks a lot like a NFT collectionThere was a problem hiding this comment.
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.