Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,79 @@ access(all) contract IncrementFiSwapConnectors {
}
}

/// PriceOracle
///
/// A DeFiActions connector that provides price data for a given asset using IncrementFi's SwapRouter spot price
/// (getAmountsOut for 1 unit of the base token along a path to the unit-of-account token).
///
access(all) struct PriceOracle : DeFiActions.PriceOracle {
/// The token type serving as the price basis - e.g. USDC in FLOW/USDC
access(self) let quote: Type
/// The base token type that this oracle can price (path starts with this token)
access(self) let baseToken: Type
/// Swap path from baseToken to quote as defined by IncrementFi's SwapRouter
access(self) let path: [String]
/// The unique ID of the PriceOracle
access(contract) var uniqueID: DeFiActions.UniqueIdentifier?

init(
unitOfAccount: Type,
baseToken: Type,
path: [String],
uniqueID: DeFiActions.UniqueIdentifier?
) {
pre {
path.length >= 2:
"Provided path must have a length of at least 2 - provided path has \(path.length) elements"
unitOfAccount.isSubtype(of: Type<@{FungibleToken.Vault}>()):
"Invalid unitOfAccount - \(unitOfAccount.identifier) is not a FungibleToken.Vault implementation"
baseToken.isSubtype(of: Type<@{FungibleToken.Vault}>()):
"Invalid baseToken - \(baseToken.identifier) is not a FungibleToken.Vault implementation"
}
IncrementFiSwapConnectors._validateSwapperInitArgs(path: path, inVault: baseToken, outVault: unitOfAccount)

self.quote = unitOfAccount
self.baseToken = baseToken
self.path = path
self.uniqueID = uniqueID
}

/// Returns a ComponentInfo struct containing information about this PriceOracle and its inner DFA components
access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
return DeFiActions.ComponentInfo(
type: self.getType(),
id: self.id(),
innerComponents: []
)
}
access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
return self.uniqueID
}
access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
self.uniqueID = id
}
/// Returns the asset type serving as the price basis - e.g. USDC in FLOW/USDC
access(all) view fun unitOfAccount(): Type {
return self.quote
}
/// Returns the latest price for ofToken denominated in unitOfAccount(), using the pool spot price from
/// SwapRouter.getAmountsOut. Returns nil if ofToken is not the base or quote token for this oracle.
access(all) fun price(ofToken: Type): UFix64? {
if ofToken == self.quote {
return 1.0
}
if ofToken != self.baseToken {
return nil
}
let amountsOut = SwapRouter.getAmountsOut(amountIn: 1.0, tokenKeyPath: self.path)
let priceAmount = amountsOut.length > 0 ? amountsOut[amountsOut.length - 1] : 0.0
if priceAmount <= 0.0 {
return nil
}
return priceAmount
}
}

/* --- INTERNAL HELPERS --- */

/// Reverts if the in and out Vaults are not defined by the token key path identifiers as used by IncrementFi's
Expand Down
19 changes: 19 additions & 0 deletions cadence/scripts/increment-fi-adapters/get_price.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import "DeFiActions"
import "IncrementFiSwapConnectors"

access(all)
fun main(
unitOfAccountIdentifier: String,
baseTokenIdentifier: String,
path: [String],
ofTokenIdentifier: String
): UFix64? {
let oracle = IncrementFiSwapConnectors.PriceOracle(
unitOfAccount: CompositeType(unitOfAccountIdentifier) ?? panic("Invalid unitOfAccount \(unitOfAccountIdentifier)"),
baseToken: CompositeType(baseTokenIdentifier) ?? panic("Invalid baseToken \(baseTokenIdentifier)"),
path: path,
uniqueID: nil
)
let ofToken = CompositeType(ofTokenIdentifier) ?? panic("Invalid ofToken \(ofTokenIdentifier)")
return oracle.price(ofToken: ofToken)
}
22 changes: 22 additions & 0 deletions cadence/tests/IncrementFiSwapConnectors_test.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,25 @@ fun testAdapterGetAmountsOutSucceeds() {
let quote = amountsOutRes.returnValue! as! {DeFiActions.Quote}
Test.assertEqual(inAmount, quote.inAmount)
}

access(all)
fun testPriceOracleGetPrice() {
let path = [tokenAKey, tokenBKey]
let priceOfBaseRes = executeScript(
"../scripts/increment-fi-adapters/get_price.cdc",
[tokenBIdentifier, tokenAIdentifier, path, tokenAIdentifier]
)
Test.expect(priceOfBaseRes, Test.beSucceeded())
let priceOfBase = priceOfBaseRes.returnValue as! UFix64?
Test.assert(priceOfBase != nil, message: "Price of base token should be non-nil")
Test.assert(priceOfBase! > 0.0, message: "Price of base token should be positive")
log(priceOfBase!)
let priceOfQuoteRes = executeScript(
"../scripts/increment-fi-adapters/get_price.cdc",
[tokenBIdentifier, tokenAIdentifier, path, tokenBIdentifier]
)
Test.expect(priceOfQuoteRes, Test.beSucceeded())
let priceOfQuote = priceOfQuoteRes.returnValue
Test.assert(priceOfQuote != nil, message: "Price of quote token should be non-nil")
Test.assertEqual(priceOfQuote, 1.0 as UFix64?)
}
Loading