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
14 changes: 2 additions & 12 deletions src/oracles/ChainlinkToOracleAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.20;

import {IOracle} from "../interfaces/IOracle.sol";
import {OracleScalingLib} from "./OracleScalingLib.sol";

// minimal chainlink interface, only what we need
interface AggregatorV3Interface {
Expand Down Expand Up @@ -29,7 +30,7 @@ contract ChainlinkToOracleAdapter is IOracle {
require(answer > 0, "bad value");
// casting is safe because answer > 0
// forge-lint: disable-next-line(unsafe-typecast)
return _scaleToWad(uint256(answer), feed.decimals());
return OracleScalingLib.scaleToWad(uint256(answer), feed.decimals());
}

// chainlink has single value, so min = max = value
Expand All @@ -50,15 +51,4 @@ contract ChainlinkToOracleAdapter is IOracle {
function description() external view returns (string memory) {
return feed.description();
}

// scale native decimals to WAD (1e18)
function _scaleToWad(uint256 value, uint8 valueDecimals) internal pure returns (uint256) {
if (valueDecimals == 18) {
return value;
}
if (valueDecimals < 18) {
return value * (10 ** (18 - uint256(valueDecimals)));
}
return value / (10 ** (uint256(valueDecimals) - 18));
}
}
14 changes: 14 additions & 0 deletions src/oracles/OracleScalingLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

library OracleScalingLib {
function scaleToWad(uint256 value, uint8 valueDecimals) internal pure returns (uint256) {
if (valueDecimals == 18) {
return value;
}
if (valueDecimals < 18) {
return value * (10 ** (18 - uint256(valueDecimals)));
}
return value / (10 ** (uint256(valueDecimals) - 18));
}
}
54 changes: 54 additions & 0 deletions src/oracles/OrbOracleToOracleAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IOracle} from "../interfaces/IOracle.sol";
import {OracleScalingLib} from "./OracleScalingLib.sol";

interface OrbOracleInterface {
function readValue() external view returns (int256);
function readMaxValue(uint256 sampleSize) external view returns (int256);
function readMinValue(uint256 sampleSize) external view returns (int256);
function lastSubmissionTime() external view returns (uint256);
function description() external view returns (string memory);
}

contract OrbOracleToOracleAdapter is IOracle {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need an adapter for orb oracles. Instead, by having Gluon and Orb agreeing on the oracle interface, Gluon should be able to use Orb oracles directly.

It seems that there are two disagreements that are forcing you to implement this adapter:

1 - Orb is returning int256 but Gluon is expecting uint256.

2 - Orb has a configurable number of decimals, but Gluon is expecting 18 decimals.

To solve 2, let's make Orb always use 18 decimals too.

To solve 2, let's make Orb always return uint256.

OrbOracleInterface public immutable feed;
uint8 public immutable valueDecimals;
uint256 public immutable sampleSize;

constructor(address feedParam, uint8 valueDecimalsParam, uint256 sampleSizeParam) {
require(feedParam != address(0), "invalid feed");
require(sampleSizeParam > 0, "invalid sample size");

feed = OrbOracleInterface(feedParam);
valueDecimals = valueDecimalsParam;
sampleSize = sampleSizeParam;
}

function readValue() public view returns (uint256 value) {
return _scaleIntToWad(feed.readValue());
}

function readMaxValue() external view returns (uint256 maxValue) {
return _scaleIntToWad(feed.readMaxValue(sampleSize));
}

function readMinValue() external view returns (uint256 minValue) {
return _scaleIntToWad(feed.readMinValue(sampleSize));
}

function lastUpdated() external view returns (uint256 timestamp) {
return feed.lastSubmissionTime();
}

function description() external view returns (string memory) {
return feed.description();
}

function _scaleIntToWad(int256 value) internal view returns (uint256) {
require(value > 0, "bad value");
// forge-lint: disable-next-line(unsafe-typecast)
return OracleScalingLib.scaleToWad(uint256(value), valueDecimals);
}
}
125 changes: 125 additions & 0 deletions test/OrbOracleAdapter.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Test} from "forge-std/Test.sol";
import {OrbOracleToOracleAdapter} from "../src/oracles/OrbOracleToOracleAdapter.sol";

