From 2ec2125c6008547ff41c596282fb418944e17808 Mon Sep 17 00:00:00 2001 From: highskore Date: Sun, 28 Jul 2024 01:56:52 +0200 Subject: [PATCH] refactor: depreacte USDC, add FlowToUSD Oracle support --- contracts/FIND.cdc | 1057 +++++++++++---------- contracts/community/OracleConfig.cdc | 61 ++ contracts/community/OracleInterface.cdc | 144 +++ contracts/community/PublicPriceOracle.cdc | 106 +++ 4 files changed, 864 insertions(+), 504 deletions(-) create mode 100644 contracts/community/OracleConfig.cdc create mode 100644 contracts/community/OracleInterface.cdc create mode 100644 contracts/community/PublicPriceOracle.cdc diff --git a/contracts/FIND.cdc b/contracts/FIND.cdc index 76734aed..94577149 100644 --- a/contracts/FIND.cdc +++ b/contracts/FIND.cdc @@ -1,6 +1,4 @@ import FungibleToken from "./standard/FungibleToken.cdc" -import FUSD from "./standard/FUSD.cdc" -import FiatToken from "./standard/FiatToken.cdc" import FlowToken from "./standard/FlowToken.cdc" import DapperUtilityCoin from "./standard/DapperUtilityCoin.cdc" import Profile from "./Profile.cdc" @@ -9,8 +7,10 @@ import Clock from "./Clock.cdc" import Sender from "./Sender.cdc" import ProfileCache from "./ProfileCache.cdc" import FindUtils from "./FindUtils.cdc" -/* +import PublicPriceOracle from "./community/PublicPriceOracle.cdc" +import FUSD from "./standard/FUSD.cdc" +/* ///FIND ///Flow Integrated Name Directory - A naming service on flow, @@ -26,24 +26,6 @@ Taxonomy: - leaseStatus: FREE|TAKEN|LOCKED, a LOCKED lease can be reopend by the owner. A lease will be locked for 90 days before it is freed */ pub contract FIND { - - - //Old events not in use anymore we cannot remove - pub event Sold() - pub event SoldAuction() - pub event DirectOfferRejected() - pub event DirectOfferCanceled() - pub event AuctionStarted() - pub event AuctionCanceled() - pub event AuctionBid() - pub event AuctionCanceledReservePrice() - pub event ForSale() - pub event ForAuction() - - // Deprecated in testnet - pub event TokensRewarded() - pub event TokensCanNotBeRewarded() - //event when FT is sent pub event FungibleTokenSent(from:Address, fromName:String?, name:String, toAddress:Address, message:String, tag:String, amount: UFix64, ftType:String) @@ -88,6 +70,49 @@ pub contract FIND { panic("Network is not set up") } + ////////////////////////////////////////// + // ORACLE + ////////////////////////////////////////// + + // FLOW/USD Price Oracle + access(account) var oracle: Address + + // Set the Oracle address + access(account) fun setOracle(_ oracle: Address) { + self.oracle = oracle + } + + // Get the Oracle address + pub fun getOracle(): Address { + return self.oracle + } + + // Get the latest FLOW/USD price + pub fun getLatestPrice(): UFix64 { + let lastResult = PublicPriceOracle.getLatestPrice(oracleAddr: self.oracle) + let lastBlockNum = PublicPriceOracle.getLatestBlockHeight(oracleAddr: self.oracle) + + // Make sure the price is not expired + if getCurrentBlock().height - lastBlockNum > 2000 { + panic("Price is expired") + } + + return lastResult + } + + // Convert FLOW to USD + pub fun convertFLOWToUSD(_ amount: UFix64): UFix64 { + return amount * self.getLatestPrice() + } + + // Convert USD to FLOW + pub fun convertUSDToFLOW(_ amount: UFix64): UFix64 { + return amount / self.getLatestPrice() + } + + ////////////////////////////////////////// + // HELPER FUNCTIONS + ////////////////////////////////////////// //These methods are basically just here for convenience @@ -240,8 +265,8 @@ pub contract FIND { var path = "" if vault.getType() == Type<@FlowToken.Vault>() { path ="flowTokenReceiver" - } else if vault.getType() == Type<@FUSD.Vault>() { - path="fusdReceiver" + } else { + panic("Could not find a valid receiver for this vault type") } if path != "" { emit FungibleTokenSent(from: fromAddress, fromName: FIND.reverseLookup(fromAddress), name: "", toAddress: address, message:message, tag:tag, amount:vault.balance, ftType:vault.getType().identifier) @@ -252,7 +277,6 @@ pub contract FIND { } - /// Deposit FT to name /// @param to: The name to send money too /// @param from: The vault to send too @@ -380,7 +404,7 @@ pub contract FIND { self.offerCallback=callback } - pub fun extendLease(_ vault: @FUSD.Vault) { + pub fun extendLease(_ vault: @FlowToken.Vault) { let network= self.networkCap.borrow() ?? panic("The network is not up") network.renew(name: self.name, vault:<- vault) } @@ -466,72 +490,6 @@ pub contract FIND { } } - /* An Auction for a lease */ - pub resource Auction { - access(contract) var endsAt: UFix64 - access(contract) var startedAt: UFix64 - access(contract) let extendOnLateBid: UFix64 - access(contract) var latestBidCallback: Capability<&BidCollection{BidCollectionPublic}> - access(contract) let name: String - - init(endsAt: UFix64, startedAt: UFix64, extendOnLateBid: UFix64, latestBidCallback: Capability<&BidCollection{BidCollectionPublic}>, name: String) { - - if startedAt >= endsAt { - panic("Cannot start before it will end") - } - if extendOnLateBid == 0.0 { - panic("Extends on late bid must be a non zero value") - } - self.endsAt=endsAt - self.startedAt=startedAt - self.extendOnLateBid=extendOnLateBid - self.latestBidCallback=latestBidCallback - self.name=name - } - - pub fun getBalance() : UFix64 { - let cb = self.latestBidCallback.borrow() ?? panic("The bidder has unlinked the capability. bidder address: ".concat(self.latestBidCallback.address.toString())) - return cb.getBalance(self.name) - } - - pub fun addBid(callback: Capability<&BidCollection{BidCollectionPublic}>, timestamp: UFix64, lease: &Lease) { - let offer=callback.borrow()! - offer.setBidType(name: self.name, type: "auction") - - var previousBuyer: Address?=nil - if callback.address != self.latestBidCallback.address { - if offer.getBalance(self.name) <= self.getBalance() { - panic("bid must be larger then current bid. Current bid is : ".concat(self.getBalance().toString()).concat(". New bid is at : ").concat(offer.getBalance(self.name).toString())) - } - previousBuyer=self.latestBidCallback.address - //we send the money back - self.latestBidCallback.borrow()!.cancel(self.name) - } - self.latestBidCallback=callback - let suggestedEndTime=timestamp+self.extendOnLateBid - if suggestedEndTime > self.endsAt { - self.endsAt=suggestedEndTime - } - - let bidder= callback.address - let profile=getAccount(bidder).getCapability<&{Profile.Public}>(Profile.publicPath).borrow() - if profile == nil { - panic("Create a profile before you make a bid") - } - let bidderName= profile!.getName() - let bidderAvatar= profile!.getAvatar() - let owner=lease.owner!.address - let ownerName=self.name - - var previousBuyerName:String?=nil - if let pb = previousBuyer { - previousBuyerName=FIND.reverseLookup(pb) - } - - emit EnglishAuction(name: self.name, uuid: lease.uuid, seller: owner, sellerName:ownerName, amount: offer.getBalance(self.name), auctionReservePrice: lease.auctionReservePrice!, status: "active_ongoing", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar:bidderAvatar, endsAt: self.endsAt ,validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:previousBuyer, previousBuyerName:previousBuyerName) - } - } - //struct to expose information about leases pub struct LeaseInformation { pub let name: String @@ -591,16 +549,14 @@ pub contract FIND { //add a new lease token to the collection, can only be called in this contract access(contract) fun deposit(token: @FIND.Lease) - access(contract)fun cancelUserBid(_ name: String) - access(contract) fun increaseBid(_ name: String, balance: UFix64) - //place a bid on a token - access(contract) fun registerBid(name: String, callback: Capability<&BidCollection{BidCollectionPublic}>) + // access(contract) fun increaseBid(_ name: String, balance: UFix64) + // access(contract) fun registerBid(name: String, callback: Capability<&BidCollection{BidCollectionPublic}>) //anybody should be able to fulfill an auction as long as it is done pub fun fulfillAuction(_ name: String) - pub fun buyAddon(name:String, addon: String, vault: @FUSD.Vault) + pub fun buyAddon(name:String, addon: String, vault: @FlowToken.Vault) pub fun buyAddonDapper(merchAccount: Address, name:String, addon:String, vault: @DapperUtilityCoin.Vault) access(account) fun adminAddAddon(name:String, addon: String) pub fun getAddon(name:String) : [String] @@ -632,7 +588,7 @@ pub contract FIND { self.networkWallet=networkWallet } - pub fun buyAddon(name:String, addon:String, vault: @FUSD.Vault) { + pub fun buyAddon(name:String, addon:String, vault: @FlowToken.Vault) { if !self.leases.containsKey(name) { panic("Invalid name=".concat(name)) } @@ -646,7 +602,12 @@ pub contract FIND { if network.addonPrices[addon] == nil { panic("This addon is not available. addon : ".concat(addon)) } - let addonPrice = network.addonPrices[addon]! + + // Get addon price in USD + var addonPrice = network.addonPrices[addon]! + + // Convert USD to FLOW + addonPrice = FIND.convertUSDToFLOW(addonPrice) let lease = self.borrow(name) @@ -820,48 +781,47 @@ pub contract FIND { return info } + // Deprecated //call this to start an auction for this lease - pub fun startAuction(_ name: String) { - let timestamp=Clock.time() - let lease = self.borrow(name) - - if !lease.validate() { - panic("This is not a valid lease. Lease already expires and some other user registered it. Lease : ".concat(name)) - } - - let duration=lease.auctionDuration - let extensionOnLateBid=lease.auctionExtensionOnLateBid - if lease.offerCallback == nil { - panic("cannot start an auction on a name without a bid, set salePrice") - } - - let callback=lease.offerCallback! - let offer=callback.borrow()! - offer.setBidType(name: name, type: "auction") - - - - let bidder= callback.address - let bidderProfile= getAccount(bidder).getCapability<&{Profile.Public}>(Profile.publicPath).borrow() ?? panic("Bidder unlinked the profile capability. bidder address : ".concat(bidder.toString())) - let bidderName= bidderProfile.getName() - let bidderAvatar= bidderProfile.getAvatar() - let owner=lease.owner!.address - let ownerName=lease.name - - let endsAt=timestamp + duration - emit EnglishAuction(name: name, uuid:lease.uuid, seller: owner, sellerName:FIND.reverseLookup(owner), amount: offer.getBalance(name), auctionReservePrice: lease.auctionReservePrice!, status: "active_ongoing", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar:bidderAvatar, endsAt: endsAt, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) - - let oldAuction <- self.auctions[name] <- create Auction(endsAt:endsAt, startedAt: timestamp, extendOnLateBid: extensionOnLateBid, latestBidCallback: callback, name: name) - lease.setCallback(nil) - - if lease.offerCallback == nil { - Debug.log("offer callback is empty") - }else { - Debug.log("offer callback is NOT empty") - } - - destroy oldAuction - } + // pub fun startAuction(_ name: String) { + // let timestamp=Clock.time() + // let lease = self.borrow(name) + + // if !lease.validate() { + // panic("This is not a valid lease. Lease already expires and some other user registered it. Lease : ".concat(name)) + // } + + // let duration=lease.auctionDuration + // let extensionOnLateBid=lease.auctionExtensionOnLateBid + // if lease.offerCallback == nil { + // panic("cannot start an auction on a name without a bid, set salePrice") + // } + + // let callback=lease.offerCallback! + // let offer=callback.borrow()! + // offer.setBidType(name: name, type: "auction") + + // let bidder= callback.address + // let bidderProfile= getAccount(bidder).getCapability<&{Profile.Public}>(Profile.publicPath).borrow() ?? panic("Bidder unlinked the profile capability. bidder address : ".concat(bidder.toString())) + // let bidderName= bidderProfile.getName() + // let bidderAvatar= bidderProfile.getAvatar() + // let owner=lease.owner!.address + // let ownerName=lease.name + + // let endsAt=timestamp + duration + // emit EnglishAuction(name: name, uuid:lease.uuid, seller: owner, sellerName:FIND.reverseLookup(owner), amount: offer.getBalance(name), auctionReservePrice: lease.auctionReservePrice!, status: "active_ongoing", vaultType:Type<@FlowToken.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar:bidderAvatar, endsAt: endsAt, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) + + // let oldAuction <- self.auctions[name] <- create Auction(endsAt:endsAt, startedAt: timestamp, extendOnLateBid: extensionOnLateBid, latestBidCallback: callback, name: name) + // lease.setCallback(nil) + + // if lease.offerCallback == nil { + // Debug.log("offer callback is empty") + // }else { + // Debug.log("offer callback is NOT empty") + // } + + // destroy oldAuction + // } access(contract) fun cancelUserBid(_ name: String) { @@ -876,7 +836,6 @@ pub contract FIND { let lease= self.borrow(name) if let callback = lease.offerCallback { - let bidder= callback.address let bidderProfile= getAccount(bidder).getCapability<&{Profile.Public}>(Profile.publicPath).borrow() let bidderName=bidderProfile?.getName() @@ -887,141 +846,143 @@ pub contract FIND { if callback.check() { amount = callback.borrow()!.getBalance(name) } - emit DirectOffer(name: name, uuid: lease.uuid, seller: owner, sellerName: ownerName, amount: amount, status: "cancel_rejected", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) + emit DirectOffer(name: name, uuid: lease.uuid, seller: owner, sellerName: ownerName, amount: amount, status: "cancel_rejected", vaultType:Type<@FlowToken.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) } lease.setCallback(nil) } - access(contract) fun increaseBid(_ name: String, balance: UFix64) { - if !self.leases.containsKey(name) { - panic("Invalid name=".concat(name)) - } - - let lease = self.borrow(name) - - if !lease.validate() { - panic("This is not a valid lease. Lease already expires and some other user registered it. Lease : ".concat(name)) - } - - let timestamp=Clock.time() - - if balance < lease.auctionMinBidIncrement { - panic("Increment should be greater than ".concat(lease.auctionMinBidIncrement.toString())) - } - if self.auctions.containsKey(name) { - let auction = self.borrowAuction(name) - if auction.endsAt < timestamp { - panic("Auction has ended") - } - auction.addBid(callback:auction.latestBidCallback, timestamp:timestamp, lease: lease) - return - } - - - let bidder= lease.offerCallback!.address - let bidderProfile= getAccount(bidder).getCapability<&{Profile.Public}>(Profile.publicPath).borrow() ?? panic("Create a profile before you make a bid") - let bidderName= bidderProfile.getName() - let bidderAvatar= bidderProfile.getAvatar() - let owner=lease.owner!.address - let ownerName=lease.name - - let balance=lease.offerCallback!.borrow()?.getBalance(name) ?? panic("Bidder unlinked the bid collection capability. bidder address : ".concat(bidder.toString())) - Debug.log("Offer is at ".concat(balance.toString())) - if lease.salePrice == nil && lease.auctionStartPrice == nil{ - - emit DirectOffer(name: name, uuid: lease.uuid, seller: owner, sellerName: ownerName, amount: balance, status: "active_offered", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) - return - } - - - if lease.salePrice != nil && lease.salePrice != nil && balance >= lease.salePrice! { - self.fulfill(name) - } else if lease.auctionStartPrice != nil && balance >= lease.auctionStartPrice! { - self.startAuction(name) - } else { - emit DirectOffer(name: name, uuid: lease.uuid, seller: owner, sellerName: ownerName, amount: balance, status: "active_offered", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) - } - - } - - access(contract) fun registerBid(name: String, callback: Capability<&BidCollection{BidCollectionPublic}>) { - - if !self.leases.containsKey(name) { - panic("Invalid name=".concat(name)) - } - - let timestamp=Clock.time() - let lease = self.borrow(name) - - if !lease.validate() { - panic("This is not a valid lease. Lease already expires and some other user registered it. Lease : ".concat(name)) - } - - if self.auctions.containsKey(name) { - let auction = self.borrowAuction(name) - - if auction.latestBidCallback.address == callback.address { - panic("You already have the latest bid on this item, use the incraseBid transaction") - } - if auction.endsAt < timestamp { - panic("Auction has ended") - } - auction.addBid(callback:callback, timestamp:timestamp, lease: lease) - return - } - - let balance=callback.borrow()?.getBalance(name) ?? panic("Bidder unlinked the bid collection capability. bidder address : ".concat(callback.address.toString())) - var previousBuyer:Address?=nil - if let cb= lease.offerCallback { - if cb.address == callback.address { - panic("You already have the latest bid on this item, use the incraseBid transaction") - } - let cbRef = cb.borrow() ?? panic("Bidder unlinked the bid collection capability. bidder address : ".concat(cb.address.toString())) - let currentBalance=cbRef.getBalance(name) - - Debug.log("currentBalance=".concat(currentBalance.toString()).concat(" new bid is at=").concat(balance.toString())) - if currentBalance >= balance { - panic("There is already a higher bid on this lease. Current bid is : ".concat(currentBalance.toString()).concat(" New bid is at : ").concat(balance.toString())) - } - previousBuyer=cb.address - cbRef.cancel(name) - } - - lease.setCallback(callback) - - - - let bidder= callback.address - let profile=getAccount(bidder).getCapability<&{Profile.Public}>(Profile.publicPath).borrow() - if profile == nil { - panic("Create a profile before you make a bid") - } - let bidderName= profile!.getName() - let bidderAvatar= profile!.getAvatar() - let owner=lease.owner!.address - let ownerName=lease.name - - var previousBuyerName:String?=nil - if let pb=previousBuyer { - previousBuyerName=FIND.reverseLookup(pb) - } - Debug.log("Balance of lease is at ".concat(balance.toString())) - if lease.salePrice == nil && lease.auctionStartPrice == nil { - Debug.log("Sale price not set") - emit DirectOffer(name: name, uuid:lease.uuid, seller: owner, sellerName: ownerName, amount: balance, status: "active_offered", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:previousBuyer, previousBuyerName:previousBuyerName) - return - } - - if lease.salePrice != nil && balance >= lease.salePrice! { - Debug.log("Direct sale!") - self.fulfill(name) - } else if lease.auctionStartPrice != nil && balance >= lease.auctionStartPrice! { - self.startAuction(name) - } else { - emit DirectOffer(name: name, uuid: lease.uuid, seller: owner, sellerName: ownerName, amount: balance, status: "active_offered", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:previousBuyer, previousBuyerName:previousBuyerName) - } - } + // Deprecated + // access(contract) fun increaseBid(_ name: String, balance: UFix64) { + // if !self.leases.containsKey(name) { + // panic("Invalid name=".concat(name)) + // } + + // let lease = self.borrow(name) + + // if !lease.validate() { + // panic("This is not a valid lease. Lease already expires and some other user registered it. Lease : ".concat(name)) + // } + + // let timestamp=Clock.time() + + // if balance < lease.auctionMinBidIncrement { + // panic("Increment should be greater than ".concat(lease.auctionMinBidIncrement.toString())) + // } + // if self.auctions.containsKey(name) { + // let auction = self.borrowAuction(name) + // if auction.endsAt < timestamp { + // panic("Auction has ended") + // } + // auction.addBid(callback:auction.latestBidCallback, timestamp:timestamp, lease: lease) + // return + // } + + + // let bidder= lease.offerCallback!.address + // let bidderProfile= getAccount(bidder).getCapability<&{Profile.Public}>(Profile.publicPath).borrow() ?? panic("Create a profile before you make a bid") + // let bidderName= bidderProfile.getName() + // let bidderAvatar= bidderProfile.getAvatar() + // let owner=lease.owner!.address + // let ownerName=lease.name + + // let balance=lease.offerCallback!.borrow()?.getBalance(name) ?? panic("Bidder unlinked the bid collection capability. bidder address : ".concat(bidder.toString())) + // Debug.log("Offer is at ".concat(balance.toString())) + // if lease.salePrice == nil && lease.auctionStartPrice == nil{ + + // emit DirectOffer(name: name, uuid: lease.uuid, seller: owner, sellerName: ownerName, amount: balance, status: "active_offered", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) + // return + // } + + + // if lease.salePrice != nil && lease.salePrice != nil && balance >= lease.salePrice! { + // self.fulfill(name) + // } else if lease.auctionStartPrice != nil && balance >= lease.auctionStartPrice! { + // self.startAuction(name) + // } else { + // emit DirectOffer(name: name, uuid: lease.uuid, seller: owner, sellerName: ownerName, amount: balance, status: "active_offered", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) + // } + + // } + + // Deprecated + // access(contract) fun registerBid(name: String, callback: Capability<&BidCollection{BidCollectionPublic}>) { + + // if !self.leases.containsKey(name) { + // panic("Invalid name=".concat(name)) + // } + + // let timestamp=Clock.time() + // let lease = self.borrow(name) + + // if !lease.validate() { + // panic("This is not a valid lease. Lease already expires and some other user registered it. Lease : ".concat(name)) + // } + + // if self.auctions.containsKey(name) { + // let auction = self.borrowAuction(name) + + // if auction.latestBidCallback.address == callback.address { + // panic("You already have the latest bid on this item, use the incraseBid transaction") + // } + // if auction.endsAt < timestamp { + // panic("Auction has ended") + // } + // auction.addBid(callback:callback, timestamp:timestamp, lease: lease) + // return + // } + + // let balance=callback.borrow()?.getBalance(name) ?? panic("Bidder unlinked the bid collection capability. bidder address : ".concat(callback.address.toString())) + // var previousBuyer:Address?=nil + // if let cb= lease.offerCallback { + // if cb.address == callback.address { + // panic("You already have the latest bid on this item, use the incraseBid transaction") + // } + // let cbRef = cb.borrow() ?? panic("Bidder unlinked the bid collection capability. bidder address : ".concat(cb.address.toString())) + // let currentBalance=cbRef.getBalance(name) + + // Debug.log("currentBalance=".concat(currentBalance.toString()).concat(" new bid is at=").concat(balance.toString())) + // if currentBalance >= balance { + // panic("There is already a higher bid on this lease. Current bid is : ".concat(currentBalance.toString()).concat(" New bid is at : ").concat(balance.toString())) + // } + // previousBuyer=cb.address + // cbRef.cancel(name) + // } + + // lease.setCallback(callback) + + + + // let bidder= callback.address + // let profile=getAccount(bidder).getCapability<&{Profile.Public}>(Profile.publicPath).borrow() + // if profile == nil { + // panic("Create a profile before you make a bid") + // } + // let bidderName= profile!.getName() + // let bidderAvatar= profile!.getAvatar() + // let owner=lease.owner!.address + // let ownerName=lease.name + + // var previousBuyerName:String?=nil + // if let pb=previousBuyer { + // previousBuyerName=FIND.reverseLookup(pb) + // } + // Debug.log("Balance of lease is at ".concat(balance.toString())) + // if lease.salePrice == nil && lease.auctionStartPrice == nil { + // Debug.log("Sale price not set") + // emit DirectOffer(name: name, uuid:lease.uuid, seller: owner, sellerName: ownerName, amount: balance, status: "active_offered", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:previousBuyer, previousBuyerName:previousBuyerName) + // return + // } + + // if lease.salePrice != nil && balance >= lease.salePrice! { + // Debug.log("Direct sale!") + // self.fulfill(name) + // } else if lease.auctionStartPrice != nil && balance >= lease.auctionStartPrice! { + // self.startAuction(name) + // } else { + // emit DirectOffer(name: name, uuid: lease.uuid, seller: owner, sellerName: ownerName, amount: balance, status: "active_offered", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:previousBuyer, previousBuyerName:previousBuyerName) + // } + // } //cancel will cancel and auction or reject a bid if no auction has started pub fun cancel(_ name: String) { @@ -1042,7 +1003,7 @@ pub contract FIND { let ownerName=lease.name Debug.log("we have a blind bid so we cancel that") let cbRef = cb.borrow() ?? panic("Bidder unlinked the bid collection capability. bidder address : ".concat(cb.address.toString())) - emit DirectOffer(name: name, uuid:lease.uuid, seller: owner, sellerName: ownerName, amount: cbRef.getBalance(name), status: "rejected", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) + emit DirectOffer(name: name, uuid:lease.uuid, seller: owner, sellerName: ownerName, amount: cbRef.getBalance(name), status: "rejected", vaultType:Type<@FlowToken.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) cbRef.cancel(name) lease.setCallback(nil) @@ -1077,9 +1038,9 @@ pub contract FIND { let leaseInfo = self.getLease(name)! if auctionEnded { - emit EnglishAuction(name: name, uuid:lease.uuid, seller: owner, sellerName:ownerName, amount: balance, auctionReservePrice: lease.auctionReservePrice!, status: "cancel_reserved_not_met", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, endsAt: auction.endsAt, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) + emit EnglishAuction(name: name, uuid:lease.uuid, seller: owner, sellerName:ownerName, amount: balance, auctionReservePrice: lease.auctionReservePrice!, status: "cancel_reserved_not_met", vaultType:Type<@FlowToken.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, endsAt: auction.endsAt, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) } else { - emit EnglishAuction(name: name, uuid:lease.uuid, seller: owner, sellerName:ownerName, amount: balance, auctionReservePrice: lease.auctionReservePrice!, status: "cancel_listing", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, endsAt: auction.endsAt, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) + emit EnglishAuction(name: name, uuid:lease.uuid, seller: owner, sellerName:ownerName, amount: balance, auctionReservePrice: lease.auctionReservePrice!, status: "cancel_listing", vaultType:Type<@FlowToken.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, endsAt: auction.endsAt, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) } let cbRef = auction.latestBidCallback.borrow() ?? panic("Bidder unlinked the bid collection capability. bidder address : ".concat(bidder.toString())) cbRef.cancel(name) @@ -1088,7 +1049,7 @@ pub contract FIND { } let owner=lease.owner!.address let ownerName=lease.name - emit EnglishAuction(name: name, uuid:lease.uuid, seller: owner, sellerName:ownerName, amount: 0.0, auctionReservePrice: lease.auctionReservePrice!, status: "cancel_listing", vaultType:Type<@FUSD.Vault>().identifier, buyer:nil, buyerName:nil, buyerAvatar: nil, endsAt: nil, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) + emit EnglishAuction(name: name, uuid:lease.uuid, seller: owner, sellerName:ownerName, amount: 0.0, auctionReservePrice: lease.auctionReservePrice!, status: "cancel_listing", vaultType:Type<@FlowToken.Vault>().identifier, buyer:nil, buyerName:nil, buyerAvatar: nil, endsAt: nil, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) } @@ -1131,9 +1092,9 @@ pub contract FIND { //move the token to the new profile lease.move(profile: newProfile) if lease.salePrice == nil || lease.salePrice != soldFor { - emit DirectOffer(name: name, uuid: lease.uuid, seller: lease.owner!.address, sellerName: FIND.reverseLookup(lease.owner!.address), amount: soldFor, status: "sold", vaultType:Type<@FUSD.Vault>().identifier, buyer:newProfile.address, buyerName:FIND.reverseLookup(newProfile.address), buyerAvatar: avatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) + emit DirectOffer(name: name, uuid: lease.uuid, seller: lease.owner!.address, sellerName: FIND.reverseLookup(lease.owner!.address), amount: soldFor, status: "sold", vaultType:Type<@FlowToken.Vault>().identifier, buyer:newProfile.address, buyerName:FIND.reverseLookup(newProfile.address), buyerAvatar: avatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) } else { - emit Sale(name: name, uuid: lease.uuid, seller: lease.owner!.address, sellerName: FIND.reverseLookup(lease.owner!.address), amount: soldFor, status: "sold", vaultType:Type<@FUSD.Vault>().identifier, buyer:newProfile.address, buyerName:FIND.reverseLookup(newProfile.address), buyerAvatar: avatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil()) + emit Sale(name: name, uuid: lease.uuid, seller: lease.owner!.address, sellerName: FIND.reverseLookup(lease.owner!.address), amount: soldFor, status: "sold", vaultType:Type<@FlowToken.Vault>().identifier, buyer:newProfile.address, buyerName:FIND.reverseLookup(newProfile.address), buyerAvatar: avatar, validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil()) } let token <- self.leases.remove(key: name)! @@ -1221,7 +1182,7 @@ pub contract FIND { let ownerName=tokenRef.name Debug.log("we have a blind bid so we cancel that") let cbRef = cb.borrow() ?? panic("Bidder unlinked the bid collection capability. bidder address : ".concat(bidder.toString())) - emit DirectOffer(name: name, uuid:tokenRef.uuid, seller: owner, sellerName: ownerName, amount: cbRef.getBalance(name), status: "rejected", vaultType:Type<@FUSD.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, validUntil: tokenRef.getLeaseExpireTime(), lockedUntil: tokenRef.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) + emit DirectOffer(name: name, uuid:tokenRef.uuid, seller: owner, sellerName: ownerName, amount: cbRef.getBalance(name), status: "rejected", vaultType:Type<@FlowToken.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar: bidderAvatar, validUntil: tokenRef.getLeaseExpireTime(), lockedUntil: tokenRef.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) cbRef.cancel(name) tokenRef.setCallback(nil) } @@ -1230,7 +1191,7 @@ pub contract FIND { tokenRef.setReservePrice(auctionReservePrice) tokenRef.setAuctionDuration(auctionDuration) tokenRef.setExtentionOnLateBid(auctionExtensionOnLateBid) - emit EnglishAuction(name: name, uuid: tokenRef.uuid, seller: self.owner!.address, sellerName:FIND.reverseLookup(self.owner!.address), amount: tokenRef.auctionStartPrice!, auctionReservePrice: tokenRef.auctionReservePrice!, status: "active_listed", vaultType:Type<@FUSD.Vault>().identifier, buyer:nil, buyerName:nil, buyerAvatar: nil, endsAt: nil, validUntil: tokenRef.getLeaseExpireTime(), lockedUntil: tokenRef.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) + emit EnglishAuction(name: name, uuid: tokenRef.uuid, seller: self.owner!.address, sellerName:FIND.reverseLookup(self.owner!.address), amount: tokenRef.auctionStartPrice!, auctionReservePrice: tokenRef.auctionReservePrice!, status: "active_listed", vaultType:Type<@FlowToken.Vault>().identifier, buyer:nil, buyerName:nil, buyerAvatar: nil, endsAt: nil, validUntil: tokenRef.getLeaseExpireTime(), lockedUntil: tokenRef.getLeaseLockedUntil(), previousBuyer:nil, previousBuyerName:nil) } pub fun listForSale(name :String, directSellPrice:UFix64) { @@ -1246,10 +1207,9 @@ pub contract FIND { } tokenRef.setSalePrice(directSellPrice) - emit Sale(name: name, uuid: tokenRef.uuid, seller: self.owner!.address, sellerName: FIND.reverseLookup(self.owner!.address), amount: tokenRef.salePrice!, status: "active_listed", vaultType:Type<@FUSD.Vault>().identifier, buyer:nil, buyerName:nil, buyerAvatar: nil, validUntil: tokenRef.getLeaseExpireTime(), lockedUntil: tokenRef.getLeaseLockedUntil()) + emit Sale(name: name, uuid: tokenRef.uuid, seller: self.owner!.address, sellerName: FIND.reverseLookup(self.owner!.address), amount: tokenRef.salePrice!, status: "active_listed", vaultType:Type<@FlowToken.Vault>().identifier, buyer:nil, buyerName:nil, buyerAvatar: nil, validUntil: tokenRef.getLeaseExpireTime(), lockedUntil: tokenRef.getLeaseLockedUntil()) } - pub fun delistAuction(_ name: String) { if !self.leases.containsKey(name) { @@ -1262,14 +1222,13 @@ pub contract FIND { tokenRef.setReservePrice(nil) } - pub fun delistSale(_ name: String) { if !self.leases.containsKey(name) { panic("Cannot list name for sale that is not registered to you name=".concat(name)) } let tokenRef = self.borrow(name) - emit Sale(name: name, uuid:tokenRef.uuid, seller: self.owner!.address, sellerName: FIND.reverseLookup(self.owner!.address), amount: tokenRef.salePrice!, status: "cancel", vaultType:Type<@FUSD.Vault>().identifier, buyer:nil, buyerName:nil, buyerAvatar: nil, validUntil: tokenRef.getLeaseExpireTime(), lockedUntil: tokenRef.getLeaseLockedUntil()) + emit Sale(name: name, uuid:tokenRef.uuid, seller: self.owner!.address, sellerName: FIND.reverseLookup(self.owner!.address), amount: tokenRef.salePrice!, status: "cancel", vaultType:Type<@FlowToken.Vault>().identifier, buyer:nil, buyerName:nil, buyerAvatar: nil, validUntil: tokenRef.getLeaseExpireTime(), lockedUntil: tokenRef.getLeaseLockedUntil()) tokenRef.setSalePrice(nil) } @@ -1334,23 +1293,23 @@ pub contract FIND { } - //This has to be here since you can only get this from a auth account and thus we ensure that you cannot use wrong paths - pub fun registerUSDC(name: String, vault: @FiatToken.Vault){ - let profileCap = self.owner!.getCapability<&{Profile.Public}>(Profile.publicPath) - let leases= self.owner!.getCapability<&LeaseCollection{LeaseCollectionPublic}>(FIND.LeasePublicPath) - - let network=FIND.account.borrow<&Network>(from: FIND.NetworkStoragePath)! + // Deprecated + // //This has to be here since you can only get this from a auth account and thus we ensure that you cannot use wrong paths + // pub fun registerUSDC(name: String, vault: @FiatToken.Vault){ + // let profileCap = self.owner!.getCapability<&{Profile.Public}>(Profile.publicPath) + // let leases= self.owner!.getCapability<&LeaseCollection{LeaseCollectionPublic}>(FIND.LeasePublicPath) - if !network.publicEnabled { - panic("Public registration is not enabled yet") - } + // let network=FIND.account.borrow<&Network>(from: FIND.NetworkStoragePath)! - network.registerUSDC(name:name, vault: <- vault, profile: profileCap, leases: leases) - } + // if !network.publicEnabled { + // panic("Public registration is not enabled yet") + // } + // network.registerUSDC(name:name, vault: <- vault, profile: profileCap, leases: leases) + // } //This has to be here since you can only get this from a auth account and thus we ensure that you cannot use wrong paths - pub fun register(name: String, vault: @FUSD.Vault){ + pub fun register(name: String, vault: @FlowToken.Vault){ let profileCap = self.owner!.getCapability<&{Profile.Public}>(Profile.publicPath) let leases= self.owner!.getCapability<&LeaseCollection{LeaseCollectionPublic}>(FIND.LeasePublicPath) @@ -1399,8 +1358,6 @@ pub contract FIND { panic("Network is not set up") } - - /* Core network things //=================================================================================================================== @@ -1509,9 +1466,9 @@ pub contract FIND { //this method is only called from a lease, and only the owner has that capability - access(contract) fun renew(name: String, vault: @FUSD.Vault) { + access(contract) fun renew(name: String, vault: @FlowToken.Vault) { if let lease= self.profiles[name] { - let cost= self.calculateCost(name) + let cost= FIND.convertFLOWToUSD(self.calculateCost(name)) if vault.balance != cost { panic("Vault did not contain ".concat(cost.toString()).concat(" amount of FUSD")) } @@ -1587,38 +1544,40 @@ pub contract FIND { } + // Deprecated //everybody can call register, normally done through the convenience method in the contract - pub fun registerUSDC(name: String, vault: @FiatToken.Vault, profile: Capability<&{Profile.Public}>, leases: Capability<&LeaseCollection{LeaseCollectionPublic}>) { + // pub fun registerUSDC(name: String, vault: @FiatToken.Vault, profile: Capability<&{Profile.Public}>, leases: Capability<&LeaseCollection{LeaseCollectionPublic}>) { - if name.length < 3 { - panic( "A FIND name has to be minimum 3 letters long") - } + // if name.length < 3 { + // panic( "A FIND name has to be minimum 3 letters long") + // } - let nameStatus=self.readStatus(name) - if nameStatus.status == LeaseStatus.TAKEN { - panic("Name already registered") - } + // let nameStatus=self.readStatus(name) + // if nameStatus.status == LeaseStatus.TAKEN { + // panic("Name already registered") + // } - //if we have a locked profile that is not owned by the same identity then panic - if nameStatus.status == LeaseStatus.LOCKED { - panic("Name is locked") - } + // //if we have a locked profile that is not owned by the same identity then panic + // if nameStatus.status == LeaseStatus.LOCKED { + // panic("Name is locked") + // } - let cost= self.calculateCost(name) - if vault.balance != cost { - panic("Vault did not contain ".concat(cost.toString()).concat(" amount of FUSD")) - } + // let cost= self.calculateCost(name) + // if vault.balance != cost { + // panic("Vault did not contain ".concat(cost.toString()).concat(" amount of FUSD")) + // } - let address=self.wallet.address - let account=getAccount(address) - let usdcCap = account.getCapability<&{FungibleToken.Receiver}>(FiatToken.VaultReceiverPubPath) - let usdcReceiver = usdcCap.borrow() ?? panic("cound not find usdc vault receiver for address".concat(self.wallet.address.toString())) - usdcReceiver.deposit(from: <- vault) + // let address=self.wallet.address + // let account=getAccount(address) + // let usdcCap = account.getCapability<&{FungibleToken.Receiver}>(FiatToken.VaultReceiverPubPath) + // let usdcReceiver = usdcCap.borrow() ?? panic("cound not find usdc vault receiver for address".concat(self.wallet.address.toString())) + // usdcReceiver.deposit(from: <- vault) + + // self.internal_register(name: name, profile: profile, leases: leases) + // } - self.internal_register(name: name, profile: profile, leases: leases) - } //everybody can call register, normally done through the convenience method in the contract - pub fun register(name: String, vault: @FUSD.Vault, profile: Capability<&{Profile.Public}>, leases: Capability<&LeaseCollection{LeaseCollectionPublic}>) { + pub fun register(name: String, vault: @FlowToken.Vault, profile: Capability<&{Profile.Public}>, leases: Capability<&LeaseCollection{LeaseCollectionPublic}>) { if name.length < 3 { panic( "A FIND name has to be minimum 3 letters long") @@ -1634,7 +1593,8 @@ pub contract FIND { panic("Name is locked") } - let cost= self.calculateCost(name) + let cost= FIND.convertFLOWToUSD(self.calculateCost(name)) + if vault.balance != cost { panic("Vault did not contain ".concat(cost.toString()).concat(" amount of FUSD")) } @@ -1791,23 +1751,226 @@ pub contract FIND { ========================================================================== */ - //Struct that is used to return information about bids - pub struct BidInfo{ - pub let name: String - pub let type: String - pub let amount: UFix64 - pub let timestamp: UFix64 - pub let lease: LeaseInformation? - - init(name: String, amount: UFix64, timestamp: UFix64, type: String, lease: LeaseInformation?) { - self.name=name - self.amount=amount - self.timestamp=timestamp - self.type=type - self.lease=lease + pub fun validateFindName(_ value: String) : Bool { + if value.length < 3 || value.length > 16 { + return false + } + if !FIND.validateAlphanumericLowerDash(value) { + return false + } + + if value.length==16 && FIND.validateHex(value) { + return false + } + + return true + } + + pub fun validateAlphanumericLowerDash(_ value:String) : Bool { + let lowerA: UInt8=97 + let lowerZ: UInt8=122 + + let dash:UInt8=45 + let number0:UInt8=48 + let number9:UInt8=57 + + let bytes=value.utf8 + for byte in bytes { + if byte >= lowerA && byte <= lowerZ { + continue + } + if byte >= number0 && byte <= number9 { + continue + } + + if byte == dash { + continue + } + return false + } + return true + } + pub fun validateHex(_ value:String) : Bool { + let lowerA: UInt8=97 + let lowerF: UInt8=102 + + let number0:UInt8=48 + let number9:UInt8=57 + + let bytes=value.utf8 + for byte in bytes { + if byte >= lowerA && byte <= lowerF { + continue + } + if byte >= number0 && byte <= number9 { + continue + } + return false + + } + return true + + } + + pub fun trimFindSuffix(_ name: String) : String { + return FindUtils.trimSuffix(name, suffix: ".find") + } + + access(contract) fun checkMerchantAddress(_ merchAccount: Address) { + // If only find can sign the trxns and call this function, then we do not have to check the address passed in. + // Otherwise, would it be wiser if we hard code the address here? + + if FIND.account.address == 0x097bafa4e0b48eef { + // This is for mainnet + if merchAccount != 0x55459409d30274ee { + panic("Merch Account address does not match with expected") + } + } else if FIND.account.address == 0x35717efbbce11c74 { + // This is for testnet + if merchAccount != 0x4748780c8bf65e19{ + panic("Merch Account address does not match with expected") + } + } else { + // otherwise falls into emulator and user dapper + if merchAccount != 0x01cf0e2f2f715450{ + panic("Merch Account address does not match with expected ".concat(merchAccount.toString())) + } + } + } + + access(account) fun getMerchantAddress() : Address { + // If only find can sign the trxns and call this function, then we do not have to check the address passed in. + // Otherwise, would it be wiser if we hard code the address here? + + if FIND.account.address == 0x097bafa4e0b48eef { + // This is for mainnet + return 0x55459409d30274ee + } else if FIND.account.address == 0x35717efbbce11c74 { + // This is for testnet + return 0x4748780c8bf65e19 + } else { + // otherwise falls into emulator and user dapper + return 0x01cf0e2f2f715450 + } + } + + init(oracle: Address) { + self.oracle = oracle + self.NetworkPrivatePath= /private/FIND + self.NetworkStoragePath= /storage/FIND + + self.LeasePublicPath=/public/findLeases + self.LeaseStoragePath=/storage/findLeases + + self.BidPublicPath=/public/findBids + self.BidStoragePath=/storage/findBids + + let wallet=self.account.getCapability<&{FungibleToken.Receiver}>(/public/fusdReceiver) + + // these values are hardcoded here for a reason. Then plan is to throw away the key and not have setters for them so that people can trust the contract to be the same + let network <- create Network( + leasePeriod: 31536000.0, //365 days + lockPeriod: 7776000.0, //90 days + secondaryCut: 0.05, + defaultPrice: 5.0, + lengthPrices: {3: 500.0, 4:100.0}, + wallet: wallet, + publicEnabled: false + ) + self.account.save(<-network, to: FIND.NetworkStoragePath) + self.account.link<&Network>( FIND.NetworkPrivatePath, target: FIND.NetworkStoragePath) + } + + ////////////////////////////////////////////////////////////////////// + // DEPRECATED + ////////////////////////////////////////////////////////////////////// + + // import FiatToken from "./standard/FiatToken.cdc" + + //Old events not in use anymore we cannot remove + pub event Sold() + pub event SoldAuction() + pub event DirectOfferRejected() + pub event DirectOfferCanceled() + pub event AuctionStarted() + pub event AuctionCanceled() + pub event AuctionBid() + pub event AuctionCanceledReservePrice() + pub event ForSale() + pub event ForAuction() + + // Deprecated in testnet + pub event TokensRewarded() + pub event TokensCanNotBeRewarded() + + /* An Auction for a lease */ + pub resource Auction { + access(contract) var endsAt: UFix64 + access(contract) var startedAt: UFix64 + access(contract) let extendOnLateBid: UFix64 + access(contract) var latestBidCallback: Capability<&BidCollection{BidCollectionPublic}> + access(contract) let name: String + + init(endsAt: UFix64, startedAt: UFix64, extendOnLateBid: UFix64, latestBidCallback: Capability<&BidCollection{BidCollectionPublic}>, name: String) { + + if startedAt >= endsAt { + panic("Cannot start before it will end") + } + if extendOnLateBid == 0.0 { + panic("Extends on late bid must be a non zero value") + } + self.endsAt=endsAt + self.startedAt=startedAt + self.extendOnLateBid=extendOnLateBid + self.latestBidCallback=latestBidCallback + self.name=name + } + + pub fun getBalance() : UFix64 { + let cb = self.latestBidCallback.borrow() ?? panic("The bidder has unlinked the capability. bidder address: ".concat(self.latestBidCallback.address.toString())) + return cb.getBalance(self.name) + } + + pub fun addBid(callback: Capability<&BidCollection{BidCollectionPublic}>, timestamp: UFix64, lease: &Lease) { + let offer=callback.borrow()! + offer.setBidType(name: self.name, type: "auction") + + var previousBuyer: Address?=nil + if callback.address != self.latestBidCallback.address { + if offer.getBalance(self.name) <= self.getBalance() { + panic("bid must be larger then current bid. Current bid is : ".concat(self.getBalance().toString()).concat(". New bid is at : ").concat(offer.getBalance(self.name).toString())) + } + previousBuyer=self.latestBidCallback.address + //we send the money back + self.latestBidCallback.borrow()!.cancel(self.name) + } + self.latestBidCallback=callback + let suggestedEndTime=timestamp+self.extendOnLateBid + if suggestedEndTime > self.endsAt { + self.endsAt=suggestedEndTime + } + + let bidder= callback.address + let profile=getAccount(bidder).getCapability<&{Profile.Public}>(Profile.publicPath).borrow() + if profile == nil { + panic("Create a profile before you make a bid") + } + let bidderName= profile!.getName() + let bidderAvatar= profile!.getAvatar() + let owner=lease.owner!.address + let ownerName=self.name + + var previousBuyerName:String?=nil + if let pb = previousBuyer { + previousBuyerName=FIND.reverseLookup(pb) + } + + emit EnglishAuction(name: self.name, uuid: lease.uuid, seller: owner, sellerName:ownerName, amount: offer.getBalance(self.name), auctionReservePrice: lease.auctionReservePrice!, status: "active_ongoing", vaultType:Type<@FlowToken.Vault>().identifier, buyer:bidder, buyerName:bidderName, buyerAvatar:bidderAvatar, endsAt: self.endsAt ,validUntil: lease.getLeaseExpireTime(), lockedUntil: lease.getLeaseLockedUntil(), previousBuyer:previousBuyer, previousBuyerName:previousBuyerName) + } + } pub resource Bid { access(contract) let from: Capability<&LeaseCollection{LeaseCollectionPublic}> @@ -1837,6 +2000,23 @@ pub contract FIND { } } + //Struct that is used to return information about bids + pub struct BidInfo{ + pub let name: String + pub let type: String + pub let amount: UFix64 + pub let timestamp: UFix64 + pub let lease: LeaseInformation? + + init(name: String, amount: UFix64, timestamp: UFix64, type: String, lease: LeaseInformation?) { + self.name=name + self.amount=amount + self.timestamp=timestamp + self.type=type + self.lease=lease + } + } + pub resource interface BidCollectionPublic { pub fun getBids() : [BidInfo] pub fun getBalance(_ name: String) : UFix64 @@ -1900,49 +2080,51 @@ pub contract FIND { return bidInfo } + // Deprecated //make a bid on a name - pub fun bid(name: String, vault: @FUSD.Vault) { - let nameStatus=FIND.status(name) - if nameStatus.status == LeaseStatus.FREE { - panic("cannot bid on name that is free") - } + // pub fun bid(name: String, vault: @FUSD.Vault) { + // let nameStatus=FIND.status(name) + // if nameStatus.status == LeaseStatus.FREE { + // panic("cannot bid on name that is free") + // } - if self.owner!.address == nameStatus.owner { - panic("cannot bid on your own name") - } + // if self.owner!.address == nameStatus.owner { + // panic("cannot bid on your own name") + // } - let from=getAccount(nameStatus.owner!).getCapability<&LeaseCollection{LeaseCollectionPublic}>(FIND.LeasePublicPath) + // let from=getAccount(nameStatus.owner!).getCapability<&LeaseCollection{LeaseCollectionPublic}>(FIND.LeasePublicPath) - let bid <- create Bid(from: from, name:name, vault: <- vault) - let leaseCollection= from.borrow() ?? panic("Could not borrow lease bid from owner of name=".concat(name)) + // let bid <- create Bid(from: from, name:name, vault: <- vault) + // let leaseCollection= from.borrow() ?? panic("Could not borrow lease bid from owner of name=".concat(name)) - let callbackCapability =self.owner!.getCapability<&BidCollection{BidCollectionPublic}>(FIND.BidPublicPath) - let oldToken <- self.bids[bid.name] <- bid - //send info to leaseCollection - destroy oldToken - leaseCollection.registerBid(name: name, callback: callbackCapability) - } + // let callbackCapability =self.owner!.getCapability<&BidCollection{BidCollectionPublic}>(FIND.BidPublicPath) + // let oldToken <- self.bids[bid.name] <- bid + // //send info to leaseCollection + // destroy oldToken + // leaseCollection.registerBid(name: name, callback: callbackCapability) + // } + // Deprecated //increase a bid, will not work if the auction has already started - pub fun increaseBid(name: String, vault: @FungibleToken.Vault) { - let nameStatus=FIND.status(name) - if nameStatus.status == LeaseStatus.FREE { - panic("cannot increaseBid on name that is free") - } - let seller=getAccount(nameStatus.owner!).getCapability<&LeaseCollection{LeaseCollectionPublic}>(FIND.LeasePublicPath) - let balance = vault.balance - let bid =self.borrowBid(name) - bid.setBidAt(Clock.time()) - bid.vault.deposit(from: <- vault) - - let from=getAccount(nameStatus.owner!).getCapability<&LeaseCollection{LeaseCollectionPublic}>(FIND.LeasePublicPath) - if !from.check() { - panic("The seller unlinked the lease collection capability. seller address : ".concat(nameStatus.owner!.toString())) - } - from.borrow()!.increaseBid(name, balance: balance) - } + // pub fun increaseBid(name: String, vault: @FungibleToken.Vault) { + // let nameStatus=FIND.status(name) + // if nameStatus.status == LeaseStatus.FREE { + // panic("cannot increaseBid on name that is free") + // } + // let seller=getAccount(nameStatus.owner!).getCapability<&LeaseCollection{LeaseCollectionPublic}>(FIND.LeasePublicPath) + // let balance = vault.balance + // let bid =self.borrowBid(name) + // bid.setBidAt(Clock.time()) + // bid.vault.deposit(from: <- vault) + + // let from=getAccount(nameStatus.owner!).getCapability<&LeaseCollection{LeaseCollectionPublic}>(FIND.LeasePublicPath) + // if !from.check() { + // panic("The seller unlinked the lease collection capability. seller address : ".concat(nameStatus.owner!.toString())) + // } + // from.borrow()!.increaseBid(name, balance: balance) + // } //cancel a bid, will panic if called after auction has started pub fun cancelBid(_ name: String) { @@ -1982,137 +2164,4 @@ pub contract FIND { pub fun createEmptyBidCollection(receiver: Capability<&{FungibleToken.Receiver}>, leases: Capability<&LeaseCollection{LeaseCollectionPublic}>) : @BidCollection { return <- create BidCollection(receiver: receiver, leases: leases) } - - pub fun validateFindName(_ value: String) : Bool { - if value.length < 3 || value.length > 16 { - return false - } - if !FIND.validateAlphanumericLowerDash(value) { - return false - } - - if value.length==16 && FIND.validateHex(value) { - return false - } - - return true - } - - pub fun validateAlphanumericLowerDash(_ value:String) : Bool { - let lowerA: UInt8=97 - let lowerZ: UInt8=122 - - let dash:UInt8=45 - let number0:UInt8=48 - let number9:UInt8=57 - - let bytes=value.utf8 - for byte in bytes { - if byte >= lowerA && byte <= lowerZ { - continue - } - if byte >= number0 && byte <= number9 { - continue - } - - if byte == dash { - continue - } - return false - - } - return true - - } - - pub fun validateHex(_ value:String) : Bool { - let lowerA: UInt8=97 - let lowerF: UInt8=102 - - let number0:UInt8=48 - let number9:UInt8=57 - - let bytes=value.utf8 - for byte in bytes { - if byte >= lowerA && byte <= lowerF { - continue - } - if byte >= number0 && byte <= number9 { - continue - } - return false - - } - return true - - } - - pub fun trimFindSuffix(_ name: String) : String { - return FindUtils.trimSuffix(name, suffix: ".find") - } - - access(contract) fun checkMerchantAddress(_ merchAccount: Address) { - // If only find can sign the trxns and call this function, then we do not have to check the address passed in. - // Otherwise, would it be wiser if we hard code the address here? - - if FIND.account.address == 0x097bafa4e0b48eef { - // This is for mainnet - if merchAccount != 0x55459409d30274ee { - panic("Merch Account address does not match with expected") - } - } else if FIND.account.address == 0x35717efbbce11c74 { - // This is for testnet - if merchAccount != 0x4748780c8bf65e19{ - panic("Merch Account address does not match with expected") - } - } else { - // otherwise falls into emulator and user dapper - if merchAccount != 0x01cf0e2f2f715450{ - panic("Merch Account address does not match with expected ".concat(merchAccount.toString())) - } - } - } - - access(account) fun getMerchantAddress() : Address { - // If only find can sign the trxns and call this function, then we do not have to check the address passed in. - // Otherwise, would it be wiser if we hard code the address here? - - if FIND.account.address == 0x097bafa4e0b48eef { - // This is for mainnet - return 0x55459409d30274ee - } else if FIND.account.address == 0x35717efbbce11c74 { - // This is for testnet - return 0x4748780c8bf65e19 - } else { - // otherwise falls into emulator and user dapper - return 0x01cf0e2f2f715450 - } - } - - init() { - self.NetworkPrivatePath= /private/FIND - self.NetworkStoragePath= /storage/FIND - - self.LeasePublicPath=/public/findLeases - self.LeaseStoragePath=/storage/findLeases - - self.BidPublicPath=/public/findBids - self.BidStoragePath=/storage/findBids - - let wallet=self.account.getCapability<&{FungibleToken.Receiver}>(/public/fusdReceiver) - - // these values are hardcoded here for a reason. Then plan is to throw away the key and not have setters for them so that people can trust the contract to be the same - let network <- create Network( - leasePeriod: 31536000.0, //365 days - lockPeriod: 7776000.0, //90 days - secondaryCut: 0.05, - defaultPrice: 5.0, - lengthPrices: {3: 500.0, 4:100.0}, - wallet: wallet, - publicEnabled: false - ) - self.account.save(<-network, to: FIND.NetworkStoragePath) - self.account.link<&Network>( FIND.NetworkPrivatePath, target: FIND.NetworkStoragePath) - - } } diff --git a/contracts/community/OracleConfig.cdc b/contracts/community/OracleConfig.cdc new file mode 100644 index 00000000..7e02d2c0 --- /dev/null +++ b/contracts/community/OracleConfig.cdc @@ -0,0 +1,61 @@ +/** + +# This contract stores some commonly used paths & library functions for PriceOracle + +# Author Increment Labs + +*/ + +pub contract OracleConfig { + // Admin resource stored in every PriceOracle contract + pub let OracleAdminPath: StoragePath + // Reader public interface exposed in every PriceOracle contract + pub let OraclePublicInterface_ReaderPath: PublicPath + // Feeder public interface exposed in every PriceOracle contract + pub let OraclePublicInterface_FeederPath: PublicPath + // Recommended storage path of reader's certificate + pub let ReaderCertificateStoragePath: StoragePath + + pub fun sortUFix64List(list: [UFix64]): [UFix64] { + let len = list.length + var preIndex = 0 + var current = 0.0 + var i = 1 + while (i < len) { + preIndex = i - 1 + current = list[i]; + while(preIndex >= 0 && list[preIndex] > current) { + list[preIndex+1] = list[preIndex] + preIndex = preIndex - 1 + } + list[preIndex+1] = current + i = i + 1 + } + return list + } + + pub fun sortUInt64List(list: [UInt64]): [UInt64] { + let len = list.length + var preIndex = 0 + var current: UInt64 = 0 + var i = 1 + while (i < len) { + preIndex = i - 1 + current = list[i]; + while(preIndex >= 0 && list[preIndex] > current) { + list[preIndex+1] = list[preIndex] + preIndex = preIndex - 1 + } + list[preIndex+1] = current + i = i + 1 + } + return list + } + + init() { + self.OracleAdminPath = /storage/increment_oracle_admin + self.OraclePublicInterface_ReaderPath = /public/increment_oracle_reader_public + self.OraclePublicInterface_FeederPath = /public/increment_oracle_feeder_public + self.ReaderCertificateStoragePath = /storage/increment_oracle_reader_certificate + } +} diff --git a/contracts/community/OracleInterface.cdc b/contracts/community/OracleInterface.cdc new file mode 100644 index 00000000..855b6206 --- /dev/null +++ b/contracts/community/OracleInterface.cdc @@ -0,0 +1,144 @@ +/** + +# This contract is the interface description of PriceOracle. + The oracle includes an medianizer, which obtains prices from multiple feeds and calculate the median as the final price. + +# Structure + Feeder1(off-chain) --> PriceFeeder(resource) 3.4$ PriceReader1(resource) + Feeder2(off-chain) --> PriceFeeder(resource) 3.2$ --> PriceOracle(contract) cal median 3.4$ --> PriceReader2(resource) + Feeder3(off-chain) --> PriceFeeder(resource) 3.6$ PriceReader3(resource) + +# Robustness + 1. Median value is the current aggregation strategy. + 2. _MinFeederNumber determines the minimum number of feeds required to provide a valid price. As long as there're more than 50% of the nodes are honest the median data is trustworthy. + 3. The feeder needs to set a price expiration time, after which the price will be invalid (0.0). Dev is responsible to detect and deal with this abnormal data in the contract logic. + +# More price-feeding institutions and partners are welcome to join and build a more decentralized oracle on flow. +# To apply to join the Feeder whitelist, please follow: https://docs.increment.fi/protocols/decentralized-price-feed-oracle/apply-as-feeder +# On-chain price data can be publicly & freely accessed through the PublicPriceOracle contract. + +# Author Increment Labs + +*/ + +pub contract interface OracleInterface { + /* + ************************************ + Reader interfaces + ************************************ + */ + /// Oracle price reader, users need to save this resource in their local storage + /// + /// Only readers in the addr whitelist have permission to read prices + /// Please do not share your PriceReader capability with others and take the responsibility of community governance. + pub resource PriceReader { + /// Get the median price of all current feeds. + /// + /// @Return Median price, returns 0.0 if the current price is invalid + /// + pub fun getMedianPrice(): UFix64 + + pub fun getPriceIdentifier(): String { return "" } + + /// Calculate the *raw* median of the price feed with no filtering of expired data. + /// + pub fun getRawMedianPrice(): UFix64 { return 0.0 } + + /// Calculate the published block height of the *raw* median data. If it's an even list, it is the smaller one of the two middle value. + /// + pub fun getRawMedianBlockHeight(): UInt64 { return 0 } + } + + /// Reader related public interfaces opened on PriceOracle smart contract + /// + pub resource interface OraclePublicInterface_Reader { + /// Users who need to read the oracle price should mint this resource and save locally. + /// + pub fun mintPriceReader(): @PriceReader + + /// Recommended path for PriceReader, users can manage resources by themselves + /// + pub fun getPriceReaderStoragePath(): StoragePath + } + + + /* + ************************************ + Feeder interfaces + ************************************ + */ + /// Panel for publishing price. Every feeder needs to mint this resource locally. + /// + pub resource PriceFeeder: PriceFeederPublic { + /// The feeder uses this function to offer price at the price panel + /// + /// Param price - price from off-chain + /// + pub fun publishPrice(price: UFix64) + + /// Set valid duration of price. If there is no update within the duration, the price will be expired. + /// + /// Param blockheightDuration by the block numbers + /// + pub fun setExpiredDuration(blockheightDuration: UInt64) + } + pub resource interface PriceFeederPublic { + /// Get the current feed price, this function can only be called by the PriceOracle contract + /// + pub fun fetchPrice(certificate: &OracleCertificate): UFix64 + + /// Get the current feed price regardless of whether it's expired or not. + /// + pub fun getRawPrice(certificate: &OracleCertificate): UFix64 { return 0.0 } + + pub fun getLatestPublishBlockHeight(): UInt64 { return 0 } + + pub fun getExpiredHeightDuration(): UInt64 { return 0 } + } + + + /// Feeder related public interfaces opened on PriceOracle smart contract + /// + pub resource interface OraclePublicInterface_Feeder { + /// Feeders need to mint their own price panels and expose the exact public path to oracle contract + /// + /// @Return Resource of price panel + /// + pub fun mintPriceFeeder(): @PriceFeeder + + /// The oracle contract will get the PriceFeeder resource based on this path + /// + /// Feeders need to expose the capabilities at this public path + /// + pub fun getPriceFeederPublicPath(): PublicPath + pub fun getPriceFeederStoragePath(): StoragePath + } + + /// IdentityCertificate resource which is used to identify account address or perform caller authentication + /// + pub resource interface IdentityCertificate {} + + /// Each oracle contract will hold its own certificate to identify itself. + /// + /// Only the oracle contract can mint the certificate. + /// + pub resource OracleCertificate: IdentityCertificate {} + + + + /* + ************************************ + Governace interfaces + ************************************ + */ + /// Community administrator, Increment Labs will then collect community feedback and initiate voting for governance. + /// + pub resource interface Admin { + pub fun configOracle(priceIdentifier: String, minFeederNumber: Int, feederStoragePath: StoragePath, feederPublicPath: PublicPath, readerStoragePath: StoragePath) + pub fun addFeederWhiteList(feederAddr: Address) + pub fun addReaderWhiteList(readerAddr: Address) + pub fun delFeederWhiteList(feederAddr: Address) + pub fun delReaderWhiteList(readerAddr: Address) + pub fun getFeederWhiteListPrice(): [UFix64] + } +} diff --git a/contracts/community/PublicPriceOracle.cdc b/contracts/community/PublicPriceOracle.cdc new file mode 100644 index 00000000..a7d4ab02 --- /dev/null +++ b/contracts/community/PublicPriceOracle.cdc @@ -0,0 +1,106 @@ +/** + +# This contract provides public oracle data sourced from the multi-node PriceOracle contracts of Increment. + +# Anyone can access price data directly with getLatestPrice() & getLatestBlockHeight(), no whitelist needed. Check example here: https://docs.increment.fi/protocols/decentralized-price-feed-oracle/using-price-feeds + +# Admin controls what PriceOracles are exposed publicly. + +# Author: Increment Labs + +*/ +import OracleInterface from "./OracleInterface.cdc" +import OracleConfig from "./OracleConfig.cdc" + + +pub contract PublicPriceOracle { + /// {OracleAddr: PriceIdentifier} + access(self) let oracleAddrToPriceIdentifier: {Address: String} + + /// The storage path for the Admin resource + pub let OracleAdminStoragePath: StoragePath + + /// Reserved parameter fields: {ParamName: Value} + access(self) let _reservedFields: {String: AnyStruct} + + /// Events + pub event OracleAdded(oracleAddr: Address) + pub event OracleRemoved(oracleAddr: Address) + + + /// Get the price data from the most recent update. + /// The data is updated whichever condition happens first: + /// 1. The price deviation is beyond a threahold (by default 0.5%) + /// 2. A fixed window of time has passed (by default 2000 blocks) + /// Note: It is recommended to check the updated block height of this data with getLatestBlockHeight(), and handle the extreme condition if this data is too old. + /// + pub fun getLatestPrice(oracleAddr: Address): UFix64 { + let oraclePublicInterface_ReaderRef = getAccount(oracleAddr).getCapability<&{OracleInterface.OraclePublicInterface_Reader}>(OracleConfig.OraclePublicInterface_ReaderPath).borrow() + ?? panic("Lost oracle public capability at ".concat(oracleAddr.toString())) + let priceReaderSuggestedPath = oraclePublicInterface_ReaderRef.getPriceReaderStoragePath() + let priceReaderRef = PublicPriceOracle.account.borrow<&OracleInterface.PriceReader>(from: priceReaderSuggestedPath) + ?? panic("Lost local price reader resource.") + let medianPrice = priceReaderRef.getRawMedianPrice() + return medianPrice + } + + /// Get the block height at the time of the latest update. + /// + pub fun getLatestBlockHeight(oracleAddr: Address): UInt64 { + let oraclePublicInterface_ReaderRef = getAccount(oracleAddr).getCapability<&{OracleInterface.OraclePublicInterface_Reader}>(OracleConfig.OraclePublicInterface_ReaderPath).borrow() + ?? panic("Lost oracle public capability at ".concat(oracleAddr.toString())) + let priceReaderSuggestedPath = oraclePublicInterface_ReaderRef.getPriceReaderStoragePath() + let priceReaderRef = PublicPriceOracle.account.borrow<&OracleInterface.PriceReader>(from: priceReaderSuggestedPath) + ?? panic("Lost local price reader resource.") + + let medianBlockHeight: UInt64 = priceReaderRef.getRawMedianBlockHeight() + return medianBlockHeight + } + + pub fun getAllSupportedOracles(): {Address: String} { + return self.oracleAddrToPriceIdentifier + } + + pub resource Admin { + + pub fun addOracle(oracleAddr: Address) { + if (!PublicPriceOracle.oracleAddrToPriceIdentifier.containsKey(oracleAddr)) { + /// Mint oracle reader + let oraclePublicInterface_ReaderRef = getAccount(oracleAddr).getCapability<&{OracleInterface.OraclePublicInterface_Reader}>(OracleConfig.OraclePublicInterface_ReaderPath).borrow() + ?? panic("Lost oracle public capability at ".concat(oracleAddr.toString())) + let priceReaderSuggestedPath = oraclePublicInterface_ReaderRef.getPriceReaderStoragePath() + + if (PublicPriceOracle.account.borrow<&OracleInterface.PriceReader>(from: priceReaderSuggestedPath) == nil) { + let priceReader <- oraclePublicInterface_ReaderRef.mintPriceReader() + + destroy <- PublicPriceOracle.account.load<@AnyResource>(from: priceReaderSuggestedPath) + + PublicPriceOracle.oracleAddrToPriceIdentifier[oracleAddr] = priceReader.getPriceIdentifier() + PublicPriceOracle.account.save(<- priceReader, to: priceReaderSuggestedPath) + } + + emit OracleAdded(oracleAddr: oracleAddr) + } + } + + pub fun removeOracle(oracleAddr: Address) { + PublicPriceOracle.oracleAddrToPriceIdentifier.remove(key: oracleAddr) + /// Remove local oracle reader resource + let oraclePublicInterface_ReaderRef = getAccount(oracleAddr).getCapability<&{OracleInterface.OraclePublicInterface_Reader}>(OracleConfig.OraclePublicInterface_ReaderPath).borrow() + ?? panic("Lost oracle public capability at ".concat(oracleAddr.toString())) + let priceReaderSuggestedPath = oraclePublicInterface_ReaderRef.getPriceReaderStoragePath() + destroy <- PublicPriceOracle.account.load<@AnyResource>(from: priceReaderSuggestedPath) + + emit OracleRemoved(oracleAddr: oracleAddr) + } + } + + init() { + self.OracleAdminStoragePath = /storage/publicOracleAdmin + self.oracleAddrToPriceIdentifier = {} + self._reservedFields = {} + + destroy <-self.account.load<@AnyResource>(from: self.OracleAdminStoragePath) + self.account.save(<-create Admin(), to: self.OracleAdminStoragePath) + } +}