Skip to content

Commit

Permalink
dev : add pragma api to compute fees in STRK (keep-starknet-strange#1633
Browse files Browse the repository at this point in the history
)

Co-authored-by: 0xevolve <[email protected]>
  • Loading branch information
azurwastaken and EvolveArt authored Jun 21, 2024
1 parent f5dce7e commit 7b40592
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,4 @@
- dev : clean contracts and compiled files
- fix: add from_address in calldata of l1 message
- test: add starkgate related testcase
- feat: add pragma api to compute fees
3 changes: 3 additions & 0 deletions crates/client/eth-client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use ethers::types::{Address, H160};
use serde::{Deserialize, Serialize};

use crate::error::Error;
use crate::oracle::OracleConfig;

/// Default Anvil local endpoint
pub const DEFAULT_RPC_ENDPOINT: &str = "http://127.0.0.1:8545";
Expand All @@ -37,6 +38,8 @@ pub struct EthereumClientConfig {
pub wallet: Option<EthereumWalletConfig>,
#[serde(default)]
pub contracts: StarknetContracts,
#[serde(default)]
pub oracle: OracleConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down
1 change: 1 addition & 0 deletions crates/client/eth-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

pub mod config;
pub mod error;
pub mod oracle;

use std::time::Duration;

Expand Down
134 changes: 134 additions & 0 deletions crates/client/eth-client/src/oracle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use std::fmt;

use serde::{Deserialize, Serialize};

pub const DEFAULT_API_URL: &str = "https://api.dev.pragma.build/node/v1/data/";

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "oracle_name", content = "config")]
pub enum OracleConfig {
Pragma(PragmaOracle),
}

impl OracleConfig {
pub fn get_fetch_url(&self, base: String, quote: String) -> String {
match self {
OracleConfig::Pragma(pragma_oracle) => pragma_oracle.get_fetch_url(base, quote),
}
}

pub fn get_api_key(&self) -> &String {
match self {
OracleConfig::Pragma(oracle) => &oracle.api_key,
}
}

pub fn is_in_bounds(&self, price: u128) -> bool {
match self {
OracleConfig::Pragma(oracle) => oracle.price_bounds.low <= price && price <= oracle.price_bounds.high,
}
}
}

impl Default for OracleConfig {
fn default() -> Self {
Self::Pragma(PragmaOracle::default())
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PragmaOracle {
#[serde(default = "default_oracle_api_url")]
pub api_url: String,
#[serde(default)]
pub api_key: String,
#[serde(default)]
pub aggregation_method: AggregationMethod,
#[serde(default)]
pub interval: Interval,
#[serde(default)]
pub price_bounds: PriceBounds,
}

impl Default for PragmaOracle {
fn default() -> Self {
Self {
api_url: default_oracle_api_url(),
api_key: String::default(),
aggregation_method: AggregationMethod::Median,
interval: Interval::OneMinute,
price_bounds: Default::default(),
}
}
}

impl PragmaOracle {
fn get_fetch_url(&self, base: String, quote: String) -> String {
format!("{}{}/{}?interval={}&aggregation={}", self.api_url, base, quote, self.interval, self.aggregation_method)
}
}

#[derive(Default, Debug, Serialize, Deserialize, Clone)]
/// Supported Aggregation Methods
pub enum AggregationMethod {
#[serde(rename = "median")]
Median,
#[serde(rename = "mean")]
Mean,
#[serde(rename = "twap")]
#[default]
Twap,
}

impl fmt::Display for AggregationMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
AggregationMethod::Median => "median",
AggregationMethod::Mean => "mean",
AggregationMethod::Twap => "twap",
};
write!(f, "{}", name)
}
}

/// Supported Aggregation Intervals
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub enum Interval {
#[serde(rename = "1min")]
OneMinute,
#[serde(rename = "15min")]
FifteenMinutes,
#[serde(rename = "1h")]
OneHour,
#[serde(rename = "2h")]
#[default]
TwoHours,
}

impl fmt::Display for Interval {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Interval::OneMinute => "1min",
Interval::FifteenMinutes => "15min",
Interval::OneHour => "1h",
Interval::TwoHours => "2h",
};
write!(f, "{}", name)
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceBounds {
pub low: u128,
pub high: u128,
}

impl Default for PriceBounds {
fn default() -> Self {
Self { low: 0, high: u128::MAX }
}
}

fn default_oracle_api_url() -> String {
DEFAULT_API_URL.into()
}
51 changes: 44 additions & 7 deletions crates/client/l1-gas-price/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,32 @@ use std::sync::Arc;
use std::time::Duration;

use anyhow::{format_err, Result};
use ethers::types::U256;
use ethers::utils::__serde_json::json;
use futures::lock::Mutex;
use mc_eth_client::config::EthereumClientConfig;
use mc_eth_client::oracle::OracleConfig;
use mp_starknet_inherent::L1GasPrices;
use serde::Deserialize;
use tokio::time::sleep;