contract MockOrbOracle {
int256 public valueVal;
int256 public maxVal;
int256 public minVal;
uint256 public lastSubmissionTimeVal;
uint256 public expectedSampleSize;
string private descriptionVal;

constructor(int256 value_, int256 max_, int256 min_, uint256 sampleSize_, string memory description_) {
valueVal = value_;
maxVal = max_;
minVal = min_;
expectedSampleSize = sampleSize_;
descriptionVal = description_;
lastSubmissionTimeVal = block.timestamp;
}

function readValue() external view returns (int256) {
return valueVal;
}

function readMaxValue(uint256 sampleSize) external view returns (int256) {
require(sampleSize == expectedSampleSize, "wrong sample size");
return maxVal;
}

function readMinValue(uint256 sampleSize) external view returns (int256) {
require(sampleSize == expectedSampleSize, "wrong sample size");
return minVal;
}

function lastSubmissionTime() external view returns (uint256) {
return lastSubmissionTimeVal;
}

function description() external view returns (string memory) {
return descriptionVal;
}
}

contract OrbOracleAdapterTest is Test {
function testScalesEightDecimalsToWad() public {
MockOrbOracle feed = new MockOrbOracle(3000 * 1e8, 3100 * 1e8, 2900 * 1e8, 3, "ORB / USD");
OrbOracleToOracleAdapter adapter = new OrbOracleToOracleAdapter(address(feed), 8, 3);

assertEq(adapter.readValue(), 3000 * 1e18, "8 dec scaling failed");
}

function testSupportsEighteenDecimals() public {
MockOrbOracle feed = new MockOrbOracle(2000 * 1e18, 2100 * 1e18, 1900 * 1e18, 3, "ORB / USD");
OrbOracleToOracleAdapter adapter = new OrbOracleToOracleAdapter(address(feed), 18, 3);

assertEq(adapter.readValue(), 2000 * 1e18, "18 dec scaling failed");
}

function testSupportsMoreThanEighteenDecimals() public {
MockOrbOracle feed = new MockOrbOracle(2000 * 1e20, 2100 * 1e20, 1900 * 1e20, 3, "ORB / USD");
OrbOracleToOracleAdapter adapter = new OrbOracleToOracleAdapter(address(feed), 20, 3);

assertEq(adapter.readValue(), 2000 * 1e18, "20 dec scaling failed");
}

function testMinAndMaxUseSampleSize() public {
MockOrbOracle feed = new MockOrbOracle(3000 * 1e8, 3200 * 1e8, 2800 * 1e8, 5, "ORB / USD");
OrbOracleToOracleAdapter adapter = new OrbOracleToOracleAdapter(address(feed), 8, 5);

assertEq(adapter.readMaxValue(), 3200 * 1e18, "wrong max value");
assertEq(adapter.readMinValue(), 2800 * 1e18, "wrong min value");
}

function testLastUpdatedReturnsSubmissionTime() public {
MockOrbOracle feed = new MockOrbOracle(100 * 1e8, 110 * 1e8, 90 * 1e8, 3, "ORB / USD");
OrbOracleToOracleAdapter adapter = new OrbOracleToOracleAdapter(address(feed), 8, 3);

assertEq(adapter.lastUpdated(), block.timestamp, "wrong timestamp");
}

function testDescriptionReturnsFeedDescription() public {
MockOrbOracle feed = new MockOrbOracle(100 * 1e8, 110 * 1e8, 90 * 1e8, 3, "ORB / USD");
OrbOracleToOracleAdapter adapter = new OrbOracleToOracleAdapter(address(feed), 8, 3);

assertEq(adapter.description(), "ORB / USD", "wrong description");
}

function testRevertsOnZeroValue() public {
MockOrbOracle feed = new MockOrbOracle(0, 110 * 1e8, 90 * 1e8, 3, "ORB / USD");
OrbOracleToOracleAdapter adapter = new OrbOracleToOracleAdapter(address(feed), 8, 3);

vm.expectRevert("bad value");
adapter.readValue();
}

function testRevertsOnNegativeValue() public {
MockOrbOracle feed = new MockOrbOracle(-1, 110 * 1e8, 90 * 1e8, 3, "ORB / USD");
OrbOracleToOracleAdapter adapter = new OrbOracleToOracleAdapter(address(feed), 8, 3);

vm.expectRevert("bad value");
adapter.readValue();
}

function testRevertsOnZeroFeedAddress() public {
vm.expectRevert("invalid feed");
new OrbOracleToOracleAdapter(address(0), 8, 3);
}

function testRevertsOnZeroSampleSize() public {
MockOrbOracle feed = new MockOrbOracle(100 * 1e8, 110 * 1e8, 90 * 1e8, 3, "ORB / USD");

vm.expectRevert("invalid sample size");
new OrbOracleToOracleAdapter(address(feed), 8, 0);
}

function testScalesZeroDecimalsToWad() public {
MockOrbOracle feed = new MockOrbOracle(1234, 1300, 1200, 3, "ORB / USD");
OrbOracleToOracleAdapter adapter = new OrbOracleToOracleAdapter(address(feed), 0, 3);

assertEq(adapter.readValue(), 1234 * 1e18, "0 dec scaling failed");
}
}
Loading