diff --git a/README.md b/README.md index 9265b45..485f552 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,3 @@ -## Foundry +# astria-oracle-contracts -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** - -Foundry consists of: - -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. - -## Documentation - -https://book.getfoundry.sh/ - -## Usage - -### Build - -```shell -$ forge build -``` - -### Test - -```shell -$ forge test -``` - -### Format - -```shell -$ forge fmt -``` - -### Gas Snapshots - -```shell -$ forge snapshot -``` - -### Anvil - -```shell -$ anvil -``` - -### Deploy - -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key -``` - -### Cast - -```shell -$ cast -``` - -### Help - -```shell -$ forge --help -$ anvil --help -$ cast --help -``` +Repository of contracts used for Astria's native oracle. diff --git a/script/Counter.s.sol b/script/AstriaOracle.s.sol similarity index 58% rename from script/Counter.s.sol rename to script/AstriaOracle.s.sol index cdc1fe9..3ef0776 100644 --- a/script/Counter.s.sol +++ b/script/AstriaOracle.s.sol @@ -2,17 +2,17 @@ pragma solidity ^0.8.13; import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; +import {AstriaOracle} from "../src/AstriaOracle.sol"; -contract CounterScript is Script { - Counter public counter; +contract AstriaOracleScript is Script { + AstriaOracle public astriaOracle; function setUp() public {} function run() public { vm.startBroadcast(); - counter = new Counter(); + astriaOracle = new AstriaOracle(); vm.stopBroadcast(); } diff --git a/src/AstriaOracle.sol b/src/AstriaOracle.sol new file mode 100644 index 0000000..61ba728 --- /dev/null +++ b/src/AstriaOracle.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT or Apache-2.0 +pragma solidity ^0.8.21; + +// Core oracle contract for Astria's native oracle. +// This contract stores the price data for every currency pair supported by the oracle. +contract AstriaOracle { + // mapping of block number to mapping of hash of the currency pair string to its price data + mapping(uint256 => mapping(bytes32 => PriceData)) public priceData; + + // mapping of hash of the currency pair string to the number of decimals the price is represented in + mapping(bytes32 => uint8) public decimals; + + // block number of the latest price data update + uint256 public latestBlockNumber; + + struct PriceData { + uint128 price; + uint256 timestamp; + } + + function setDecimals(bytes32 _currencyPair, uint8 _decimals) public { + decimals[_currencyPair] = _decimals; + } + + function updatePriceData(bytes32[] memory _currencyPairs, uint128[] memory _prices) public { + require(_currencyPairs.length == _prices.length, "currency pair and price length mismatch"); + latestBlockNumber = block.number; + for (uint256 i = 0; i < _currencyPairs.length; i++) { + priceData[latestBlockNumber][_currencyPairs[i]] = PriceData(_prices[i], block.timestamp); + } + } +} diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/src/MockAggregator.sol b/src/MockAggregator.sol new file mode 100644 index 0000000..e0c0081 --- /dev/null +++ b/src/MockAggregator.sol @@ -0,0 +1,74 @@ +pragma solidity ^0.8.21; + +import {AggregatorV2V3Interface} from "./interfaces/AggregatorV2V3Interface.sol"; +import {AstriaOracle} from "./AstriaOracle.sol"; + +// Mock currency pair aggregator contract for a single currency pair. +contract MockAggregator is AggregatorV2V3Interface { + // core oracle contract + AstriaOracle immutable public oracle; + + // currency pair hash + bytes32 immutable public currencyPairHash; + + constructor(AstriaOracle _oracle, bytes32 _currencyPairHash) { + oracle = _oracle; + currencyPairHash = _currencyPairHash; + } + + /* v2 aggregator interface */ + + function latestAnswer() external view returns (int256) { + (uint128 price, ) = oracle.priceData(oracle.latestBlockNumber(), currencyPairHash); + return int256(uint256(price)); + } + + function latestTimestamp() external view returns (uint256) { + (, uint256 timestamp) = oracle.priceData(oracle.latestBlockNumber(), currencyPairHash); + return timestamp; + } + + function latestRound() external view returns (uint256) { + return oracle.latestBlockNumber(); + } + + function getAnswer(uint256 roundId) external view returns (int256) { + (uint128 price, ) = oracle.priceData(roundId, currencyPairHash); + return int256(uint256(price)); + } + + function getTimestamp(uint256 roundId) external view returns (uint256) { + (, uint256 timestamp) = oracle.priceData(roundId, currencyPairHash); + return timestamp; + } + + /* v3 aggregator interface */ + + function decimals() external view returns (uint8) { + return oracle.decimals(currencyPairHash); + } + + function description() external pure returns (string memory) { + return "MockAggregator"; + } + + function version() external pure returns (uint256) { + return 0; + } + + function getRoundData( + uint80 _roundId + ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { + (uint128 price, uint256 timestamp) = oracle.priceData(_roundId, currencyPairHash); + return (_roundId, int256(uint256(price)), timestamp, timestamp, _roundId); + } + + function latestRoundData() + external + view + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { + roundId = uint80(oracle.latestBlockNumber()); + (uint128 price, uint256 timestamp) = oracle.priceData(roundId, currencyPairHash); + return (roundId, int256(uint256(price)), timestamp, timestamp, roundId); + } +} diff --git a/src/interfaces/AggregatorInterface.sol b/src/interfaces/AggregatorInterface.sol new file mode 100644 index 0000000..1bd8f90 --- /dev/null +++ b/src/interfaces/AggregatorInterface.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// https://github.com/smartcontractkit/chainlink/blob/dde17518ff7f3dd3fe1d53614f211357944516f0/contracts/src/v0.8/shared/interfaces/AggregatorInterface.sol +// solhint-disable-next-line interface-starts-with-i +interface AggregatorInterface { + function latestAnswer() external view returns (int256); + + function latestTimestamp() external view returns (uint256); + + function latestRound() external view returns (uint256); + + function getAnswer(uint256 roundId) external view returns (int256); + + function getTimestamp(uint256 roundId) external view returns (uint256); + + event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt); + + event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt); +} diff --git a/src/interfaces/AggregatorV2V3Interface.sol b/src/interfaces/AggregatorV2V3Interface.sol new file mode 100644 index 0000000..4c69830 --- /dev/null +++ b/src/interfaces/AggregatorV2V3Interface.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AggregatorInterface} from "./AggregatorInterface.sol"; +import {AggregatorV3Interface} from "./AggregatorV3Interface.sol"; + +// https://github.com/smartcontractkit/chainlink/blob/4eca0ec7a7fcf23fb31d0df1581cd7721deb7507/contracts/src/v0.8/shared/interfaces/AggregatorV2V3Interface.sol +// solhint-disable-next-line interface-starts-with-i +interface AggregatorV2V3Interface is AggregatorInterface, AggregatorV3Interface {} diff --git a/src/interfaces/AggregatorV3Interface.sol b/src/interfaces/AggregatorV3Interface.sol new file mode 100644 index 0000000..f04e06c --- /dev/null +++ b/src/interfaces/AggregatorV3Interface.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// https://github.com/smartcontractkit/chainlink/blob/dde17518ff7f3dd3fe1d53614f211357944516f0/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol +// solhint-disable-next-line interface-starts-with-i +interface AggregatorV3Interface { + function decimals() external view returns (uint8); + + function description() external view returns (string memory); + + function version() external view returns (uint256); + + function getRoundData( + uint80 _roundId + ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); + + function latestRoundData() + external + view + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 54b724f..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -}