Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

integrate the price feed server #11

Merged
merged 12 commits into from
Aug 10, 2021
Merged
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions pallets/oracle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,7 @@ pub mod pallet {
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(block: T::BlockNumber) -> Weight {
for i in 0..AssetsCount::<T>::get() {
let asset_info = AssetsInfo::<T>::get(i);
for (i, asset_info) in AssetsInfo::<T>::iter() {
// TODO maybe add a check if price is requested, is less operations?
let pre_pruned_prices = PrePrices::<T>::get(i);
let mut pre_prices = Vec::new();
Expand Down Expand Up @@ -518,7 +517,7 @@ pub mod pallet {
}

pub fn check_requests() {
for i in 0..AssetsCount::<T>::get() {
for (i, _) in AssetsInfo::<T>::iter() {
if Requested::<T>::get(i) {
let _ = Self::fetch_price_and_send_signed(&i);
}
Expand Down
1 change: 1 addition & 0 deletions price-feed/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ warp = "0.3"
signal-hook = "0.3"
signal-hook-tokio = { version = "0.3", features = [ "futures-v0_3" ] }
futures = "0.3"
lazy_static = "1.4.0"
19 changes: 19 additions & 0 deletions price-feed/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Draft

Currently, in the pallet, the price of an asset is expressed in USD cents, it's not a ratio between two assets like in exchanges.
The price server handle arbitrary asset pair instead of fixing the denominator to USD, e.g. ETH/BTC, ADA/BTC...

We currently use an arbitrary `asset_id` in the oracle pallet.
The server has a hardcoded map (u8 => Asset) to represent this ID.

# Getting started

1. Run an instance of the composable node.
2. Run `pythd` along with `pyth_tx` using the provided nix script:
- Open a new terminal and run `nix-shell run_pyth.nix`.
- A bash function `run` is now available to start `pythd/pyth_tx`
- Whenever you exit the terminal after having ran the `run` function, the two instances are going to be shutdown.
- You have accesss to both `pythd/pyth_tx` logs by using $PYTHD_LOG and $PYTH_TX_LOG
3. Run the price server, assuming you are running `RUST_LOG=info cargo run --bin price-feed` ![img not found](images/normal_run.png).
4. Go on your local [substrate panel](https://polkadot.js.org/apps) and add a new asset, make sure to use one of the `FNV1A64` hash as `asset_id`.
5. Trigger a price request for each `asset_id` you created and watch the oracle state machine progress.
Binary file added price-feed/images/normal_run.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 35 additions & 15 deletions price-feed/run_pyth.nix
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,48 @@ let
'';
installPhase = ''
mkdir -p $out/bin
mv pyth $out/bin
mv pythd $out/bin
mv pyth_tx $out/bin
mv ../pctest/init_key_store.sh $out/bin
'';
};
in mkShell {
packages = [ pythd ];
buildInputs = [ pythd ];
SOLANA_ENV = "devnet";
shellHook = ''
echo "Running up pyth_tx & puthd"
export PYTH_TX_LOG=$(mktemp)
pyth_tx -l $PYTH_TX_LOG -d -r api.$SOLANA_ENV.solana.com &
export PYTH_TX_PID=$!
export PYTHD_LOG=$(mktemp)
pythd -l $PYTHD_LOG -d -r api.$SOLANA_ENV.solana.com &
export PYTHD_PID=$!
teardown() {
echo "Shuting down pyth_tx & pythd";
kill -2 $PYTHD_PID
kill -2 $PYTH_TX_PID
rm $PYTH_TX_LOG
rm $PYTHD_LOG
export PYTHD_KEYSTORE=$HOME/.pythd

function init_keystore() {
echo "Creating key store"
rm -rf $PYTHD_KEYSTORE || true
mkdir -m 600 -p $PYTHD_KEYSTORE
echo "Initializing key store"
${pythd}/bin/pyth init_key -k $PYTHD_KEYSTORE
echo "Populating key store"
${pythd}/bin/init_key_store.sh $SOLANA_ENV $PYTHD_KEYSTORE
}

function run() {
echo "Running pyth_tx"
export PYTH_TX_LOG=$(mktemp)
${pythd}/bin/pyth_tx -l $PYTH_TX_LOG -d -r api.$SOLANA_ENV.solana.com &
export PYTH_TX_PID=$!

echo "Running pythd"
export PYTHD_LOG=$(mktemp)
${pythd}/bin/pythd -k $PYTHD_KEYSTORE -l $PYTHD_LOG -d -r api.$SOLANA_ENV.solana.com &
export PYTHD_PID=$!

function teardown() {
echo "Shuting down pyth_tx & pythd";
kill -2 $PYTHD_PID
kill -2 $PYTH_TX_PID
rm $PYTH_TX_LOG
rm $PYTHD_LOG
}

trap teardown EXIT
}
trap teardown EXIT
'';
}
139 changes: 129 additions & 10 deletions price-feed/src/asset.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,134 @@
use serde::Serialize;
use std::{
collections::{HashMap, HashSet},
convert::TryFrom,
num::ParseIntError,
str::FromStr,
};

custom_derive! {
#[derive(EnumFromStr, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum Asset {
BTC,
ETH,
LTC,
USD,
}
#[derive(EnumFromStr, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum Asset {
BTC,
ETH,
LTC,
DOGE,
SOL,
LUNA,
AAPL,
BNB,
TSLA,
BCH,
SRM,
AMZN,
GOOG,
NFLX,
XAU,
AMC,
SPY,
GME,
GE,
QQQ,
USDT,
USDC,
GBP,
EUR,
USD,
}
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct AssetPair(pub Asset, pub Asset);

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize)]
#[repr(transparent)]
pub struct AssetIndex(u8);

pub enum AssetIndexError {
NotANumber(ParseIntError),
AssetNotFound,
}

lazy_static! {
/*
The map of valid asset we are allowed to ask price for.
We must not swap two indexes.
*/
pub static ref INDEX_TO_ASSET: HashMap<AssetIndex, Asset> = [
(0, Asset::BTC),
(1, Asset::ETH),
(2, Asset::LTC),
(3, Asset::DOGE),
(4, Asset::SOL),
(5, Asset::LUNA),
(6, Asset::AAPL),
(7, Asset::BNB),
(6, Asset::TSLA),
(7, Asset::BCH),
(8, Asset::SRM),
(9, Asset::AMZN),
(10, Asset::GOOG),
(11, Asset::NFLX),
(12, Asset::XAU),
(13, Asset::AMC),
(14, Asset::SPY),
(15, Asset::GME),
(16, Asset::GE),
(17, Asset::QQQ),
(18, Asset::USDT),
(19, Asset::USDC),
(20, Asset::GBP),
(21, Asset::EUR),
]
.iter()
.map(|&(i, a)| (AssetIndex(i), a))
.collect();
pub static ref ASSET_TO_INDEX: HashMap<Asset, AssetIndex> =
INDEX_TO_ASSET.iter().map(|(&i, &a)| (a, i)).collect();
pub static ref VALID_ASSETS: HashSet<Asset> =
INDEX_TO_ASSET.values().copied().collect();
}

pub type AssetPair = (Asset, Asset);
impl FromStr for AssetIndex {
type Err = AssetIndexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let asset_pair_index =
AssetIndex(FromStr::from_str(s).map_err(AssetIndexError::NotANumber)?);
if INDEX_TO_ASSET.contains_key(&asset_pair_index) {
Ok(asset_pair_index)
} else {
Err(AssetIndexError::AssetNotFound)
}
}
}

impl AssetPair {
/*
We currently only allow X/USD
*/
pub fn new(x: Asset, y: Asset) -> Option<Self> {
match (x, y) {
(Asset::USD, _) => None,
(_, Asset::USD) => Some(AssetPair(x, y)),
_ => None,
}
}

pub fn symbol(&self) -> String {
format!("{:?}/{:?}", self.0, self.1)
}
}

impl TryFrom<Asset> for AssetIndex {
type Error = ();
fn try_from(asset: Asset) -> Result<AssetIndex, Self::Error> {
ASSET_TO_INDEX.get(&asset).copied().ok_or(())
}
}

pub fn symbol((x, y): AssetPair) -> String {
format!("{:?}/{:?}", x, y)
impl TryFrom<AssetIndex> for Asset {
type Error = ();
fn try_from(asset_index: AssetIndex) -> Result<Asset, Self::Error> {
INDEX_TO_ASSET.get(&asset_index).copied().ok_or(())
}
}
Loading