Skip to content
This repository has been archived by the owner on Aug 2, 2024. It is now read-only.

dev : add pragma api to compute fees in STRK #1633

Merged
merged 19 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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?;
tdelabro marked this conversation as resolved.
Show resolved Hide resolved

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
}
}
}
}
Loading