-
Notifications
You must be signed in to change notification settings - Fork 21
Add Stork Oracle Adapter #88
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
pragma solidity ^0.8.0; | ||
|
||
interface IStorkTemporalNumericValueUnsafeGetter { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The minimal required interface to get prices from our contract |
||
function getTemporalNumericValueUnsafeV1( | ||
bytes32 id | ||
) external view returns (StorkStructs.TemporalNumericValue memory value); | ||
} | ||
|
||
contract StorkStructs { | ||
struct TemporalNumericValue { | ||
uint64 timestampNs; // nanosecond level precision timestamp of latest publisher update in batch | ||
int192 quantizedValue; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
pragma solidity ^0.8.0; | ||
|
||
import "../../lib/ScaleUtils.sol"; | ||
import "./IStork.sol"; | ||
import {BaseAdapter, Errors, IPriceOracle} from "../BaseAdapter.sol"; | ||
import {IStorkTemporalNumericValueUnsafeGetter} from "./IStork.sol"; | ||
|
||
/// @title StorkOracle | ||
/// @custom:security-contact security@euler.xyz | ||
/// @author Stork Labs (https://www.stork.network/) | ||
/// @notice PriceOracle adapter for Stork price feeds. | ||
contract StorkOracle is BaseAdapter { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Modeled on the existing PythOracle adapter. Slightly simplified since Stork doesn't have a concept of confidence intervals, and doesn't have different exponents for different feeds (every value is multiplied by 10**18) |
||
/// @notice The maximum length of time that a price can be in the future. | ||
uint256 internal constant MAX_AHEADNESS = 1 minutes; | ||
/// @notice The maximum permitted value for `maxStaleness`. | ||
uint256 internal constant MAX_STALENESS_UPPER_BOUND = 15 minutes; | ||
// @notice The number of decimals in values returned by the Stork contract. | ||
int8 internal constant STORK_DECIMALS = 18; | ||
/// @inheritdoc IPriceOracle | ||
string public constant name = "StorkOracle"; | ||
/// @notice The address of the Stork oracle proxy. | ||
address public immutable stork; | ||
/// @notice The address of the base asset corresponding to the feed. | ||
address public immutable base; | ||
/// @notice The address of the quote asset corresponding to the feed. | ||
address public immutable quote; | ||
/// @notice The id of the feed in the Stork network. | ||
/// @dev See https://docs.stork.network/resources/asset-id-registry. | ||
bytes32 public immutable feedId; | ||
/// @notice The maximum allowed age of the price. | ||
uint256 public immutable maxStaleness; | ||
/// @dev Used for correcting for the decimals of base and quote. | ||
uint8 internal immutable baseDecimals; | ||
/// @dev Used for correcting for the decimals of base and quote. | ||
uint8 internal immutable quoteDecimals; | ||
|
||
/// @notice Deploy a StorkOracle. | ||
/// @param _stork The address of the Stork oracle proxy. | ||
/// @param _base The address of the base asset corresponding to the feed. | ||
/// @param _quote The address of the quote asset corresponding to the feed. | ||
/// @param _feedId The id of the feed in the Stork network. | ||
/// @param _maxStaleness The maximum allowed age of the price. | ||
constructor( | ||
address _stork, | ||
address _base, | ||
address _quote, | ||
bytes32 _feedId, | ||
uint256 _maxStaleness | ||
) { | ||
if (_maxStaleness > MAX_STALENESS_UPPER_BOUND) { | ||
revert Errors.PriceOracle_InvalidConfiguration(); | ||
} | ||
|
||
stork = _stork; | ||
base = _base; | ||
quote = _quote; | ||
feedId = _feedId; | ||
maxStaleness = _maxStaleness; | ||
baseDecimals = _getDecimals(base); | ||
quoteDecimals = _getDecimals(quote); | ||
} | ||
|
||
/// @notice Fetch the latest Stork price and transform it to a quote. | ||
/// @param inAmount The amount of `base` to convert. | ||
/// @param _base The token that is being priced. | ||
/// @param _quote The token that is the unit of account. | ||
/// @return The converted amount. | ||
function _getQuote(uint256 inAmount, address _base, address _quote) internal view override returns (uint256) { | ||
bool inverse = ScaleUtils.getDirectionOrRevert(_base, base, _quote, quote); | ||
|
||
StorkStructs.TemporalNumericValue memory temporalNumericValue = _fetchTemporalNumericValue(); | ||
|
||
uint256 value = uint256(uint192(temporalNumericValue.quantizedValue)); | ||
int8 feedExponent = int8(baseDecimals) + STORK_DECIMALS; | ||
|
||
Scale scale; | ||
if (feedExponent > 0) { | ||
scale = ScaleUtils.from(quoteDecimals, uint8(feedExponent)); | ||
} else { | ||
scale = ScaleUtils.from(quoteDecimals + uint8(-feedExponent), 0); | ||
} | ||
return ScaleUtils.calcOutAmount(inAmount, value, scale, inverse); | ||
} | ||
|
||
/// @notice Get the latest Stork price and perform sanity checks. | ||
/// @dev Revert conditions: update timestamp is too stale or too ahead, price is negative or zero, | ||
/// @return The Stork price struct without modification. | ||
function _fetchTemporalNumericValue() internal view returns (StorkStructs.TemporalNumericValue memory) { | ||
StorkStructs.TemporalNumericValue memory v = IStorkTemporalNumericValueUnsafeGetter(stork).getTemporalNumericValueUnsafeV1(feedId); | ||
uint256 publishTimestampSeconds = v.timestampNs / 1e9; | ||
if (publishTimestampSeconds < block.timestamp) { | ||
// Verify that the price is not too stale | ||
uint256 staleness = block.timestamp - publishTimestampSeconds; | ||
if (staleness > maxStaleness) revert Errors.PriceOracle_InvalidAnswer(); | ||
} else { | ||
// Verify that the price is not too ahead | ||
uint256 aheadness = publishTimestampSeconds - block.timestamp; | ||
if (aheadness > MAX_AHEADNESS) revert Errors.PriceOracle_InvalidAnswer(); | ||
} | ||
|
||
if (v.quantizedValue <= 0) { | ||
revert Errors.PriceOracle_InvalidAnswer(); | ||
} | ||
return v; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
pragma solidity ^0.8.0; | ||
|
||
import "./StorkOracleHelper.sol"; | ||
|
||
contract StorkOracleBoundsTest is StorkOracleHelper { | ||
function test_Bounds(FuzzableState memory s) public { | ||
setBounds( | ||
Bounds({ | ||
minBaseDecimals: 0, | ||
maxBaseDecimals: 18, | ||
minQuoteDecimals: 0, | ||
maxQuoteDecimals: 18, | ||
minInAmount: 0, | ||
maxInAmount: type(uint128).max, | ||
minPrice: 1, | ||
maxPrice: 1_000_000_000_000 | ||
}) | ||
); | ||
setUpState(s); | ||
setUpOracle(s); | ||
|
||
uint256 outAmount = StorkOracle(oracle).getQuote(s.inAmount, s.base, s.quote); | ||
assertEq(outAmount, calcOutAmount(s)); | ||
|
||
uint256 outAmountInverse = StorkOracle(oracle).getQuote(s.inAmount, s.quote, s.base); | ||
assertEq(outAmountInverse, calcOutAmountInverse(s)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
pragma solidity ^0.8.0; | ||
|
||
|
||
import {ForkTest} from "test/utils/ForkTest.sol"; | ||
import {StorkOracle} from "../../../src/adapter/stork/StorkOracle.sol"; | ||
import {StorkStructs, IStorkTemporalNumericValueUnsafeGetter} from "../../../src/adapter/stork/IStork.sol"; | ||
import {BTC, USD} from "test/utils/EthereumAddresses.sol"; | ||
import {stdStorage, StdStorage} from "forge-std/StdStorage.sol"; | ||
|
||
|
||
contract StorkOracleForkTest is ForkTest { | ||
using stdStorage for StdStorage; | ||
|
||
StorkOracle oracle; | ||
|
||
address storkContractAddress = 0x035B5438444f26e6Aab81E91d475b7B1Ac4Fb22b; | ||
bytes32 feedId = 0x7404e3d104ea7841c3d9e6fd20adfe99b4ad586bc08d8f3bd3afef894cf184de; // BTCUSD | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm just hardcoding this value here since feed IDs aren't used in any other tests. Would it be helpful if we added a |
||
address base = BTC; | ||
address quote = USD; | ||
uint256 blockNumber = 22241301; | ||
uint256 expectedValue = 79749.8e18; | ||
|
||
function setUp() public { | ||
_setUpFork(blockNumber); | ||
} | ||
|
||
function test_GetQuote_Integrity() public { | ||
oracle = new StorkOracle(storkContractAddress, base, quote, feedId, 15 minutes); | ||
|
||
uint256 outAmount = oracle.getQuote(1e18, base, quote); | ||
assertApproxEqRel(outAmount, expectedValue, 0.1e18); | ||
uint256 outAmountInverse = oracle.getQuote(expectedValue, quote, base); | ||
assertApproxEqRel(outAmountInverse, 1e18, 0.1e18); | ||
} | ||
|
||
function test_GetQuotes_Integrity() public { | ||
oracle = new StorkOracle(storkContractAddress, base, quote, feedId, 15 minutes); | ||
|
||
(uint256 bidOutAmount, uint256 askOutAmount) = oracle.getQuotes(1e18, base, quote); | ||
assertApproxEqRel(bidOutAmount, expectedValue, 0.1e18); | ||
assertApproxEqRel(askOutAmount, expectedValue, 0.1e18); | ||
assertEq(bidOutAmount, askOutAmount); | ||
|
||
(uint256 bidOutAmountInverse, uint256 askOutAmountInverse) = oracle.getQuotes(expectedValue, quote, base); | ||
assertApproxEqRel(bidOutAmountInverse, 1e18, 0.1e18); | ||
assertApproxEqRel(askOutAmountInverse, 1e18, 0.1e18); | ||
assertEq(bidOutAmountInverse, askOutAmountInverse); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
pragma solidity ^0.8.0; | ||
|
||
import {AdapterPropTest} from "test/adapter/AdapterPropTest.sol"; | ||
import {StorkOracleHelper} from "./StorkOracleHelper.sol"; | ||
|
||
contract StorkraclePropTest is StorkOracleHelper, AdapterPropTest { | ||
function testProp_Bidirectional(FuzzableState memory s, Prop_Bidirectional memory p) public { | ||
setUpPropTest(s); | ||
checkProp(p); | ||
} | ||
|
||
function testProp_NoOtherPaths(FuzzableState memory s, Prop_NoOtherPaths memory p) public { | ||
setUpPropTest(s); | ||
checkProp(p); | ||
} | ||
|
||
function testProp_IdempotentQuoteAndQuotes(FuzzableState memory s, Prop_IdempotentQuoteAndQuotes memory p) public { | ||
setUpPropTest(s); | ||
checkProp(p); | ||
} | ||
|
||
function testProp_SupportsZero(FuzzableState memory s, Prop_SupportsZero memory p) public { | ||
setUpPropTest(s); | ||
checkProp(p); | ||
} | ||
|
||
function testProp_ContinuousDomain(FuzzableState memory s, Prop_ContinuousDomain memory p) public { | ||
setUpPropTest(s); | ||
checkProp(p); | ||
} | ||
|
||
function testProp_OutAmountIncreasing(FuzzableState memory s, Prop_OutAmountIncreasing memory p) public { | ||
setUpPropTest(s); | ||
checkProp(p); | ||
} | ||
|
||
function setUpPropTest(FuzzableState memory s) internal { | ||
setUpState(s); | ||
setUpOracle(s); | ||
adapter = address(oracle); | ||
base = s.base; | ||
quote = s.quote; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
pragma solidity ^0.8.0; | ||
|
||
import "../../../src/adapter/stork/StorkOracle.sol"; | ||
import {Errors} from "src/lib/Errors.sol"; | ||
import {Test} from "forge-std/Test.sol"; | ||
import {boundAddr} from "test/utils/TestUtils.sol"; | ||
import {StorkOracle} from "../../../src/adapter/stork/StorkOracle.sol"; | ||
import {StorkOracleHelper} from "./StorkOracleHelper.sol"; | ||
|
||
contract StorkOracleTest is StorkOracleHelper { | ||
function test_Constructor_Integrity(FuzzableState memory s) public { | ||
setUpState(s); | ||
setUpOracle(s); | ||
|
||
assertEq(address(StorkOracle(oracle).stork()), STORK); | ||
assertEq(StorkOracle(oracle).base(), s.base); | ||
assertEq(StorkOracle(oracle).quote(), s.quote); | ||
assertEq(StorkOracle(oracle).feedId(), s.feedId); | ||
assertEq(StorkOracle(oracle).maxStaleness(), s.maxStaleness); | ||
} | ||
|
||
function test_Constructor_RevertsWhen_MaxStalenessTooHigh(FuzzableState memory s) public { | ||
setBehavior(Behavior.Constructor_MaxStalenessTooHigh, true); | ||
setUpState(s); | ||
vm.expectRevert(); | ||
setUpOracle(s); | ||
} | ||
|
||
function test_Quote_RevertsWhen_InvalidTokens(FuzzableState memory s, address otherA, address otherB) public { | ||
setUpState(s); | ||
setUpOracle(s); | ||
otherA = boundAddr(otherA); | ||
otherB = boundAddr(otherB); | ||
vm.assume(otherA != s.base && otherA != s.quote); | ||
vm.assume(otherB != s.base && otherB != s.quote); | ||
expectNotSupported(s.inAmount, s.base, s.base); | ||
expectNotSupported(s.inAmount, s.quote, s.quote); | ||
expectNotSupported(s.inAmount, s.base, otherA); | ||
expectNotSupported(s.inAmount, otherA, s.base); | ||
expectNotSupported(s.inAmount, s.quote, otherA); | ||
expectNotSupported(s.inAmount, otherA, s.quote); | ||
expectNotSupported(s.inAmount, otherA, otherA); | ||
expectNotSupported(s.inAmount, otherA, otherB); | ||
} | ||
|
||
function test_Quote_RevertsWhen_ZeroPrice(FuzzableState memory s) public { | ||
setBehavior(Behavior.FeedReturnsZeroPrice, true); | ||
setUpState(s); | ||
setUpOracle(s); | ||
|
||
bytes memory err = abi.encodeWithSelector(Errors.PriceOracle_InvalidAnswer.selector); | ||
expectRevertForAllQuotePermutations(s.inAmount, s.base, s.quote, err); | ||
} | ||
|
||
function test_Quote_RevertsWhen_NegativePrice(FuzzableState memory s) public { | ||
setBehavior(Behavior.FeedReturnsNegativePrice, true); | ||
setUpState(s); | ||
setUpOracle(s); | ||
|
||
bytes memory err = abi.encodeWithSelector(Errors.PriceOracle_InvalidAnswer.selector); | ||
expectRevertForAllQuotePermutations(s.inAmount, s.base, s.quote, err); | ||
} | ||
|
||
function test_Quote_RevertsWhen_StalePrice(FuzzableState memory s) public { | ||
setBehavior(Behavior.FeedReturnsStalePrice, true); | ||
setUpState(s); | ||
setUpOracle(s); | ||
|
||
bytes memory err = abi.encodeWithSelector(Errors.PriceOracle_InvalidAnswer.selector); | ||
expectRevertForAllQuotePermutations(s.inAmount, s.base, s.quote, err); | ||
} | ||
|
||
function test_Quote_RevertsWhen_AheadPrice(FuzzableState memory s) public { | ||
setBehavior(Behavior.FeedReturnsTooAheadPrice, true); | ||
setUpState(s); | ||
setUpOracle(s); | ||
|
||
bytes memory err = abi.encodeWithSelector(Errors.PriceOracle_InvalidAnswer.selector); | ||
expectRevertForAllQuotePermutations(s.inAmount, s.base, s.quote, err); | ||
} | ||
|
||
function test_Quote_Integrity(FuzzableState memory s) public { | ||
setUpState(s); | ||
setUpOracle(s); | ||
|
||
uint256 expectedOutAmount = calcOutAmount(s); | ||
uint256 outAmount = StorkOracle(oracle).getQuote(s.inAmount, s.base, s.quote); | ||
assertEq(outAmount, expectedOutAmount); | ||
|
||
(uint256 bidOutAmount, uint256 askOutAmount) = StorkOracle(oracle).getQuotes(s.inAmount, s.base, s.quote); | ||
assertEq(bidOutAmount, expectedOutAmount); | ||
assertEq(askOutAmount, expectedOutAmount); | ||
} | ||
|
||
function test_Quote_Integrity_Inverse(FuzzableState memory s) public { | ||
setUpState(s); | ||
setUpOracle(s); | ||
|
||
uint256 expectedOutAmount = calcOutAmountInverse(s); | ||
uint256 outAmount = StorkOracle(oracle).getQuote(s.inAmount, s.quote, s.base); | ||
assertEq(outAmount, expectedOutAmount); | ||
|
||
(uint256 bidOutAmount, uint256 askOutAmount) = StorkOracle(oracle).getQuotes(s.inAmount, s.quote, s.base); | ||
assertEq(bidOutAmount, expectedOutAmount); | ||
assertEq(askOutAmount, expectedOutAmount); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
pragma solidity ^0.8.0; | ||
|
||
import {Test} from "forge-std/Test.sol"; | ||
import {IERC20} from "forge-std/interfaces/IERC20.sol"; | ||
import {FixedPointMathLib} from "@solady/utils/FixedPointMathLib.sol"; | ||
import {AdapterHelper} from "test/adapter/AdapterHelper.sol"; | ||
import {boundAddr, distinct} from "test/utils/TestUtils.sol"; | ||
import {StubStork} from "./StubStork.sol"; | ||
import {StorkOracle} from "../../../src/adapter/stork/StorkOracle.sol"; | ||
import {StorkStructs} from "../../../src/adapter/stork/IStork.sol"; | ||
|
||
contract StorkOracleHelper is AdapterHelper { | ||
uint256 internal constant MAX_STALENESS_UPPER_BOUND = 15 minutes; | ||
|
||
struct Bounds { | ||
uint8 minBaseDecimals; | ||
uint8 maxBaseDecimals; | ||
uint8 minQuoteDecimals; | ||
uint8 maxQuoteDecimals; | ||
uint256 minInAmount; | ||
uint256 maxInAmount; | ||
int64 minPrice; | ||
int64 maxPrice; | ||
} | ||
|
||
Bounds internal DEFAULT_BOUNDS = Bounds({ | ||
minBaseDecimals: 0, | ||
maxBaseDecimals: 18, | ||
minQuoteDecimals: 0, | ||
maxQuoteDecimals: 18, | ||
minInAmount: 0, | ||
maxInAmount: type(uint128).max, | ||
minPrice: 1, | ||
maxPrice: 1_000_000_000_000 | ||
}); | ||
|
||
Bounds internal bounds = DEFAULT_BOUNDS; | ||
|
||
function setBounds(Bounds memory _bounds) internal { | ||
bounds = _bounds; | ||
} | ||
|
||
address STORK; | ||
|
||
struct FuzzableState { | ||
// Config | ||
address base; | ||
address quote; | ||
bytes32 feedId; | ||
uint256 maxStaleness; | ||
uint8 baseDecimals; | ||
uint8 quoteDecimals; | ||
// Answer | ||
StorkStructs.TemporalNumericValue v; | ||
// Environment | ||
uint256 inAmount; | ||
uint256 timestamp; | ||
} | ||
|
||
constructor() { | ||
STORK = address(new StubStork()); | ||
} | ||
|
||
function setUpState(FuzzableState memory s) internal { | ||
s.base = boundAddr(s.base); | ||
s.quote = boundAddr(s.quote); | ||
vm.assume(distinct(s.base, s.quote, STORK)); | ||
vm.assume(s.feedId != 0); | ||
|
||
if (behaviors[Behavior.Constructor_MaxStalenessTooHigh]) { | ||
s.maxStaleness = bound(s.maxStaleness, MAX_STALENESS_UPPER_BOUND + 1, type(uint128).max); | ||
} else { | ||
s.maxStaleness = bound(s.maxStaleness, 0, MAX_STALENESS_UPPER_BOUND); | ||
} | ||
|
||
s.baseDecimals = uint8(bound(s.baseDecimals, bounds.minBaseDecimals, bounds.maxBaseDecimals)); | ||
s.quoteDecimals = uint8(bound(s.quoteDecimals, bounds.minQuoteDecimals, bounds.maxQuoteDecimals)); | ||
|
||
vm.mockCall(s.base, abi.encodeWithSelector(IERC20.decimals.selector), abi.encode(s.baseDecimals)); | ||
vm.mockCall(s.quote, abi.encodeWithSelector(IERC20.decimals.selector), abi.encode(s.quoteDecimals)); | ||
|
||
if (behaviors[Behavior.FeedReturnsNegativePrice]) { | ||
s.v.quantizedValue = int192(bound(s.v.quantizedValue, type(int64).min, -1)); | ||
} else if (behaviors[Behavior.FeedReturnsZeroPrice]) { | ||
s.v.quantizedValue = 0; | ||
} else { | ||
s.v.quantizedValue = int192(bound(s.v.quantizedValue, bounds.minPrice, bounds.maxPrice)); | ||
} | ||
|
||
s.v.timestampNs = uint64(bound(uint256(s.v.timestampNs), (1 minutes + 1) * 1e9, type(uint64).max)); | ||
uint256 valueTimestampSeconds = uint256(s.v.timestampNs / 1e9); | ||
|
||
if (behaviors[Behavior.FeedReturnsStalePrice]) { | ||
s.timestamp = bound(s.timestamp, valueTimestampSeconds + s.maxStaleness + 1, type(uint144).max); | ||
} else if (behaviors[Behavior.FeedReturnsTooAheadPrice]) { | ||
s.timestamp = bound(s.timestamp, 0, valueTimestampSeconds - 1 minutes - 1); | ||
} else { | ||
s.timestamp = bound(s.timestamp, valueTimestampSeconds - 1 minutes, valueTimestampSeconds + s.maxStaleness); | ||
} | ||
|
||
if (behaviors[Behavior.FeedReverts]) { | ||
StubStork(STORK).setRevert(true); | ||
} else { | ||
StubStork(STORK).setPrice(s.v); | ||
} | ||
|
||
s.inAmount = bound(s.inAmount, 1, type(uint128).max); | ||
vm.warp(s.timestamp); | ||
} | ||
|
||
function setUpOracle(FuzzableState memory s) internal { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. separated this out from |
||
oracle = address(new StorkOracle(STORK, s.base, s.quote, s.feedId, s.maxStaleness)); | ||
} | ||
|
||
function calcOutAmount(FuzzableState memory s) internal pure returns (uint256) { | ||
int8 diff = int8(s.baseDecimals) + 18; | ||
if (diff > 0) { | ||
return FixedPointMathLib.fullMulDiv( | ||
s.inAmount, uint256(uint192(s.v.quantizedValue)) * 10 ** s.quoteDecimals, 10 ** (uint8(diff)) | ||
); | ||
} else { | ||
return FixedPointMathLib.fullMulDiv( | ||
s.inAmount, uint256(uint192(s.v.quantizedValue)) * 10 ** (s.quoteDecimals + uint8(-diff)), 1 | ||
); | ||
} | ||
} | ||
|
||
function calcOutAmountInverse(FuzzableState memory s) internal pure returns (uint256) { | ||
int8 diff = int8(s.baseDecimals) + 18; | ||
if (diff > 0) { | ||
return FixedPointMathLib.fullMulDiv( | ||
s.inAmount, 10 ** uint8(diff), uint256(uint192(s.v.quantizedValue)) * 10 ** s.quoteDecimals | ||
); | ||
} else { | ||
return FixedPointMathLib.fullMulDiv( | ||
s.inAmount, 1, uint256(uint192(s.v.quantizedValue)) * 10 ** (s.quoteDecimals + uint8(-diff)) | ||
); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
pragma solidity ^0.8.0; | ||
|
||
import {StorkStructs} from "../../../src/adapter/stork/IStork.sol"; | ||
|
||
|
||
contract StubStork { | ||
StorkStructs.TemporalNumericValue value; | ||
bool doRevert; | ||
string revertMsg = "oops"; | ||
|
||
function setPrice(StorkStructs.TemporalNumericValue memory _value) external { | ||
value = _value; | ||
} | ||
|
||
function setRevert(bool _doRevert) external { | ||
doRevert = _doRevert; | ||
} | ||
|
||
function getTemporalNumericValueUnsafeV1( | ||
bytes32 | ||
) external view returns (StorkStructs.TemporalNumericValue memory) { | ||
if (doRevert) revert(revertMsg); | ||
return value; | ||
} | ||
} |
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.
grouped the Stork Oracle with the other External Pull oracles