├── .gitignore ├── constants.ts ├── contracts ├── interfaces │ ├── FeedAdapterInterface.sol │ ├── TypeAndVersionInterface.sol │ ├── AggregatorV2V3Interface.sol │ ├── AggregatorInterface.sol │ ├── AggregatorV3Interface.sol │ ├── DenominationsInterface.sol │ ├── VRFCoordinatorInterface.sol │ └── FeedRegistryInterface.sol ├── SID │ ├── resolvers │ │ ├── ISupportsInterface.sol │ │ ├── ResolverBase.sol │ │ ├── SupportsInterface.sol │ │ └── PublicResolver.sol │ ├── SID.sol │ └── SIDRegistry.sol ├── mock │ ├── PriceConsumerFromAdapter.sol │ ├── PriceConsumerFromRegistry.sol │ └── VRFConsumerBase.sol └── library │ └── EnumerableTradingPairMap.sol ├── scripts ├── sid.ts └── deployMocks.ts ├── tsconfig.json ├── .env.example ├── .github └── workflows │ ├── build.yml │ └── npm-publish.yml ├── test ├── helpers │ └── setup.ts ├── FeedAdapter.test.ts └── FeedRegistry.test.ts ├── package.json ├── README.md └── hardhat.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .env 3 | typechain-types/ 4 | cache/ 5 | .idea/ 6 | artifacts/ 7 | node_modules -------------------------------------------------------------------------------- /constants.ts: -------------------------------------------------------------------------------- 1 | export const SID_TOP_DOMAIN = "boracle.bnb" 2 | export const FEED_REGISTRY_SID_SUBDOMAIN = "fr" 3 | export const SYMBOL_PAIR_SUBDOMAINS = ["btc-usd", "eth-usd", "bnb-usd"] 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /contracts/interfaces/FeedAdapterInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | import "./AggregatorV2V3Interface.sol"; 5 | 6 | interface FeedAdapterInterface is AggregatorV2V3Interface {} 7 | -------------------------------------------------------------------------------- /contracts/interfaces/TypeAndVersionInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | interface TypeAndVersionInterface { 5 | function typeAndVersion() external pure returns (string memory); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/SID/resolvers/ISupportsInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | interface ISupportsInterface { 5 | function supportsInterface(bytes4 interfaceID) external pure returns(bool); 6 | } 7 | -------------------------------------------------------------------------------- /scripts/sid.ts: -------------------------------------------------------------------------------- 1 | import namehash from 'eth-ens-namehash' 2 | import { SID_TOP_DOMAIN } from "../constants" 3 | 4 | export function getNodeValue(subDomainName: string): string { 5 | return namehash.hash(subDomainName + "." + SID_TOP_DOMAIN) 6 | } 7 | -------------------------------------------------------------------------------- /contracts/interfaces/AggregatorV2V3Interface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | import "./AggregatorInterface.sol"; 5 | import "./AggregatorV3Interface.sol"; 6 | 7 | interface AggregatorV2V3Interface is AggregatorInterface, AggregatorV3Interface {} 8 | -------------------------------------------------------------------------------- /contracts/SID/resolvers/ResolverBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | import "./SupportsInterface.sol"; 5 | 6 | abstract contract ResolverBase is SupportsInterface { 7 | function isAuthorised(bytes32 node) internal virtual view returns(bool); 8 | 9 | modifier authorised(bytes32 node) { 10 | require(isAuthorised(node)); 11 | _; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contracts/SID/resolvers/SupportsInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | import "./ISupportsInterface.sol"; 5 | 6 | abstract contract SupportsInterface is ISupportsInterface { 7 | function supportsInterface(bytes4 interfaceID) virtual override public pure returns(bool) { 8 | return interfaceID == type(ISupportsInterface).interfaceId; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "target": "es2018", 5 | "module": "commonjs", 6 | "strict": true, 7 | "noImplicitAny": false, 8 | "esModuleInterop": true, 9 | "outDir": "dist", 10 | "declaration": true, 11 | "resolveJsonModule": true 12 | }, 13 | "include": ["./scripts", "./test", "./typechain"], 14 | "files": ["./hardhat.config.ts"] 15 | } 16 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | 2 | # Fill the URLS with your favourite node providers. By default, uses BSC free tier node 3 | TESTNET_URL=https://data-seed-prebsc-1-s1.binance.org:8545 4 | MAINNET_URL=https://bsc-dataseed.binance.org 5 | 6 | # Not needed for running tests. Key format -> abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234 7 | TESTNET_PRIVATE_KEY= 8 | MAINNET_PRIVATE_KEY= 9 | 10 | 11 | # Optional. Used for contract verification. Use the guide here to get your own API key: https://docs.bscscan.com/getting-started/viewing-api-usage-statistics 12 | ETHERSCAN_API_KEY= 13 | -------------------------------------------------------------------------------- /contracts/interfaces/AggregatorInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | interface AggregatorInterface { 5 | function latestAnswer() external view returns (int256); 6 | 7 | function latestTimestamp() external view returns (uint256); 8 | 9 | function latestRound() external view returns (uint256); 10 | 11 | function getAnswer(uint256 roundId) external view returns (int256); 12 | 13 | function getTimestamp(uint256 roundId) external view returns (uint256); 14 | 15 | event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt); 16 | event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt); 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: build 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version: 16 20 | - run: npm i 21 | - run: cp .env.example .env 22 | - run: npm test 23 | -------------------------------------------------------------------------------- /contracts/interfaces/AggregatorV3Interface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | interface AggregatorV3Interface { 5 | function decimals() external view returns (uint8); 6 | 7 | function description() external view returns (string memory); 8 | 9 | function version() external view returns (uint256); 10 | 11 | function getRoundData(uint80 _roundId) 12 | external 13 | view 14 | returns ( 15 | uint80 roundId, 16 | int256 answer, 17 | uint256 startedAt, 18 | uint256 updatedAt, 19 | uint80 answeredInRound 20 | ); 21 | 22 | function latestRoundData() 23 | external 24 | view 25 | returns ( 26 | uint80 roundId, 27 | int256 answer, 28 | uint256 startedAt, 29 | uint256 updatedAt, 30 | uint80 answeredInRound 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /contracts/interfaces/DenominationsInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | import "../library/EnumerableTradingPairMap.sol"; 5 | 6 | interface DenominationsInterface { 7 | function totalPairsAvailable() external view returns (uint256); 8 | 9 | function getAllPairs() external view returns (EnumerableTradingPairMap.Pair[] memory); 10 | 11 | function getTradingPairDetails(string calldata base, string calldata quote) 12 | external 13 | view 14 | returns ( 15 | address, 16 | address, 17 | address 18 | ); 19 | 20 | function insertPair( 21 | string calldata base, 22 | string calldata quote, 23 | address baseAssetAddress, 24 | address quoteAssetAddress, 25 | address feedAdapterAddress 26 | ) external; 27 | 28 | function removePair(string calldata base, string calldata quote) external; 29 | 30 | function exists(string calldata base, string calldata quote) external view returns (bool); 31 | } 32 | -------------------------------------------------------------------------------- /test/helpers/setup.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'hardhat' 2 | import { Signer } from 'ethers' 3 | const hre = require("hardhat"); 4 | 5 | export interface Roles { 6 | defaultAccount: Signer 7 | walletAccount1: Signer 8 | } 9 | 10 | export interface Users { 11 | roles: Roles 12 | } 13 | 14 | export async function getUsers() { 15 | const accounts = await ethers.getSigners() 16 | const roles: Roles = { 17 | defaultAccount: accounts[0], 18 | walletAccount1: accounts[1], 19 | } 20 | const users: Users = { 21 | roles, 22 | } 23 | return users 24 | } 25 | 26 | export function getSIDRegistryAddressByEnv() { 27 | return hre.network.name == "testnet" ? "0xfFB52185b56603e0fd71De9de4F6f902f05EEA23" : "0x08CEd32a7f3eeC915Ba84415e9C07a7286977956" 28 | } 29 | 30 | export function getSIDResolverAddressByEnv() { 31 | return hre.network.name == "testnet" ? "0x65085651CcbCd165A9d20902ED1dFFB823d915A2" : "0x65085651CcbCd165A9d20902ED1dFFB823d915A2" 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 16 18 | - run: npm ci 19 | - run: cp .env.example .env 20 | - run: npm test 21 | 22 | publish-npm: 23 | needs: build 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v3 27 | - uses: actions/setup-node@v3 28 | with: 29 | node-version: 16 30 | registry-url: https://registry.npmjs.org/ 31 | - run: npm ci 32 | - run: npm publish 33 | env: 34 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@binance-oracle/binance-oracle-starter", 3 | "version": "1.1.0", 4 | "description": "Starter project for Binance Oracle containing solidity interfaces and sample code", 5 | "main": "hardhat.config.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "compile": "hardhat compile", 11 | "test": "hardhat test", 12 | "deploy:testnet": "hardhat run scripts/deployMocks.ts --network testnet", 13 | "deploy:mainnet": "hardhat run scripts/deployMocks.ts --network mainnet" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/binance-cloud/binance-oracle.git" 18 | }, 19 | "author": "Sri Krishna Mannem", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/binance-cloud/binance-oracle/issues" 23 | }, 24 | "homepage": "https://github.com/binance-cloud/binance-oracle#readme", 25 | "devDependencies": { 26 | "@nomicfoundation/hardhat-toolbox": "^2.0.0", 27 | "dotenv": "^16.0.1", 28 | "hardhat": "^2.12.0" 29 | }, 30 | "dependencies": { 31 | "eth-ens-namehash": "^2.0.8" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/mock/PriceConsumerFromAdapter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | import "../interfaces/AggregatorV2V3Interface.sol"; 5 | import "../SID/SIDRegistry.sol"; 6 | import "../SID/resolvers/PublicResolver.sol"; 7 | 8 | /* 9 | * Consumer contract which retrieves price data from a FeedAdapter of a particular trading pair 10 | * Access control is enforced for most of the trading pairs. Only whitelisted users will be able to retrieve the price data onchain 11 | * However, some pairs will be open to all 12 | * See https://oracle.binance.com/docs/price-feeds/feed-adapter/ for more details. 13 | */ 14 | contract PriceConsumerWithAdapter { 15 | AggregatorV2V3Interface private s_feedAdapter; 16 | 17 | constructor(address _sidRegistryAddress, bytes32 _feedAdapterNodeHash) { 18 | SIDRegistry _sidRegistry = SIDRegistry(_sidRegistryAddress); 19 | address _publicResolverAddress = _sidRegistry.resolver(_feedAdapterNodeHash); 20 | PublicResolver _publicResolver = PublicResolver(_publicResolverAddress); 21 | address _feedAdapterAddress = _publicResolver.addr(_feedAdapterNodeHash); 22 | s_feedAdapter = AggregatorV2V3Interface(_feedAdapterAddress); 23 | } 24 | 25 | function getDescription() external view returns (string memory) { 26 | return s_feedAdapter.description(); 27 | } 28 | 29 | function getDecimals() external view returns (uint8) { 30 | return s_feedAdapter.decimals(); 31 | } 32 | 33 | function getLatestAnswer() 34 | external 35 | view 36 | returns (int256 answer) 37 | { 38 | return s_feedAdapter.latestAnswer(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![build](https://github.com/binance-cloud/binance-oracle/actions/workflows/build.yml/badge.svg?branch=main) 2 | ![npm (scoped)](https://img.shields.io/npm/v/@binance-oracle/binance-oracle-starter) 3 | ![npm](https://img.shields.io/npm/dw/@binance-oracle/binance-oracle-starter) 4 | ![website](https://img.shields.io/website?down_color=red&down_message=offline&up_color=green&up_message=online&url=https%3A%2F%2Foracle.binance.com%2F) 5 | 6 | # Binance-Oracle 7 | Binance Oracle lets smart contracts query the price of assets on BSC. For detailed documentation, please visit [docs](https://oracle.binance.com/docs). 8 | 9 | ## Installation 10 | If you like to import the Binance-Oracle contracts into your project, you can do so by running the following command: 11 | ```shell 12 | npm install --save-dev @binance-oracle/binance-oracle-starter 13 | ``` 14 | ## Import contracts to solidity 15 | 16 | After installation, for instance if you like to import the 'FeedRegistryInterface' contract, you can do so as follows: 17 | 18 | ```solidity 19 | import "@binance-oracle/binance-oracle-starter/contracts/interfaces/FeedRegistryInterface.sol"; 20 | ``` 21 | 22 | 23 | ## Run standalone tests 24 | 25 | ```shell 26 | git clone https://github.com/binance-cloud/binance-oracle.git 27 | cd binance-oracle 28 | cp .env.example .env 29 | npm install 30 | npm run test 31 | ``` 32 | 33 | ## Deploy mock consumer contracts to Testnet/ Mainnet 34 | 35 | First make sure, you copy .env.example to .env in the root directory of the project. 36 | The .env file should be populated with the private keys and few other credentials of the environment you like to deploy to. 37 | Then run either of the scripts below to deploy to Testnet or Mainnet 38 | 39 | ```shell 40 | npm run deploy:testnet 41 | npm run deploy:mainnet 42 | ``` 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/config"; 2 | import "@nomicfoundation/hardhat-toolbox"; 3 | import * as dotenv from 'dotenv' 4 | dotenv.config() 5 | 6 | 7 | // Get private keys from env 8 | const getAccounts = (privateKeys: string | undefined): Array => { 9 | if (!privateKeys) { 10 | return [] 11 | } 12 | 13 | const privateKeyArr = privateKeys.split(',') 14 | return privateKeyArr 15 | .filter((privateKey) => { 16 | // Filter empty strings, no empty strings should occupy array positions 17 | return privateKey.trim().length > 0 18 | }) 19 | .map((privateKey) => { 20 | const tempPrivateKey = privateKey.trim() 21 | if (tempPrivateKey.startsWith('0x')) { 22 | return tempPrivateKey 23 | } 24 | return `0x${tempPrivateKey}` 25 | }) 26 | } 27 | 28 | const COMPILER_SETTINGS = { 29 | optimizer: { 30 | enabled: true, 31 | runs: 1000, 32 | }, 33 | } 34 | const config: HardhatUserConfig = { 35 | defaultNetwork: 'testnet', 36 | solidity: { 37 | compilers: [ 38 | { 39 | version: '0.8.4', 40 | settings: COMPILER_SETTINGS, 41 | }, 42 | ], 43 | }, 44 | networks: { 45 | testnet: { 46 | url: process.env.TESTNET_URL || '', 47 | accounts: getAccounts(process.env.TESTNET_PRIVATE_KEY), 48 | }, 49 | mainnet: { 50 | url: process.env.MAINNET_URL || '', 51 | accounts: getAccounts(process.env.MAINNET_PRIVATE_KEY), 52 | }, 53 | }, 54 | etherscan: { 55 | apiKey: process.env.ETHERSCAN_API_KEY, 56 | customChains: [ 57 | { 58 | network: 'testnet', 59 | chainId: 97, 60 | urls: { 61 | apiURL: 'https://api-testnet.bscscan.com/api', 62 | browserURL: 'https://testnet.bscscan.com/', 63 | }, 64 | }, 65 | ], 66 | }, 67 | } 68 | 69 | export default config; 70 | -------------------------------------------------------------------------------- /contracts/SID/SID.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | interface SID { 5 | // Logged when the owner of a node assigns a new owner to a subnode. 6 | event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); 7 | 8 | // Logged when the owner of a node transfers ownership to a new account. 9 | event Transfer(bytes32 indexed node, address owner); 10 | 11 | // Logged when the resolver for a node changes. 12 | event NewResolver(bytes32 indexed node, address resolver); 13 | 14 | // Logged when the TTL of a node changes 15 | event NewTTL(bytes32 indexed node, uint64 ttl); 16 | 17 | // Logged when an operator is added or removed. 18 | event ApprovalForAll( 19 | address indexed owner, 20 | address indexed operator, 21 | bool approved 22 | ); 23 | 24 | function setRecord( 25 | bytes32 node, 26 | address owner, 27 | address resolver, 28 | uint64 ttl 29 | ) external; 30 | 31 | function setSubnodeRecord( 32 | bytes32 node, 33 | bytes32 label, 34 | address owner, 35 | address resolver, 36 | uint64 ttl 37 | ) external; 38 | 39 | function setSubnodeOwner( 40 | bytes32 node, 41 | bytes32 label, 42 | address owner 43 | ) external returns (bytes32); 44 | 45 | function setResolver(bytes32 node, address resolver) external; 46 | 47 | function setOwner(bytes32 node, address owner) external; 48 | 49 | function setTTL(bytes32 node, uint64 ttl) external; 50 | 51 | function setApprovalForAll(address operator, bool approved) external; 52 | 53 | function owner(bytes32 node) external view returns (address); 54 | 55 | function resolver(bytes32 node) external view returns (address); 56 | 57 | function ttl(bytes32 node) external view returns (uint64); 58 | 59 | function recordExists(bytes32 node) external view returns (bool); 60 | 61 | function isApprovedForAll(address owner, address operator) 62 | external 63 | view 64 | returns (bool); 65 | } 66 | -------------------------------------------------------------------------------- /contracts/SID/resolvers/PublicResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | import "../SID.sol"; 4 | import "./ResolverBase.sol"; 5 | 6 | /** 7 | * A more advanced resolver that allows for multiple records of the same domain. 8 | */ 9 | contract PublicResolver is ResolverBase { 10 | SID immutable sid; 11 | uint private constant COIN_TYPE_BNB = 60; 12 | mapping(bytes32 => mapping(uint => bytes)) _addresses; 13 | 14 | constructor(SID _sid) { 15 | sid = _sid; 16 | } 17 | 18 | function addr(bytes32 node) public view virtual returns (address payable) { 19 | bytes memory a = addr(node, COIN_TYPE_BNB); 20 | if (a.length == 0) { 21 | return payable(0); 22 | } 23 | return bytesToAddress(a); 24 | } 25 | 26 | function setAddr(bytes32 node, address a) 27 | external 28 | virtual 29 | authorised(node) 30 | { 31 | setAddr(node, COIN_TYPE_BNB, addressToBytes(a)); 32 | } 33 | 34 | function setAddr( 35 | bytes32 node, 36 | uint coinType, 37 | bytes memory a 38 | ) public virtual authorised(node) { 39 | if (coinType == COIN_TYPE_BNB) {} 40 | _addresses[node][coinType] = a; 41 | } 42 | 43 | function isAuthorised(bytes32 node) internal view override returns (bool) { 44 | address owner = sid.owner(node); 45 | return owner == msg.sender; 46 | } 47 | 48 | function bytesToAddress(bytes memory b) 49 | internal 50 | pure 51 | returns (address payable a) 52 | { 53 | require(b.length == 20); 54 | assembly { 55 | a := div(mload(add(b, 32)), exp(256, 12)) 56 | } 57 | } 58 | 59 | function addressToBytes(address a) internal pure returns (bytes memory b) { 60 | b = new bytes(20); 61 | assembly { 62 | mstore(add(b, 32), mul(a, exp(256, 12))) 63 | } 64 | } 65 | 66 | function addr(bytes32 node, uint coinType) 67 | public 68 | view 69 | virtual 70 | returns (bytes memory) 71 | { 72 | return _addresses[node][coinType]; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /test/FeedAdapter.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import { ethers } from "hardhat"; 3 | import { getSIDResolverAddressByEnv } from "./helpers/setup"; 4 | import { getNodeValue } from '../scripts/sid' 5 | import { Contract } from "ethers"; 6 | 7 | describe("Consumer through FeedAdapter", async function () { 8 | 9 | // never timeout 10 | this.timeout(0); 11 | 12 | let adapter: Contract 13 | let resolver: Contract 14 | 15 | // Access the full list of supporting symbol pairs through the following links: 16 | // BSC Mainnet: https://oracle.binance.com/docs/price-feeds/contract-addresses/bnb-mainnet 17 | // BSC Testnet: https://oracle.binance.com/docs/price-feeds/contract-addresses/bnb-testnet 18 | const symbolPair = 'btc-usd' 19 | 20 | before(async function () { 21 | // get adapter address 22 | resolver = await ethers.getContractAt('PublicResolver', getSIDResolverAddressByEnv()) 23 | // NOTE: We use lowercase in Space ID 24 | const adapterAddress = await resolver['addr(bytes32)'](getNodeValue(symbolPair.toLowerCase())) 25 | // FeedAdapterInterface or AggregatorV2V3Interface are equivalent 26 | adapter = await ethers.getContractAt('AggregatorV2V3Interface', adapterAddress) 27 | }) 28 | 29 | it("Should be able to get description from an adapter's address", async function () { 30 | const description = await adapter.description() 31 | assert(description != null) 32 | console.log(`Description of the adapter: ${description}`) 33 | }); 34 | 35 | it("Should be able to retrieve latest round data", async function () { 36 | const roundData = await adapter.latestRoundData() 37 | assert(roundData != null) 38 | console.log(`Latest round data for BTC/USD: ${roundData.toString()}`) 39 | }) 40 | 41 | it("Should be able to retrieve decimals for a pair", async function () { 42 | const decimals = await adapter.decimals() 43 | assert(decimals > 0) 44 | console.log(`Decimals for the answer of BTC/USD: ${decimals.toString()}`) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /test/FeedRegistry.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from "chai"; 2 | import { ethers } from "hardhat"; 3 | import { getSIDResolverAddressByEnv } from "./helpers/setup"; 4 | import { Contract } from "ethers"; 5 | import { getNodeValue } from "../scripts/sid"; 6 | 7 | describe("Consumer through FeedRegistry", async function () { 8 | 9 | // never timeout 10 | this.timeout(0); 11 | 12 | let registry: Contract 13 | let resolver: Contract 14 | let baseSymbolAddress: string 15 | let quoteSymbolAddress: string 16 | 17 | // ----- Change your query here 18 | const BASE_SYMBOL = 'BTC' 19 | const QUOTE_SYMBOL = 'USD' 20 | 21 | before(async function () { 22 | // get adapter address 23 | resolver = await ethers.getContractAt('PublicResolver', getSIDResolverAddressByEnv()) 24 | const feedRegistryAddress = await resolver['addr(bytes32)'](getNodeValue('fr')) 25 | console.log(feedRegistryAddress) 26 | registry = await ethers.getContractAt('FeedRegistryInterface', feedRegistryAddress) 27 | 28 | // get base and quote token address 29 | const pairDetail = await registry.getTradingPairDetails(BASE_SYMBOL, QUOTE_SYMBOL) 30 | baseSymbolAddress = pairDetail[0] 31 | quoteSymbolAddress = pairDetail[1] 32 | console.log(`${BASE_SYMBOL} token address: `, pairDetail[0]) 33 | console.log(`${QUOTE_SYMBOL} token address: `, pairDetail[1]) 34 | console.log(`${BASE_SYMBOL}/${QUOTE_SYMBOL} FeedAdapter contract address: `, pairDetail[2]) 35 | }) 36 | 37 | it("Should be able to get all the available pairs", async function () { 38 | const totalCount = await registry.totalPairsAvailable() 39 | assert(totalCount > 0) 40 | console.log(`Total pairs available: ${totalCount.toString()}`) 41 | 42 | const allPairs = await registry.getAllPairs() 43 | console.log(`All pairs available on registry: ${allPairs.map(p => p.toString().replace(',', '/'))}`) 44 | }); 45 | 46 | it("Should be able to retrieve latest round data", async function () { 47 | const roundData = await registry.latestRoundData(baseSymbolAddress, quoteSymbolAddress) 48 | assert(roundData != null) 49 | console.log(`Latest round data for BTC/USD: ${roundData.toString()}`) 50 | }) 51 | 52 | it("Should be able to retrieve decimals for a pair", async function () { 53 | const decimals = await registry.decimals(baseSymbolAddress,quoteSymbolAddress) 54 | assert(decimals > 0) 55 | console.log(`Decimals for the answer of BTC/USD: ${decimals.toString()}`) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /contracts/mock/PriceConsumerFromRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | import "../interfaces/FeedRegistryInterface.sol"; 5 | import "../library/EnumerableTradingPairMap.sol"; 6 | import "../SID/SIDRegistry.sol"; 7 | import "../SID/resolvers/PublicResolver.sol"; 8 | 9 | /* 10 | * Consumer contract which retrieves price data from FeedRegistry 11 | * Access control is enforced for most of the trading pairs. Only whitelisted users will be able to retrieve the price data onchain 12 | * However, some pairs will be open to all 13 | * See https://oracle.binance.com/docs/price-feeds/feed-registry/ for more details. 14 | */ 15 | contract PriceConsumerFromRegistry { 16 | FeedRegistryInterface internal s_feedRegistry; 17 | 18 | constructor(address _sidRegistryAddress, bytes32 _feedRegistryNodeHash) { 19 | SIDRegistry _sidRegistry = SIDRegistry(_sidRegistryAddress); 20 | address _publicResolverAddress = _sidRegistry.resolver(_feedRegistryNodeHash); 21 | PublicResolver _publicResolver = PublicResolver(_publicResolverAddress); 22 | address _feedRegistryAddress = _publicResolver.addr(_feedRegistryNodeHash); 23 | s_feedRegistry = FeedRegistryInterface(_feedRegistryAddress); 24 | } 25 | 26 | // Get all the available feeds on the registry. A pair is (base, quote) as strings 27 | function getAllFeeds() external view returns (EnumerableTradingPairMap.Pair[] memory) { 28 | return s_feedRegistry.getAllPairs(); 29 | } 30 | 31 | // Returns the details of a particular feed. 32 | // The structure returned is (Base token address, quote token address, Pair's feed adapter address) 33 | function getFeedDetails(string memory base, string memory quote) external view returns (address, address, address) { 34 | return s_feedRegistry.getTradingPairDetails(base, quote); 35 | } 36 | 37 | //Query using the token addresses 38 | function getLatestAnswer(address base, address quote) 39 | external 40 | view 41 | returns (int256 answer) 42 | { 43 | return s_feedRegistry.latestAnswer(base, quote); 44 | } 45 | 46 | //Query decimal point precision for the pair 47 | function getDecimalsForPair(address base, address quote) external view returns (uint8 decimals) { 48 | return s_feedRegistry.decimals(base, quote); 49 | } 50 | 51 | //Query by using base/quote token names 52 | function getLatestPriceByName(string calldata base, string calldata quote) external view returns (int256 answer) { 53 | return s_feedRegistry.latestAnswerByName(base, quote); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /scripts/deployMocks.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { 3 | FEED_REGISTRY_SID_SUBDOMAIN, 4 | SYMBOL_PAIR_SUBDOMAINS, 5 | } from "../constants"; 6 | import hre from 'hardhat' 7 | import { getNodeValue } from '../scripts/sid' 8 | import { getSIDRegistryAddressByEnv } from "../test/helpers/setup"; 9 | import fs from 'fs'; 10 | 11 | //Deploy to testnet/mainnet 12 | export async function main() { 13 | 14 | // Deploy feed registry 15 | const frNode = getNodeValue(`${FEED_REGISTRY_SID_SUBDOMAIN}`) 16 | const PriceConsumerFromRegistry = await ethers.getContractFactory("PriceConsumerFromRegistry"); 17 | const feedRegistryConsumer = await PriceConsumerFromRegistry.deploy( 18 | getSIDRegistryAddressByEnv(), 19 | frNode, 20 | { 21 | gasLimit: 1000000 22 | } 23 | ); 24 | await feedRegistryConsumer.deployed(); 25 | // save arguments 26 | fs.writeFileSync('./fr.js', `module.exports = ${JSON.stringify([getSIDRegistryAddressByEnv(), frNode])}`) 27 | console.log(`[${hre.network.name}] Mock Price Consumer through Registry deployed to: ${feedRegistryConsumer.address}`); 28 | 29 | // deploy feed adapters on by one 30 | const feedAdapters = {} 31 | for (const symbolPair of SYMBOL_PAIR_SUBDOMAINS) { 32 | // const adapterAddress = await resolver['addr(bytes32)'](getNodeValue(symbolPair)) 33 | const PriceConsumerWithAdapter = await ethers.getContractFactory("PriceConsumerWithAdapter"); 34 | const adapterConsumer = await PriceConsumerWithAdapter.deploy( 35 | getSIDRegistryAddressByEnv(), 36 | getNodeValue(symbolPair), 37 | { 38 | gasLimit: 1000000 39 | }); 40 | await adapterConsumer.deployed(); 41 | feedAdapters[symbolPair] = adapterConsumer.address; 42 | fs.writeFileSync(`./${symbolPair}.js`, `module.exports = ${JSON.stringify([getSIDRegistryAddressByEnv(), getNodeValue(symbolPair)])}`) 43 | console.log(`[${hre.network.name}] Mock Price Consumer from ${symbolPair} Feed Adapter deployed to: ${adapterConsumer.address}`); 44 | } 45 | 46 | console.log('Verify your contracts running the these commands, then you can interact with your consumer contracts on BSCScan!') 47 | console.log('*** Don\'t forget to set your ETHERSCAN_API_KEY in the .env file ***') 48 | console.log(`npx hardhat verify ${feedRegistryConsumer.address} --network ${hre.network.name} --constructor-args ./fr.js`) 49 | for(const addr in feedAdapters) { 50 | console.log(`npx hardhat verify ${feedAdapters[addr]} --network ${hre.network.name} --constructor-args ./${addr}.js`) 51 | } 52 | } 53 | 54 | main().catch((error) => { 55 | console.error(error); 56 | process.exitCode = 1; 57 | }); 58 | -------------------------------------------------------------------------------- /contracts/library/EnumerableTradingPairMap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | /** 5 | * @notice Library providing database functionality for trading pairs 6 | * @author Sri Krishna Mannem 7 | */ 8 | library EnumerableTradingPairMap { 9 | struct TradingPairDetails { 10 | address baseAssetAddress; 11 | address quoteAssetAddress; 12 | address feedAddress; 13 | } 14 | struct Pair { 15 | string baseAsset; 16 | string quoteAsset; 17 | } 18 | 19 | struct EnumerableMap { 20 | Pair[] keyList; 21 | mapping(bytes32 => mapping(bytes32 => uint256)) keyPointers; 22 | mapping(bytes32 => mapping(bytes32 => TradingPairDetails)) values; 23 | } 24 | 25 | /** 26 | * @notice insert a key. 27 | * @dev duplicate keys are not permitted. 28 | * @param self storage space for pairs 29 | * @param base base asset to insert 30 | * @param quote quote asset to insert 31 | * @param value details of the pair to insert 32 | */ 33 | function insert( 34 | EnumerableMap storage self, 35 | string memory base, 36 | string memory quote, 37 | TradingPairDetails memory value 38 | ) internal { 39 | require(!exists(self, base, quote), "Insert: Key already exists in the mapping"); 40 | self.keyList.push(Pair(base, quote)); 41 | self.keyPointers[toBytes32(base)][toBytes32(quote)] = self.keyList.length - 1; 42 | self.values[toBytes32(base)][toBytes32(quote)] = value; 43 | } 44 | 45 | /** 46 | * @notice remove a key 47 | * @dev key to remove must exist. 48 | * @param self storage space for pairs 49 | * @param base base asset to insert 50 | * @param quote quote asset to insert 51 | */ 52 | function remove( 53 | EnumerableMap storage self, 54 | string memory base, 55 | string memory quote 56 | ) internal { 57 | require(exists(self, base, quote), "Remove: Key does not exist in the mapping"); 58 | uint256 last = count(self) - 1; 59 | uint256 indexToReplace = self.keyPointers[toBytes32(base)][toBytes32(quote)]; 60 | if (indexToReplace != last) { 61 | Pair memory keyToMove = self.keyList[last]; 62 | self.keyPointers[toBytes32(keyToMove.baseAsset)][toBytes32(keyToMove.quoteAsset)] = indexToReplace; 63 | self.keyList[indexToReplace] = keyToMove; 64 | } 65 | delete self.keyPointers[toBytes32(base)][toBytes32(quote)]; 66 | self.keyList.pop(); //Purge last element 67 | delete self.values[toBytes32(base)][toBytes32(quote)]; 68 | } 69 | 70 | /** 71 | * @notice Get trading pair details 72 | * @param self storage space for pairs 73 | * @param base base asset of pair 74 | * @param quote quote asset of pair 75 | * @return trading pair details (base address, quote address, feedAdapter address) 76 | */ 77 | function getTradingPair( 78 | EnumerableMap storage self, 79 | string memory base, 80 | string memory quote 81 | ) external view returns (TradingPairDetails memory) { 82 | require(exists(self, base, quote), "Get trading pair: Key does not exist in the mapping"); 83 | return self.values[toBytes32(base)][toBytes32(quote)]; 84 | } 85 | 86 | /* 87 | * @param self storage space for pairs 88 | * @return all the pairs in memory (base address, quote address) 89 | */ 90 | function getAllPairs(EnumerableMap storage self) external view returns (Pair[] memory) { 91 | return self.keyList; 92 | } 93 | 94 | /* 95 | * @param self storage space for pairs 96 | * @return total number of available pairs 97 | */ 98 | function count(EnumerableMap storage self) internal view returns (uint256) { 99 | return (self.keyList.length); 100 | } 101 | 102 | /** 103 | * @notice check if a key is in the Set. 104 | * @param self storage space for pairs 105 | * @param base base asset to insert 106 | * @param quote quote asset to insert 107 | * @return bool true if a pair exists 108 | */ 109 | function exists( 110 | EnumerableMap storage self, 111 | string memory base, 112 | string memory quote 113 | ) internal view returns (bool) { 114 | if (self.keyList.length == 0) return false; 115 | return 116 | pairToBytes32(self.keyList[self.keyPointers[toBytes32(base)][toBytes32(quote)]]) == 117 | pairToBytes32(Pair(base, quote)); 118 | } 119 | 120 | /** 121 | * @dev Compute the hash of an asset string 122 | */ 123 | function toBytes32(string memory s) private pure returns (bytes32) { 124 | return (keccak256(bytes(s))); 125 | } 126 | 127 | /** 128 | * @dev Compute the hash of a trading pair 129 | */ 130 | function pairToBytes32(Pair memory p) private pure returns (bytes32) { 131 | return keccak256(abi.encode(p.baseAsset, "/", p.quoteAsset)); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /contracts/interfaces/VRFCoordinatorInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface VRFCoordinatorInterface { 5 | /** 6 | * @notice Get configuration relevant for making requests 7 | * @return minimumRequestConfirmations global min for request confirmations 8 | * @return maxGasLimit global max for request gas limit 9 | * @return s_provingKeyHashes list of registered key hashes 10 | */ 11 | function getRequestConfig() 12 | external 13 | view 14 | returns ( 15 | uint16, 16 | uint32, 17 | bytes32[] memory 18 | ); 19 | 20 | /** 21 | * @notice Request a set of random words. 22 | * @param keyHash - Corresponds to a particular oracle job which uses 23 | * that key for generating the VRF proof. Different keyHash's have different gas price 24 | * ceilings, so you can select a specific one to bound your maximum per request cost. 25 | * @param subId - The ID of the VRF subscription. Must be funded 26 | * with the minimum subscription balance required for the selected keyHash. 27 | * @param minimumRequestConfirmations - How many blocks you'd like the 28 | * oracle to wait before responding to the request. See SECURITY CONSIDERATIONS 29 | * for why you may want to request more. The acceptable range is 30 | * [minimumRequestBlockConfirmations, 200]. 31 | * @param callbackGasLimit - How much gas you'd like to receive in your 32 | * fulfillRandomWords callback. Note that gasleft() inside fulfillRandomWords 33 | * may be slightly less than this amount because of gas used calling the function 34 | * (argument decoding etc.), so you may need to request slightly more than you expect 35 | * to have inside fulfillRandomWords. The acceptable range is 36 | * [0, maxGasLimit] 37 | * @param numWords - The number of uint256 random values you'd like to receive 38 | * in your fulfillRandomWords callback. Note these numbers are expanded in a 39 | * secure way by the VRFCoordinator from a single random value supplied by the oracle. 40 | * @return requestId - A unique identifier of the request. Can be used to match 41 | * a request to a response in fulfillRandomWords. 42 | */ 43 | function requestRandomWords( 44 | bytes32 keyHash, 45 | uint64 subId, 46 | uint16 minimumRequestConfirmations, 47 | uint32 callbackGasLimit, 48 | uint32 numWords 49 | ) external returns (uint256 requestId); 50 | 51 | /** 52 | * @notice Create a VRF subscription. 53 | * @return subId - A unique subscription id. 54 | * @dev You can manage the consumer set dynamically with addConsumer/removeConsumer. 55 | * @dev Note to fund the subscription, use transferAndCall. For example 56 | */ 57 | function createSubscription() external returns (uint64 subId); 58 | 59 | /** 60 | * @notice Get a VRF subscription. 61 | * @param subId - ID of the subscription 62 | * @return balance - BNB balance of the subscription in juels. 63 | * @return reqCount - number of requests for this subscription, determines fee tier. 64 | * @return owner - owner of the subscription. 65 | * @return consumers - list of consumer address which are able to use this subscription. 66 | */ 67 | function getSubscription(uint64 subId) 68 | external 69 | view 70 | returns ( 71 | uint96 balance, 72 | uint64 reqCount, 73 | address owner, 74 | address[] memory consumers 75 | ); 76 | 77 | /** 78 | * @notice Request subscription owner transfer. 79 | * @param subId - ID of the subscription 80 | * @param newOwner - proposed new owner of the subscription 81 | */ 82 | function requestSubscriptionOwnerTransfer(uint64 subId, address newOwner) external; 83 | 84 | /** 85 | * @notice Request subscription owner transfer. 86 | * @param subId - ID of the subscription 87 | * @dev will revert if original owner of subId has 88 | * not requested that msg.sender become the new owner. 89 | */ 90 | function acceptSubscriptionOwnerTransfer(uint64 subId) external; 91 | 92 | /** 93 | * @notice Add a consumer to a VRF subscription. 94 | * @param subId - ID of the subscription 95 | * @param consumer - New consumer which can use the subscription 96 | */ 97 | function addConsumer(uint64 subId, address consumer) external; 98 | 99 | /** 100 | * @notice Remove a consumer from a VRF subscription. 101 | * @param subId - ID of the subscription 102 | * @param consumer - Consumer to remove from the subscription 103 | */ 104 | function removeConsumer(uint64 subId, address consumer) external; 105 | 106 | /** 107 | * @notice Cancel a subscription 108 | * @param subId - ID of the subscription 109 | * @param to - Where to send the remaining BNB to 110 | */ 111 | function cancelSubscription(uint64 subId, address to) external; 112 | 113 | /* 114 | * @notice Check to see if there exists a request commitment consumers 115 | * for all consumers and keyhashes for a given sub. 116 | * @param subId - ID of the subscription 117 | * @return true if there exists at least one unfulfilled request for the subscription, false 118 | * otherwise. 119 | */ 120 | function pendingRequestExists(uint64 subId) external view returns (bool); 121 | 122 | } 123 | -------------------------------------------------------------------------------- /contracts/interfaces/FeedRegistryInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | import "./AggregatorV2V3Interface.sol"; 5 | import "./DenominationsInterface.sol"; 6 | 7 | /** 8 | * @dev Feed registry must expose AggregatorV2V3Interface to be able to serve FeedAdapters for backward compatibility 9 | */ 10 | interface FeedRegistryInterface is DenominationsInterface { 11 | struct Phase { 12 | uint16 phaseId; 13 | uint80 startingAggregatorRoundId; 14 | uint80 endingAggregatorRoundId; 15 | } 16 | struct RoundData { 17 | uint80 roundId; 18 | int256 answer; 19 | uint256 startedAt; 20 | uint256 updatedAt; 21 | uint80 answeredInRound; 22 | } 23 | event FeedProposed( 24 | address indexed asset, 25 | address indexed denomination, 26 | address indexed proposedAggregator, 27 | address currentAggregator, 28 | address sender 29 | ); 30 | event FeedConfirmed( 31 | address indexed asset, 32 | address indexed denomination, 33 | address indexed latestAggregator, 34 | address previousAggregator, 35 | uint16 nextPhaseId, 36 | address sender 37 | ); 38 | 39 | //Latest interface to query prices through FeedRegistry 40 | function decimalsByName(string memory base, string memory quote) external view returns (uint8); 41 | 42 | function descriptionByName(string memory base, string memory quote) external view returns (string memory); 43 | 44 | function versionByName(string memory base, string memory quote) external view returns (uint256); 45 | 46 | function latestRoundDataByName(string memory base, string memory quote) 47 | external 48 | view 49 | returns ( 50 | uint80 roundId, 51 | int256 answer, 52 | uint256 startedAt, 53 | uint256 updatedAt, 54 | uint80 answeredInRound 55 | ); 56 | 57 | function latestAnswerByName(string memory base, string memory quote) external view returns (int256 answer); 58 | 59 | function getMultipleLatestRoundData(string[] memory bases, string[] memory quotes) 60 | external 61 | view 62 | returns (RoundData[] memory); 63 | 64 | // V3 AggregatorInterface 65 | function decimals(address base, address quote) external view returns (uint8); 66 | 67 | function description(address base, address quote) external view returns (string memory); 68 | 69 | function version(address base, address quote) external view returns (uint256); 70 | 71 | function latestRoundData(address base, address quote) 72 | external 73 | view 74 | returns ( 75 | uint80 roundId, 76 | int256 answer, 77 | uint256 startedAt, 78 | uint256 updatedAt, 79 | uint80 answeredInRound 80 | ); 81 | 82 | function getRoundData( 83 | address base, 84 | address quote, 85 | uint80 _roundId 86 | ) 87 | external 88 | view 89 | returns ( 90 | uint80 roundId, 91 | int256 answer, 92 | uint256 startedAt, 93 | uint256 updatedAt, 94 | uint80 answeredInRound 95 | ); 96 | 97 | // V2 AggregatorInterface 98 | 99 | function latestAnswer(address base, address quote) external view returns (int256 answer); 100 | 101 | function latestTimestamp(address base, address quote) external view returns (uint256 timestamp); 102 | 103 | function latestRound(address base, address quote) external view returns (uint256 roundId); 104 | 105 | function getAnswer( 106 | address base, 107 | address quote, 108 | uint256 roundId 109 | ) external view returns (int256 answer); 110 | 111 | function getTimestamp( 112 | address base, 113 | address quote, 114 | uint256 roundId 115 | ) external view returns (uint256 timestamp); 116 | 117 | // Registry getters 118 | 119 | function getFeed(address base, address quote) external view returns (AggregatorV2V3Interface aggregator); 120 | 121 | function getPhaseFeed( 122 | address base, 123 | address quote, 124 | uint16 phaseId 125 | ) external view returns (AggregatorV2V3Interface aggregator); 126 | 127 | function isFeedEnabled(address aggregator) external view returns (bool); 128 | 129 | function getPhase( 130 | address base, 131 | address quote, 132 | uint16 phaseId 133 | ) external view returns (Phase memory phase); 134 | 135 | // Round helpers 136 | 137 | function getRoundFeed( 138 | address base, 139 | address quote, 140 | uint80 roundId 141 | ) external view returns (AggregatorV2V3Interface aggregator); 142 | 143 | function getPhaseRange( 144 | address base, 145 | address quote, 146 | uint16 phaseId 147 | ) external view returns (uint80 startingRoundId, uint80 endingRoundId); 148 | 149 | function getPreviousRoundId( 150 | address base, 151 | address quote, 152 | uint80 roundId 153 | ) external view returns (uint80 previousRoundId); 154 | 155 | function getNextRoundId( 156 | address base, 157 | address quote, 158 | uint80 roundId 159 | ) external view returns (uint80 nextRoundId); 160 | 161 | // Feed management 162 | 163 | function proposeFeed( 164 | address base, 165 | address quote, 166 | address aggregator 167 | ) external; 168 | 169 | function confirmFeed( 170 | address base, 171 | address quote, 172 | address aggregator 173 | ) external; 174 | 175 | // Proposed aggregator 176 | 177 | function getProposedFeed(address base, address quote) 178 | external 179 | view 180 | returns (AggregatorV2V3Interface proposedAggregator); 181 | 182 | function proposedGetRoundData( 183 | address base, 184 | address quote, 185 | uint80 roundId 186 | ) 187 | external 188 | view 189 | returns ( 190 | uint80 id, 191 | int256 answer, 192 | uint256 startedAt, 193 | uint256 updatedAt, 194 | uint80 answeredInRound 195 | ); 196 | 197 | function proposedLatestRoundData(address base, address quote) 198 | external 199 | view 200 | returns ( 201 | uint80 id, 202 | int256 answer, 203 | uint256 startedAt, 204 | uint256 updatedAt, 205 | uint80 answeredInRound 206 | ); 207 | 208 | // Phases 209 | function getCurrentPhaseId(address base, address quote) external view returns (uint16 currentPhaseId); 210 | } 211 | -------------------------------------------------------------------------------- /contracts/SID/SIDRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.4; 3 | 4 | import "./SID.sol"; 5 | 6 | /** 7 | * The SID registry contract. 8 | */ 9 | contract SIDRegistry is SID { 10 | 11 | struct Record { 12 | address owner; 13 | address resolver; 14 | uint64 ttl; 15 | } 16 | 17 | mapping (bytes32 => Record) records; 18 | mapping (address => mapping(address => bool)) operators; 19 | 20 | // Permits modifications only by the owner of the specified node. 21 | modifier authorised(bytes32 node) { 22 | address owner = records[node].owner; 23 | require(owner == msg.sender || operators[owner][msg.sender]); 24 | _; 25 | } 26 | 27 | /** 28 | * @dev Constructs a new ENS registry. 29 | */ 30 | constructor() public { 31 | records[0x0].owner = msg.sender; 32 | } 33 | 34 | /** 35 | * @dev Sets the record for a node. 36 | * @param node The node to update. 37 | * @param owner The address of the new owner. 38 | * @param resolver The address of the resolver. 39 | * @param ttl The TTL in seconds. 40 | */ 41 | function setRecord(bytes32 node, address owner, address resolver, uint64 ttl) external virtual override { 42 | setOwner(node, owner); 43 | _setResolverAndTTL(node, resolver, ttl); 44 | } 45 | 46 | /** 47 | * @dev Sets the record for a subnode. 48 | * @param node The parent node. 49 | * @param label The hash of the label specifying the subnode. 50 | * @param owner The address of the new owner. 51 | * @param resolver The address of the resolver. 52 | * @param ttl The TTL in seconds. 53 | */ 54 | function setSubnodeRecord(bytes32 node, bytes32 label, address owner, address resolver, uint64 ttl) external virtual override { 55 | bytes32 subnode = setSubnodeOwner(node, label, owner); 56 | _setResolverAndTTL(subnode, resolver, ttl); 57 | } 58 | 59 | /** 60 | * @dev Transfers ownership of a node to a new address. May only be called by the current owner of the node. 61 | * @param node The node to transfer ownership of. 62 | * @param owner The address of the new owner. 63 | */ 64 | function setOwner(bytes32 node, address owner) public virtual override authorised(node) { 65 | _setOwner(node, owner); 66 | emit Transfer(node, owner); 67 | } 68 | 69 | /** 70 | * @dev Transfers ownership of a subnode keccak256(node, label) to a new address. May only be called by the owner of the parent node. 71 | * @param node The parent node. 72 | * @param label The hash of the label specifying the subnode. 73 | * @param owner The address of the new owner. 74 | */ 75 | function setSubnodeOwner(bytes32 node, bytes32 label, address owner) public virtual override authorised(node) returns(bytes32) { 76 | bytes32 subnode = keccak256(abi.encodePacked(node, label)); 77 | _setOwner(subnode, owner); 78 | emit NewOwner(node, label, owner); 79 | return subnode; 80 | } 81 | 82 | /** 83 | * @dev Sets the resolver address for the specified node. 84 | * @param node The node to update. 85 | * @param resolver The address of the resolver. 86 | */ 87 | function setResolver(bytes32 node, address resolver) public virtual override authorised(node) { 88 | emit NewResolver(node, resolver); 89 | records[node].resolver = resolver; 90 | } 91 | 92 | /** 93 | * @dev Sets the TTL for the specified node. 94 | * @param node The node to update. 95 | * @param ttl The TTL in seconds. 96 | */ 97 | function setTTL(bytes32 node, uint64 ttl) public virtual override authorised(node) { 98 | emit NewTTL(node, ttl); 99 | records[node].ttl = ttl; 100 | } 101 | 102 | /** 103 | * @dev Enable or disable approval for a third party ("operator") to manage 104 | * all of `msg.sender`'s ENS records. Emits the ApprovalForAll event. 105 | * @param operator Address to add to the set of authorized operators . 106 | * @param approved True if the operator is approved, false to revoke approval. 107 | */ 108 | function setApprovalForAll(address operator, bool approved) external virtual override { 109 | operators[msg.sender][operator] = approved; 110 | emit ApprovalForAll(msg.sender, operator, approved); 111 | } 112 | 113 | /** 114 | * @dev Returns the address that owns the specified node. 115 | * @param node The specified node. 116 | * @return address of the owner. 117 | */ 118 | function owner(bytes32 node) public virtual override view returns (address) { 119 | address addr = records[node].owner; 120 | if (addr == address(this)) { 121 | return address(0x0); 122 | } 123 | 124 | return addr; 125 | } 126 | 127 | /** 128 | * @dev Returns the address of the resolver for the specified node. 129 | * @param node The specified node. 130 | * @return address of the resolver. 131 | */ 132 | function resolver(bytes32 node) public virtual override view returns (address) { 133 | return records[node].resolver; 134 | } 135 | 136 | /** 137 | * @dev Returns the TTL of a node, and any records associated with it. 138 | * @param node The specified node. 139 | * @return ttl of the node. 140 | */ 141 | function ttl(bytes32 node) public virtual override view returns (uint64) { 142 | return records[node].ttl; 143 | } 144 | 145 | /** 146 | * @dev Returns whether a record has been imported to the registry. 147 | * @param node The specified node. 148 | * @return Bool if record exists 149 | */ 150 | function recordExists(bytes32 node) public virtual override view returns (bool) { 151 | return records[node].owner != address(0x0); 152 | } 153 | 154 | /** 155 | * @dev Query if an address is an authorized operator for another address. 156 | * @param owner The address that owns the records. 157 | * @param operator The address that acts on behalf of the owner. 158 | * @return True if `operator` is an approved operator for `owner`, false otherwise. 159 | */ 160 | function isApprovedForAll(address owner, address operator) external virtual override view returns (bool) { 161 | return operators[owner][operator]; 162 | } 163 | 164 | function _setOwner(bytes32 node, address owner) internal virtual { 165 | records[node].owner = owner; 166 | } 167 | 168 | function _setResolverAndTTL(bytes32 node, address resolver, uint64 ttl) internal { 169 | if(resolver != records[node].resolver) { 170 | records[node].resolver = resolver; 171 | emit NewResolver(node, resolver); 172 | } 173 | 174 | if(ttl != records[node].ttl) { 175 | records[node].ttl = ttl; 176 | emit NewTTL(node, ttl); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /contracts/mock/VRFConsumerBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /** **************************************************************************** 5 | * @notice Interface for contracts using VRF randomness 6 | * ***************************************************************************** 7 | * @dev PURPOSE 8 | * 9 | * @dev Reggie the Random Oracle (not his real job) wants to provide randomness 10 | * @dev to Vera the verifier in such a way that Vera can be sure he's not 11 | * @dev making his output up to suit himself. Reggie provides Vera a public key 12 | * @dev to which he knows the secret key. Each time Vera provides a seed to 13 | * @dev Reggie, he gives back a value which is computed completely 14 | * @dev deterministically from the seed and the secret key. 15 | * 16 | * @dev Reggie provides a proof by which Vera can verify that the output was 17 | * @dev correctly computed once Reggie tells it to her, but without that proof, 18 | * @dev the output is indistinguishable to her from a uniform random sample 19 | * @dev from the output space. 20 | * 21 | * @dev The purpose of this contract is to make it easy for unrelated contracts 22 | * @dev to talk to Vera the verifier about the work Reggie is doing, to provide 23 | * @dev simple access to a verifiable source of randomness. It ensures 2 things: 24 | * @dev 1. The fulfillment came from the VRFCoordinator 25 | * @dev 2. The consumer contract implements fulfillRandomWords. 26 | * ***************************************************************************** 27 | * @dev USAGE 28 | * 29 | * @dev Calling contracts must inherit from VRFConsumerBase, and can 30 | * @dev initialize VRFConsumerBase's attributes in their constructor as 31 | * @dev shown: 32 | * 33 | * @dev contract VRFConsumer { 34 | * @dev constructor(, address _vrfCoordinator, address _link) 35 | * @dev VRFConsumerBase(_vrfCoordinator) public { 36 | * @dev 37 | * @dev } 38 | * @dev } 39 | * 40 | * @dev The oracle will have given you an ID for the VRF keypair they have 41 | * @dev committed to (let's call it keyHash). Create subscription, fund it 42 | * @dev and your consumer contract as a consumer of it (see VRFCoordinatorInterface 43 | * @dev subscription management functions). 44 | * @dev Call requestRandomWords(keyHash, subId, minimumRequestConfirmations, 45 | * @dev callbackGasLimit, numWords), 46 | * @dev see (VRFCoordinatorInterface for a description of the arguments). 47 | * 48 | * @dev Once the VRFCoordinator has received and validated the oracle's response 49 | * @dev to your request, it will call your contract's fulfillRandomWords method. 50 | * 51 | * @dev The randomness argument to fulfillRandomWords is a set of random words 52 | * @dev generated from your requestId and the blockHash of the request. 53 | * 54 | * @dev If your contract could have concurrent requests open, you can use the 55 | * @dev requestId returned from requestRandomWords to track which response is associated 56 | * @dev with which randomness request. 57 | * @dev See "SECURITY CONSIDERATIONS" for principles to keep in mind, 58 | * @dev if your contract could have multiple requests in flight simultaneously. 59 | * 60 | * @dev Colliding `requestId`s are cryptographically impossible as long as seeds 61 | * @dev differ. 62 | * 63 | * ***************************************************************************** 64 | * @dev SECURITY CONSIDERATIONS 65 | * 66 | * @dev A method with the ability to call your fulfillRandomness method directly 67 | * @dev could spoof a VRF response with any random value, so it's critical that 68 | * @dev it cannot be directly called by anything other than this base contract 69 | * @dev (specifically, by the VRFConsumerBase.rawFulfillRandomness method). 70 | * 71 | * @dev For your users to trust that your contract's random behavior is free 72 | * @dev from malicious interference, it's best if you can write it so that all 73 | * @dev behaviors implied by a VRF response are executed *during* your 74 | * @dev fulfillRandomness method. If your contract must store the response (or 75 | * @dev anything derived from it) and use it later, you must ensure that any 76 | * @dev user-significant behavior which depends on that stored value cannot be 77 | * @dev manipulated by a subsequent VRF request. 78 | * 79 | * @dev Similarly, both miners and the VRF oracle itself have some influence 80 | * @dev over the order in which VRF responses appear on the blockchain, so if 81 | * @dev your contract could have multiple VRF requests in flight simultaneously, 82 | * @dev you must ensure that the order in which the VRF responses arrive cannot 83 | * @dev be used to manipulate your contract's user-significant behavior. 84 | * 85 | * @dev Since the block hash of the block which contains the requestRandomness 86 | * @dev call is mixed into the input to the VRF *last*, a sufficiently powerful 87 | * @dev miner could, in principle, fork the blockchain to evict the block 88 | * @dev containing the request, forcing the request to be included in a 89 | * @dev different block with a different hash, and therefore a different input 90 | * @dev to the VRF. However, such an attack would incur a substantial economic 91 | * @dev cost. This cost scales with the number of blocks the VRF oracle waits 92 | * @dev until it calls responds to a request. It is for this reason that 93 | * @dev that you can signal to an oracle you'd like them to wait longer before 94 | * @dev responding to the request (however this is not enforced in the contract 95 | * @dev and so remains effective only in the case of unmodified oracle software). 96 | */ 97 | abstract contract VRFConsumerBase { 98 | address private immutable vrfCoordinator; 99 | 100 | /** 101 | * @param _vrfCoordinator address of VRFCoordinator contract 102 | */ 103 | constructor(address _vrfCoordinator) { 104 | vrfCoordinator = _vrfCoordinator; 105 | } 106 | 107 | /** 108 | * @notice fulfillRandomness handles the VRF response. Your contract must 109 | * @notice implement it. See "SECURITY CONSIDERATIONS" above for important 110 | * @notice principles to keep in mind when implementing your fulfillRandomness 111 | * @notice method. 112 | * 113 | * @dev VRFConsumerBase expects its subcontracts to have a method with this 114 | * @dev signature, and will call it once it has verified the proof 115 | * @dev associated with the randomness. (It is triggered via a call to 116 | * @dev rawFulfillRandomness, below.) 117 | * 118 | * @param requestId The Id initially returned by requestRandomness 119 | * @param randomWords the VRF output expanded to the requested number of words 120 | */ 121 | function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal virtual; 122 | 123 | // rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF 124 | // proof. rawFulfillRandomness then calls fulfillRandomness, after validating 125 | // the origin of the call 126 | function rawFulfillRandomWords(uint256 requestId, uint256[] memory randomWords) external { 127 | if (msg.sender != vrfCoordinator) { 128 | revert ('OnlyCoordinatorCanFulfill'); 129 | } 130 | fulfillRandomWords(requestId, randomWords); 131 | } 132 | } 133 | --------------------------------------------------------------------------------