Skip to content

Commit

Permalink
refactor: forge fmt
Browse files Browse the repository at this point in the history
Signed-off-by: Pablo Maldonado <[email protected]>
  • Loading branch information
md0x committed May 14, 2024
1 parent 401af4a commit addf0a9
Show file tree
Hide file tree
Showing 14 changed files with 62 additions and 209 deletions.
1 change: 0 additions & 1 deletion src/DiamondRootOval.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {IOval} from "./interfaces/IOval.sol";
* need. They are exposed here to simplify the inheritance structure of Oval contract system and to enable easier
* composability and extensibility at the integration layer, enabling arbitrary combinations of sources and destinations.
*/

abstract contract DiamondRootOval is IBaseController, IOval, IBaseOracleAdapter {
/**
* @notice Returns the latest data from the source.
Expand Down
1 change: 0 additions & 1 deletion src/Oval.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {DiamondRootOval} from "./DiamondRootOval.sol";
* contract that governs who can call unlockLatestValue.
* @custom:security-contact [email protected]
*/

abstract contract Oval is DiamondRootOval {
uint256 public lastUnlockTime; // Timestamp of the latest unlock to Oval.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {DiamondRootOval} from "../../DiamondRootOval.sol";
* implementation that consumers can connect with if they don't want to use an opinionated destination Adapter.
*
*/

abstract contract BaseDestinationAdapter is DiamondRootOval {
uint8 public constant decimals = 18;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {DiamondRootOval} from "../../DiamondRootOval.sol";
/**
* @title ChainlinkDestinationAdapter contract to expose Oval data via the standard Chainlink Aggregator interface.
*/

abstract contract ChainlinkDestinationAdapter is DiamondRootOval, IAggregatorV3 {
uint8 public immutable override decimals;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {IOval} from "../../interfaces/IOval.sol";
* that it uses to return the correct price for each cToken. This is needed as the UniswapAnchoredView interface is a
* one to many relationship with cTokens, and so we need to be able to return the correct price for each cToken.
*/

contract UniswapAnchoredViewDestinationAdapter is Ownable, IUniswapAnchoredView {
mapping(address => address) public cTokenToOval;
mapping(address => uint8) public cTokenToDecimal;
Expand Down
1 change: 0 additions & 1 deletion src/adapters/source-adapters/ChainlinkSourceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {DiamondRootOval} from "../../DiamondRootOval.sol";
* @title ChainlinkSourceAdapter contract to read data from Chainlink aggregator and standardize it for Oval.
* @dev Can fetch information from Chainlink source at a desired timestamp for historic lookups.
*/

abstract contract ChainlinkSourceAdapter is DiamondRootOval {
IAggregatorV3Source public immutable CHAINLINK_SOURCE;
uint8 private immutable SOURCE_DECIMALS;
Expand Down
73 changes: 24 additions & 49 deletions src/adapters/source-adapters/CoinbaseOracleSourceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {console} from "forge-std/console.sol";
* @title CoinbaseOracleSourceAdapter contract to read data from CoinbaseOracle and standardize it for Oval.
* @dev Can fetch information from CoinbaseOracle source at a desired timestamp for historic lookups.
*/

abstract contract CoinbaseOracleSourceAdapter is DiamondRootOval {
IAggregatorV3Source public immutable COINBASE_SOURCE;
uint8 private immutable SOURCE_DECIMALS;
Expand All @@ -32,18 +31,15 @@ abstract contract CoinbaseOracleSourceAdapter is DiamondRootOval {
* @return answer The answer as of requested timestamp, or earliest available data if not available, in 18 decimals.
* @return updatedAt The timestamp of the answer.
*/
function tryLatestDataAt(
uint256 timestamp,
uint256 maxTraversal
) public view virtual override returns (int256, uint256) {
(int256 answer, uint256 updatedAt) = _tryLatestRoundDataAt(
timestamp,
maxTraversal
);
return (
DecimalLib.convertDecimals(answer, SOURCE_DECIMALS, 18),
updatedAt
);
function tryLatestDataAt(uint256 timestamp, uint256 maxTraversal)
public
view
virtual
override
returns (int256, uint256)
{
(int256 answer, uint256 updatedAt) = _tryLatestRoundDataAt(timestamp, maxTraversal);
return (DecimalLib.convertDecimals(answer, SOURCE_DECIMALS, 18), updatedAt);
}

/**
Expand All @@ -56,64 +52,43 @@ abstract contract CoinbaseOracleSourceAdapter is DiamondRootOval {
* @return answer The latest answer in 18 decimals.
* @return updatedAt The timestamp of the answer.
*/
function getLatestSourceData()
public
view
virtual
override
returns (int256, uint256)
{
(, int256 sourceAnswer, , uint256 updatedAt, ) = COINBASE_SOURCE
.latestRoundData();
return (
DecimalLib.convertDecimals(sourceAnswer, SOURCE_DECIMALS, 18),
updatedAt
);
function getLatestSourceData() public view virtual override returns (int256, uint256) {
(, int256 sourceAnswer,, uint256 updatedAt,) = COINBASE_SOURCE.latestRoundData();
return (DecimalLib.convertDecimals(sourceAnswer, SOURCE_DECIMALS, 18), updatedAt);
}

// Tries getting latest data as of requested timestamp. If this is not possible, returns the earliest data available
// past the requested timestamp considering the maxTraversal limitations.
function _tryLatestRoundDataAt(
uint256 timestamp,
uint256 maxTraversal
) internal view returns (int256, uint256) {
(uint80 roundId, int256 answer, , uint256 updatedAt, ) = COINBASE_SOURCE
.latestRoundData();
function _tryLatestRoundDataAt(uint256 timestamp, uint256 maxTraversal) internal view returns (int256, uint256) {
(uint80 roundId, int256 answer,, uint256 updatedAt,) = COINBASE_SOURCE.latestRoundData();

// In the happy path there have been no source updates since requested time, so we can return the latest data.
// We can use updatedAt property as it matches the block timestamp of the latest source transmission.
if (updatedAt <= timestamp) return (answer, updatedAt);

// Attempt traversing historical round data backwards from roundId. This might still be newer or uninitialized.
(
int256 historicalAnswer,
uint256 historicalUpdatedAt
) = _searchRoundDataAt(timestamp, roundId, maxTraversal);
(int256 historicalAnswer, uint256 historicalUpdatedAt) = _searchRoundDataAt(timestamp, roundId, maxTraversal);

// Validate returned data. If it is uninitialized we fallback to returning the current latest round data.
if (historicalUpdatedAt > 0)
if (historicalUpdatedAt > 0) {
return (historicalAnswer, historicalUpdatedAt);
}
return (answer, updatedAt);
}

// Tries finding latest historical data (ignoring current roundId) not newer than requested timestamp. Might return
// newer data than requested if exceeds traversal or hold uninitialized data that should be handled by the caller.
function _searchRoundDataAt(
uint256 timestamp,
uint80 targetRoundId,
uint256 maxTraversal
) internal view returns (int256, uint256) {
function _searchRoundDataAt(uint256 timestamp, uint80 targetRoundId, uint256 maxTraversal)
internal
view
returns (int256, uint256)
{
int256 answer;
uint256 updatedAt;
uint80 traversedRounds = 1; // Start from 1 to avoid checking the current round.

while (
traversedRounds <= uint80(maxTraversal) &&
targetRoundId >= traversedRounds
) {
(, answer, , updatedAt, ) = COINBASE_SOURCE.getRoundData(
targetRoundId - traversedRounds
);
while (traversedRounds <= uint80(maxTraversal) && targetRoundId >= traversedRounds) {
(, answer,, updatedAt,) = COINBASE_SOURCE.getRoundData(targetRoundId - traversedRounds);
if (updatedAt <= timestamp) return (answer, updatedAt);
traversedRounds++;
}
Expand Down
1 change: 0 additions & 1 deletion src/adapters/source-adapters/SnapshotSource.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {DiamondRootOval} from "../../DiamondRootOval.sol";
/**
* @title SnapshotSource contract to be used in conjunction with a source adapter that needs to snapshot historic data.
*/

abstract contract SnapshotSource is DiamondRootOval {
// Snapshot records the historical answer at a specific timestamp.
struct Snapshot {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {IValidatorProxy} from "../../interfaces/compound/IValidatorProxy.sol";
* @title UniswapAnchoredViewSourceAdapter contract to read data from UniswapAnchoredView and standardize it for Oval.
*
*/

abstract contract UniswapAnchoredViewSourceAdapter is SnapshotSource {
IUniswapAnchoredView public immutable UNISWAP_ANCHORED_VIEW;
address public immutable C_TOKEN;
Expand Down
1 change: 0 additions & 1 deletion src/controllers/BaseController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {Oval} from "../Oval.sol";
* @title BaseController providing the simplest possible controller logic to govern who can unlock Oval.
* @dev Custom Controllers can be created to provide more granular control over who can unlock Oval.
*/

abstract contract BaseController is Ownable, Oval {
// these don't need to be public since they can be accessed via the accessor functions below.
uint256 private lockWindow_ = 60; // The lockWindow in seconds.
Expand Down
1 change: 0 additions & 1 deletion src/controllers/ImmutableController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {Oval} from "../Oval.sol";
* 2. Because LOCK_WINDOW and MAX_TRAVERSAL are immutable, the read costs are much lower in the "hot" path (end
* oracle users).
*/

abstract contract ImmutableController is Oval {
uint256 private immutable LOCK_WINDOW; // The lockWindow in seconds.
uint256 private immutable MAX_TRAVERSAL; // The maximum number of rounds to traverse when looking for historical data.
Expand Down
75 changes: 12 additions & 63 deletions src/oracles/CoinbaseOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,54 +25,25 @@ contract CoinbaseOracle is IAggregatorV3Source {
external
view
override
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
int256 latestAnswer = roundAnswers[lastRoundId];
uint256 latestTimestamp = roundTimestamps[lastRoundId];
return (
lastRoundId,
latestAnswer,
latestTimestamp,
latestTimestamp,
lastRoundId
);
return (lastRoundId, latestAnswer, latestTimestamp, latestTimestamp, lastRoundId);
}

function getRoundData(
uint80 _roundId
)
function getRoundData(uint80 _roundId)
external
view
override
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
int256 latestAnswer = roundAnswers[_roundId];
uint256 latestTimestamp = roundTimestamps[_roundId];
return (
_roundId,
latestAnswer,
latestTimestamp,
latestTimestamp,
_roundId
);
return (_roundId, latestAnswer, latestTimestamp, latestTimestamp, _roundId);
}

function pushPrice(
bytes memory priceData,
bytes memory signature
) external {
function pushPrice(bytes memory priceData, bytes memory signature) external {
(
string memory kind, // e.g. "price"
uint256 timestamp, // e.g. 1629350000
Expand All @@ -82,43 +53,21 @@ contract CoinbaseOracle is IAggregatorV3Source {

uint256 latestTimestamp = roundTimestamps[lastRoundId];

require(
keccak256(abi.encodePacked(kind)) ==
keccak256(abi.encodePacked("price")),
"Invalid kind."
);
require(keccak256(abi.encodePacked(kind)) == keccak256(abi.encodePacked("price")), "Invalid kind.");
require(price < uint256(type(int256).max), "Price exceeds max value.");
require(timestamp > latestTimestamp, "Invalid timestamp.");
require(
keccak256(abi.encodePacked(ticker)) ==
keccak256(abi.encodePacked(symbol)),
"Invalid ticker."
);
require(
recoverSigner(priceData, signature) == reporter,
"Invalid signature."
);
require(keccak256(abi.encodePacked(ticker)) == keccak256(abi.encodePacked(symbol)), "Invalid ticker.");
require(recoverSigner(priceData, signature) == reporter, "Invalid signature.");

lastRoundId++;
roundAnswers[lastRoundId] = int256(price);
roundTimestamps[lastRoundId] = timestamp;
}

// Internal function to recover the signer of a message
function recoverSigner(
bytes memory message,
bytes memory signature
) public pure returns (address) {
(bytes32 r, bytes32 s, uint8 v) = abi.decode(
signature,
(bytes32, bytes32, uint8)
);
bytes32 hash = keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
keccak256(message)
)
);
function recoverSigner(bytes memory message, bytes memory signature) public pure returns (address) {
(bytes32 r, bytes32 s, uint8 v) = abi.decode(signature, (bytes32, bytes32, uint8));
bytes32 hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", keccak256(message)));
return ecrecover(hash, v, r, s);
}
}
10 changes: 2 additions & 8 deletions test/unit/CoinbaseOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,15 @@ contract CoinbaseSourceAdapterTest is CommonTest {

bytes memory encodedData = abi.encode(kind, timestamp, ticker, price);

bytes32 hash = keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
keccak256(encodedData)
)
);
bytes32 hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", keccak256(encodedData)));

(uint8 v, bytes32 r, bytes32 s) = vm.sign(reporterPk, hash);

bytes memory signature = abi.encode(r, s, v);

coinbaseOracle.pushPrice(encodedData, signature);

(, int256 answer, uint256 updatedAt, , ) = coinbaseOracle
.latestRoundData();
(, int256 answer, uint256 updatedAt,,) = coinbaseOracle.latestRoundData();

assertEq(uint256(answer), price);
assertEq(updatedAt, timestamp);
Expand Down
Loading

0 comments on commit addf0a9

Please sign in to comment.