diff --git a/FlowActions b/FlowActions index 6769d4c9..9788d6ee 160000 --- a/FlowActions +++ b/FlowActions @@ -1 +1 @@ -Subproject commit 6769d4c9f9ded4a5b4404d8c982300e84ccef532 +Subproject commit 9788d6ee9f71e29d19643960ccb86738751065c4 diff --git a/cadence/tests/external_oracle_test.cdc b/cadence/tests/external_oracle_test.cdc new file mode 100644 index 00000000..df80aeb5 --- /dev/null +++ b/cadence/tests/external_oracle_test.cdc @@ -0,0 +1,93 @@ +#test_fork(network: "mainnet", height: nil) + +import Test + +import "FlowToken" +import "MOET" +import "EVMVMBridgedToken_99af3eea856556646c98c8b9b2548fe815240750" // PYUSD0 + +access(all) let PYUSD0VaultType = Type<@EVMVMBridgedToken_99af3eea856556646c98c8b9b2548fe815240750.Vault>() + +access(all) fun setup() { + var err: Test.Error? = nil + + // TODO(holyfuchs): + // remove this once this is deployed to mainnet: holyfuchs/incrementfi-price-oracle + err = Test.deployContract( + name: "IncrementFiSwapConnectors", + path: "../../FlowActions/cadence/contracts/connectors/increment-fi/IncrementFiSwapConnectors.cdc", + arguments: [] + ) + Test.expect(err, Test.beNil()) +} + +access(all) fun test_band() { + let feeAccount = Test.createAccount() + let txn = Test.Transaction( + code: Test.readFile("transactions/external_oracle/create_band_empty_fee.cdc"), + authorizers: [feeAccount.address], + signers: [feeAccount], + arguments: [Type<@MOET.Vault>()] + ) + let result = Test.executeTransaction(txn) + Test.expect(result, Test.beSucceeded()) + + let unitScriptResult = Test.executeScript( + Test.readFile("scripts/external_oracle/band_unit_of_account.cdc"), + [feeAccount.address] + ) + Test.expect(unitScriptResult, Test.beSucceeded()) + let unitId = unitScriptResult.returnValue! as! String? + log(unitId) + Test.assert(unitId != nil, message: "expected unitOfAccount identifier") + Test.assert(unitId!.length > 0, message: "expected non-empty unitOfAccount") +} + +access(all) fun test_band_price() { + let feeAccount = Test.createAccount() + let txn = Test.Transaction( + code: Test.readFile("transactions/external_oracle/create_band_empty_fee.cdc"), + authorizers: [feeAccount.address], + signers: [feeAccount], + arguments: [Type<@MOET.Vault>()] + ) + Test.expect(Test.executeTransaction(txn), Test.beSucceeded()) + + let priceScriptResult = Test.executeScript( + Test.readFile("scripts/external_oracle/band_price.cdc"), + [feeAccount.address, Type<@FlowToken.Vault>()] + ) + Test.expect(priceScriptResult, Test.beSucceeded()) + let price = priceScriptResult.returnValue as! UFix64? + log(price) + Test.assert(price != nil, message: "expected price, got nil") +} + +access(all) fun test_increment_fi() { + let flowKey = String.join(Type<@FlowToken.Vault>().identifier.split(separator: ".").slice(from: 0, upTo: 3), separator: ".") + let moetKey = String.join(Type<@MOET.Vault>().identifier.split(separator: ".").slice(from: 0, upTo: 3), separator: ".") + let path = [flowKey, moetKey] + let unitScriptResult = Test.executeScript( + Test.readFile("scripts/external_oracle/increment_fi_unit_of_account.cdc"), + [Type<@MOET.Vault>(), Type<@FlowToken.Vault>(), path] + ) + Test.expect(unitScriptResult, Test.beSucceeded()) + let unitId = unitScriptResult.returnValue! as! String? + Test.assert(unitId != nil, message: "expected unitOfAccount identifier") + Test.assert(unitId!.length > 0, message: "expected non-empty unitOfAccount") +} + +access(all) fun test_increment_fi_price() { + let flowKey = String.join(Type<@FlowToken.Vault>().identifier.split(separator: ".").slice(from: 0, upTo: 3), separator: ".") + let pyusd0Key = String.join(PYUSD0VaultType.identifier.split(separator: ".").slice(from: 0, upTo: 3), separator: ".") + let path = [flowKey, pyusd0Key] + let priceScriptResult = Test.executeScript( + Test.readFile("scripts/external_oracle/increment_fi_price.cdc"), + [PYUSD0VaultType, Type<@FlowToken.Vault>(), path] + ) + Test.expect(priceScriptResult, Test.beSucceeded()) + let price = priceScriptResult.returnValue as! UFix64? + log(price) + Test.assert(price != nil, message: "expected price when pair exists") +} + diff --git a/cadence/tests/scripts/external_oracle/band_price.cdc b/cadence/tests/scripts/external_oracle/band_price.cdc new file mode 100644 index 00000000..db8db272 --- /dev/null +++ b/cadence/tests/scripts/external_oracle/band_price.cdc @@ -0,0 +1,12 @@ +import "DeFiActions" + +/// Borrows the Band oracle as DeFiActions.PriceOracle at +/// /public/bandOraclePriceOracle (created by create_band_empty_fee.cdc) +/// and returns the price of the given token type. +access(all) fun main(ownerAddress: Address, ofToken: Type): UFix64? { + let oracleRef = getAccount(ownerAddress).capabilities.borrow<&{DeFiActions.PriceOracle}>(/public/bandOraclePriceOracle) + if oracleRef == nil { + return nil + } + return oracleRef!.price(ofToken: ofToken) +} diff --git a/cadence/tests/scripts/external_oracle/band_unit_of_account.cdc b/cadence/tests/scripts/external_oracle/band_unit_of_account.cdc new file mode 100644 index 00000000..62736899 --- /dev/null +++ b/cadence/tests/scripts/external_oracle/band_unit_of_account.cdc @@ -0,0 +1,12 @@ +import "DeFiActions" + +/// Borrows the Band oracle as DeFiActions.PriceOracle at +/// /public/bandOraclePriceOracle (created by create_band_empty_fee.cdc) +/// and returns the unitOfAccount type identifier. +access(all) fun main(ownerAddress: Address): String? { + let oracleRef = getAccount(ownerAddress).capabilities.borrow<&{DeFiActions.PriceOracle}>(/public/bandOraclePriceOracle) + if oracleRef == nil { + return nil + } + return oracleRef!.unitOfAccount().identifier +} diff --git a/cadence/tests/scripts/external_oracle/increment_fi_price.cdc b/cadence/tests/scripts/external_oracle/increment_fi_price.cdc new file mode 100644 index 00000000..691cb0d9 --- /dev/null +++ b/cadence/tests/scripts/external_oracle/increment_fi_price.cdc @@ -0,0 +1,15 @@ +import "DeFiActions" +import "IncrementFiSwapConnectors" + +/// Builds a DeFiActions.PriceOracle using IncrementFiSwapConnectors.PriceOracle +/// and returns price(ofToken: FLOW). +access(all) fun main(unitOfAccount: Type, baseToken: Type, path: [String]): UFix64? { + let oracle = IncrementFiSwapConnectors.PriceOracle( + unitOfAccount: unitOfAccount, + baseToken: baseToken, + path: path, + uniqueID: nil + ) + let price = oracle.price(ofToken: baseToken) + return price +} diff --git a/cadence/tests/scripts/external_oracle/increment_fi_unit_of_account.cdc b/cadence/tests/scripts/external_oracle/increment_fi_unit_of_account.cdc new file mode 100644 index 00000000..c7d0bc4e --- /dev/null +++ b/cadence/tests/scripts/external_oracle/increment_fi_unit_of_account.cdc @@ -0,0 +1,16 @@ +import "DeFiActions" +import "FlowToken" +import "USDCFlow" +import "IncrementFiSwapConnectors" + +/// Builds a DeFiActions.PriceOracle using IncrementFiSwapConnectors.PriceOracle +/// and returns the unitOfAccount identifier +access(all) fun main(unitOfAccount: Type, baseToken: Type, path: [String]): String? { + let oracle = IncrementFiSwapConnectors.PriceOracle( + unitOfAccount: unitOfAccount, + baseToken: baseToken, + path: path, + uniqueID: nil + ) + return oracle.unitOfAccount().identifier +} diff --git a/cadence/tests/transactions/external_oracle/create_band_empty_fee.cdc b/cadence/tests/transactions/external_oracle/create_band_empty_fee.cdc new file mode 100644 index 00000000..3e4f8739 --- /dev/null +++ b/cadence/tests/transactions/external_oracle/create_band_empty_fee.cdc @@ -0,0 +1,29 @@ +import "FungibleToken" +import "FlowToken" +import "FungibleTokenConnectors" +import "BandOracleConnectors" + +/// Creates a BandOracleConnectors.PriceOracle with the given unitOfAccount and +/// an empty FlowToken vault as the fee source, saves it to storage, and +/// publishes a capability at /public/bandOraclePriceOracle +transaction(unitOfAccount: Type) { + prepare(signer: auth(BorrowValue, SaveValue, Capabilities, IssueStorageCapabilityController, PublishCapability) &Account) { + let flowTokenAccount = getAccount(Type<@FlowToken.Vault>().address!) + let flowTokenRef = flowTokenAccount.contracts.borrow<&{FungibleToken}>(name: "FlowToken") + ?? panic("FlowToken contract not found") + let emptyVault <- flowTokenRef.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) + signer.storage.save(<-emptyVault, to: /storage/flowFeeVault) + + let cap = signer.capabilities.storage.issue(/storage/flowFeeVault) + let feeSource = FungibleTokenConnectors.VaultSource(min: nil, withdrawVault: cap, uniqueID: nil) + let oracle = BandOracleConnectors.PriceOracle( + unitOfAccount: unitOfAccount, + staleThreshold: 3600, + feeSource: feeSource, + uniqueID: nil + ) + signer.storage.save(oracle, to: /storage/bandOraclePriceOracle) + let oracleCap = signer.capabilities.storage.issue<&BandOracleConnectors.PriceOracle>(/storage/bandOraclePriceOracle) + signer.capabilities.publish(oracleCap, at: /public/bandOraclePriceOracle) + } +}