Smart Contract
BandOracleConnectors
A.e36ef556b8b5d955.BandOracleConnectors
1import Burner from 0xf233dcee88fe0abe
2import FungibleToken from 0xf233dcee88fe0abe
3import FlowToken from 0x1654653399040a61
4import BandOracle from 0x6801a6222ebf784a
5
6import DeFiActions from 0x6d888f175c158410
7
8/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
9/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT
10/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
11///
12/// BandOracleConnectors
13///
14/// This contract adapts BandOracle's price data oracle contracts for use as a DeFiActions PriceOracle connector
15///
16access(all) contract BandOracleConnectors {
17
18 /// Mapping of asset Types to BandOracle symbols
19 access(all) let assetSymbols: {Type: String}
20 /// StoragePath for the SymbolUpdater tasked with adding Type:SYMBOL pairs
21 access(all) let SymbolUpdaterStoragePath: StoragePath
22
23 /* EVENTS */
24 /// Emitted when a Type:SYMBOL pair is added via the SymbolUpdater resource
25 access(all) event SymbolAdded(symbol: String, asset: String)
26
27 /* CONSTRUCTS */
28
29 /// PriceOracle
30 ///
31 /// A DeFiActions connector that provides price data for a given asset using BandOracle
32 ///
33 access(all) struct PriceOracle : DeFiActions.PriceOracle {
34 /// The token type serving as the price basis - e.g. USD in FLOW/USD
35 access(self) let quote: Type
36 /// A Source providing the FlowToken necessary for BandOracle price data requests
37 access(self) let feeSource: {DeFiActions.Source}
38 /// The amount of seconds beyond which a price is considered stale and a price() call reverts
39 access(self) let staleThreshold: UInt64?
40 /// The unique ID of the PriceOracle
41 access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
42
43 init(unitOfAccount: Type, staleThreshold: UInt64?, feeSource: {DeFiActions.Source}, uniqueID: DeFiActions.UniqueIdentifier?) {
44 pre {
45 feeSource.getSourceType() == Type<@FlowToken.Vault>():
46 "Invalid feeSource - given Source must provide FlowToken Vault, but provides \(feeSource.getSourceType().identifier)"
47 unitOfAccount.isSubtype(of: Type<@{FungibleToken.Vault}>()):
48 "Invalid unitOfAccount - \(unitOfAccount.identifier) is not a FungibleToken.Vault implementation"
49 BandOracleConnectors.assetSymbols[unitOfAccount] != nil:
50 "Could not find a BandOracle symbol assigned to unitOfAccount \(unitOfAccount.identifier)"
51 }
52 self.feeSource = feeSource
53 self.quote = unitOfAccount
54 self.staleThreshold = staleThreshold
55 self.uniqueID = uniqueID
56 }
57
58 /// Returns a ComponentInfo struct containing information about this PriceOracle and its inner DFA components
59 ///
60 /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
61 /// each inner component in the stack.
62 ///
63 access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
64 return DeFiActions.ComponentInfo(
65 type: self.getType(),
66 id: self.id(),
67 innerComponents: [
68 self.feeSource.getComponentInfo()
69 ]
70 )
71 }
72 /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
73 /// a DeFiActions stack. See DeFiActions.align() for more information.
74 ///
75 /// @return a copy of the struct's UniqueIdentifier
76 ///
77 access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
78 return self.uniqueID
79 }
80 /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
81 /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
82 ///
83 /// @param id: the UniqueIdentifier to set for this component
84 ///
85 access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
86 self.uniqueID = id
87 }
88 /// Returns the asset type serving as the price basis - e.g. USD in FLOW/USD
89 access(all) view fun unitOfAccount(): Type {
90 return self.quote
91 }
92 /// Returns the latest price data for a given asset denominated in unitOfAccount(). Since BandOracle requests
93 /// are paid, this implementation reverts if price data has gone stale
94 access(all) fun price(ofToken: Type): UFix64? {
95 // lookup the symbols
96 let baseSymbol = BandOracleConnectors.assetSymbols[ofToken]
97 ?? panic("Base asset type \(ofToken.identifier) does not have an assigned symbol")
98 let quoteSymbol = BandOracleConnectors.assetSymbols[self.unitOfAccount()]!
99 // withdraw the oracle fee & get the price data from BandOracle
100 let fee <- self.feeSource.withdrawAvailable(maxAmount: BandOracle.getFee())
101 let priceData = BandOracle.getReferenceData(baseSymbol: baseSymbol, quoteSymbol: quoteSymbol, payment: <-fee)
102
103 // check price data has not gone stale based on last updated timestamp
104 let now = UInt64(getCurrentBlock().timestamp)
105 if self.staleThreshold != nil {
106 assert(now < priceData.baseTimestamp + self.staleThreshold!,
107 message: "Price data's base timestamp \(priceData.baseTimestamp) exceeds the staleThreshold "
108 .concat("\(priceData.baseTimestamp + self.staleThreshold!) at current timestamp \(now)"))
109 assert(now < priceData.quoteTimestamp + self.staleThreshold!,
110 message: "Price data's quote timestamp \(priceData.quoteTimestamp) exceeds the staleThreshold "
111 .concat("\(priceData.quoteTimestamp + self.staleThreshold!) at current timestamp \(now)"))
112 }
113
114 return priceData.fixedPointRate
115 }
116 }
117
118 // SymbolUpdater
119 //
120 /// Resource enabling the addition of new Type:SYMBOL pairings as they are supported by BandOracle's price oracle
121 access(all) resource SymbolUpdater {
122 /// Adds a Type:SYMBOL pairing to the contract's mapping. Reverts if the asset Type is already assigned a symbol
123 access(all) fun addSymbol(_ symbol: String, forAsset: Type) {
124 pre {
125 BandOracleConnectors.assetSymbols[forAsset] == nil:
126 "Asset \(forAsset.identifier) is already assigned symbol \(BandOracleConnectors.assetSymbols[forAsset]!)"
127 }
128 BandOracleConnectors.assetSymbols[forAsset] = symbol
129
130 emit SymbolAdded(symbol: symbol, asset: forAsset.identifier)
131 }
132 }
133
134 init() {
135 self.assetSymbols = {
136 Type<@FlowToken.Vault>(): "FLOW"
137 }
138 self.SymbolUpdaterStoragePath = StoragePath(identifier: "BandOracleConnectorSymbolUpdater_\(self.account.address)")!
139 self.account.storage.save(<-create SymbolUpdater(), to: self.SymbolUpdaterStoragePath)
140 }
141}
142