Skip to content

Commit

Permalink
Implement price averged index using multiple tokens (#5)
Browse files Browse the repository at this point in the history
add eth, implement index calculation with error
  • Loading branch information
jameswilliams1 authored May 25, 2022
1 parent e34320f commit 85da516
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 16 deletions.
4 changes: 4 additions & 0 deletions Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ url = "mainnet-beta" # url of the cluster that accounts are cloned from
[[test.validator.account]]
address = "GVXRSBjFk6e6J3NbVPXohDJetcTjaeeuykUpbQF8UoMU"
filename = "tests/btc_price_account.json"

[[test.validator.account]]
address = "JBu1AL4obBcCMqKBBxhpWCNUt136ijcuMZLFvTP7iWdB"
filename = "tests/eth_price_account.json"
29 changes: 29 additions & 0 deletions programs/solana_blockchain_index/src/index_service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use anchor_lang::prelude::*;
use pyth_sdk_solana::Price;

pub fn calculate_index_value(prices: Vec<Price>) -> Price {
let expo: i32 = prices.iter().map(|p| p.expo).min().unwrap(); // lowest exponent
msg!("Lowest exponent is {}", expo);

let mut sum_price: i64 = 0;
let mut variance: u64 = 0;

for current_price in prices.iter() {
msg!("Normalising {:?} to expo: {}", current_price, expo);
// NB expo > current_price.expo so this should always be positive
let exp_delta: u32 = (current_price.expo - expo).try_into().unwrap();
let multiplier = 10_u64.pow(exp_delta);
let new_price = current_price.price * multiplier as i64;
msg!("New price is {} x10^{}", new_price, expo);
sum_price += new_price;

let new_conf = current_price.conf * multiplier;
msg!("New conf is {}", new_conf);
variance += new_conf.pow(2);
}

let price = sum_price / prices.len() as i64;
let conf = ((variance as f64).sqrt() / (prices.len() as f64)).round() as u64;
let index_value = Price { price, expo, conf };
index_value
}
31 changes: 20 additions & 11 deletions programs/solana_blockchain_index/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
use anchor_lang::prelude::*;
mod index_service;
mod pyth_service;

declare_id!("4NRxSxxnNA9xKHKrmj9RD16pmGRTDMvp3SkY4uB7ifdM");

const DISCRIMINATOR_LENGTH: usize = 8;
const TIMESTAMP_LENGTH: usize = 8;
const VALUE_LENGTH: usize = 8;
const TIME_LENGTH: usize = 8;
const PRICE_LENGTH: usize = 8;
const EXPO_LENGTH: usize = 4;
const CONF_LENGTH: usize = 8;

#[account]
#[derive(Debug)]
pub struct IndexValue {
pub timestamp: i64,
pub value: i64,
pub price: i64,
pub expo: i32,
pub conf: u64,
pub time: i64,
}

impl IndexValue {
const LEN: usize = DISCRIMINATOR_LENGTH // added by Solana
+ TIMESTAMP_LENGTH // timestamp
+ VALUE_LENGTH // value
+ TIME_LENGTH // timestamp
+ PRICE_LENGTH // price
+ EXPO_LENGTH // exponent
+ CONF_LENGTH; // confidence
}
Expand All @@ -34,11 +35,17 @@ pub mod solana_blockchain_index {
let index_value: &mut Account<IndexValue> = &mut ctx.accounts.index_value;
let clock: Clock = Clock::get().unwrap();

index_value.timestamp = clock.unix_timestamp;
let btc_value = pyth_service::price_of_account(&ctx.accounts.btc_account);
index_value.value = btc_value.price;
index_value.conf = btc_value.conf;
index_value.expo = btc_value.expo;
let price_accounts = vec![&ctx.accounts.btc_account, &ctx.accounts.eth_account];
let prices: Vec<_> = price_accounts
.iter()
.map(|pa| pyth_service::price_of_account(pa))
.collect();
let index_calculation = index_service::calculate_index_value(prices);

index_value.price = index_calculation.price;
index_value.expo = index_calculation.expo;
index_value.conf = index_calculation.conf;
index_value.time = clock.unix_timestamp;

msg!("New index value: {:?}", index_value);
Ok(())
Expand All @@ -54,4 +61,6 @@ pub struct UpdateIndexValue<'info> {
pub system_program: Program<'info, System>,
/// CHECK should be validated at read time
pub btc_account: AccountInfo<'info>,
/// CHECK should be validated at read time
pub eth_account: AccountInfo<'info>,
}
2 changes: 1 addition & 1 deletion programs/solana_blockchain_index/src/pyth_service.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anchor_lang::prelude::*;
use pyth_sdk_solana::{load_price_feed_from_account_info, Price, PriceFeed};

pub fn price_of_account(price_account_info: &AccountInfo) -> Price {
pub fn price_of_account<'a>(price_account_info: &'a AccountInfo) -> Price {
let price_feed: PriceFeed = load_price_feed_from_account_info(price_account_info).unwrap();
let current_price: Price = price_feed.get_current_price().unwrap();

Expand Down
2 changes: 1 addition & 1 deletion tests/btc_price_account.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
"executable": false,
"rentEpoch": 312
}
}
}
13 changes: 13 additions & 0 deletions tests/eth_price_account.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"pubkey": "JBu1AL4obBcCMqKBBxhpWCNUt136ijcuMZLFvTP7iWdB",
"account": {
"lamports": 23942400,
"data": [
"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",
"base64"
],
"owner": "FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH",
"executable": false,
"rentEpoch": 312
}
}
7 changes: 4 additions & 3 deletions tests/solana_blockchain_index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe("SolanaBlockchainIndex", () => {
user: programProvider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
btcAccount: "GVXRSBjFk6e6J3NbVPXohDJetcTjaeeuykUpbQF8UoMU",
ethAccount: "JBu1AL4obBcCMqKBBxhpWCNUt136ijcuMZLFvTP7iWdB",
})
.signers([keypair])
.rpc();
Expand All @@ -31,9 +32,9 @@ describe("SolanaBlockchainIndex", () => {
keypair.publicKey
);
console.log(indexValue);
expect(indexValue.timestamp).is.instanceOf(anchor.BN);
expect(indexValue.value.toNumber()).to.equal(2938900000000);
expect(indexValue.conf.toNumber()).to.equal(1021038607);
expect(indexValue.time).is.instanceOf(anchor.BN);
expect(indexValue.price.toNumber()).to.equal(1567878500000);
expect(indexValue.conf.toNumber()).to.equal(512203289);
expect(indexValue.expo).to.equal(-8);
});
});

0 comments on commit 85da516

Please sign in to comment.