use crate::types::{EthRpcResponse, FeeHistory};

const DEFAULT_GAS_PRICE_POLL_MS: u64 = 10_000;

#[derive(Deserialize, Debug)]
struct OracleApiResponse {
price: String,
decimals: u32,
}

pub async fn run_worker(config: Arc<EthereumClientConfig>, gas_price: Arc<Mutex<L1GasPrices>>, infinite_loop: bool) {
let rpc_endpoint = config.provider.rpc_endpoint().clone();
let client = reqwest::Client::new();
let poll_time = config.provider.gas_price_poll_ms().unwrap_or(DEFAULT_GAS_PRICE_POLL_MS);

loop {
match update_gas_price(rpc_endpoint.clone(), &client, gas_price.clone()).await {
match update_gas_price(rpc_endpoint.clone(), &client, gas_price.clone(), config.oracle.clone()).await {
Ok(_) => log::trace!("Updated gas prices"),
Err(e) => log::error!("Failed to update gas prices: {:?}", e),
}
Expand Down Expand Up @@ -52,6 +61,7 @@ async fn update_gas_price(
rpc_endpoint: String,
client: &reqwest::Client,
gas_price: Arc<Mutex<L1GasPrices>>,
oracle: OracleConfig,
) -> Result<()> {
let fee_history: EthRpcResponse<FeeHistory> = client
.post(rpc_endpoint.clone())
Expand Down Expand Up @@ -88,18 +98,45 @@ async fn update_gas_price(
16,
)?;

// TODO: fetch this from the oracle
let eth_strk_price = 2425;
let response = reqwest::Client::new()
.get(oracle.get_fetch_url(String::from("eth"), String::from("strk")))
.header("x-api-key", oracle.get_api_key())
.send()
.await?;

let oracle_api_response = response.json::<OracleApiResponse>().await;

let mut gas_price = gas_price.lock().await;

match oracle_api_response {
Ok(api_response) => {
log::trace!("Retrieved ETH/STRK price from Oracle");
let eth_strk_price = u128::from_str_radix(api_response.price.trim_start_matches("0x"), 16)?;
if oracle.is_in_bounds(eth_strk_price) {
let stark_gas = ((U256::from(eth_gas_price) * U256::from(eth_strk_price))
/ 10u64.pow(api_response.decimals))
.as_u128();
let stark_data_gas = ((U256::from(avg_blob_base_fee) * U256::from(eth_strk_price))
/ 10u64.pow(api_response.decimals))
.as_u128();
gas_price.strk_l1_gas_price = NonZeroU128::new(stark_gas)
.ok_or(format_err!("Failed to convert `strk_l1_gas_price` to NonZeroU128"))?;
gas_price.strk_l1_data_gas_price = NonZeroU128::new(stark_data_gas)
.ok_or(format_err!("Failed to convert `strk_l1_data_gas_price` to NonZeroU128"))?;
} else {
log::error!("⚠️ Retrieved price is outside of bounds");
}
}
Err(e) => {
log::error!("Failed to retrieve ETH/STRK price: {:?}", e);
}
};

gas_price.eth_l1_gas_price =
NonZeroU128::new(eth_gas_price).ok_or(format_err!("Failed to convert `eth_gas_price` to NonZeroU128"))?;
gas_price.eth_l1_data_gas_price = NonZeroU128::new(avg_blob_base_fee)
.ok_or(format_err!("Failed to convert `eth_l1_data_gas_price` to NonZeroU128"))?;
gas_price.strk_l1_gas_price = NonZeroU128::new(eth_gas_price.saturating_mul(eth_strk_price))
.ok_or(format_err!("Failed to convert `strk_l1_gas_price` to NonZeroU128"))?;
gas_price.strk_l1_data_gas_price = NonZeroU128::new(avg_blob_base_fee.saturating_mul(eth_strk_price))
.ok_or(format_err!("Failed to convert `strk_l1_data_gas_price` to NonZeroU128"))?;

gas_price.last_update_timestamp = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_millis();
// explicitly dropping gas price here to avoid long waits when fetching the value
// on the inherent side which would increase block time
Expand Down
15 changes: 14 additions & 1 deletion examples/messaging/eth-config.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
{
"provider": {
"rpc_endpoint": "http://127.0.0.1:8545",
"rpc_endpoint": "https://ethereum-rpc.publicnode.com",
"gas_price_poll_ms": 10000
},
"contracts": {
"core_contract": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512"
},
"oracle": {
"oracle_name": "Pragma",
"config": {
"api_url": "https://api.dev.pragma.build/node/v1/data/",
"api_key": "",
"aggregation_method": "twap",
"interval": "2h",
"price_bounds": {
"low": 3000000000000000000000,
"high": 6000000000000000000000
}
}
}
}

0 comments on commit 7b40592

Please sign in to comment.