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 13 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 @@ -347,3 +347,4 @@
- dev : clean contracts and compiled files
- fix: add from_address in calldata of l1 message
- test: add starkgate related testcase
- feat: add pragam api to compute fees
110 changes: 110 additions & 0 deletions crates/client/eth-client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub const DEFAULT_CHAIN_ID: u64 = 31337;
/// PRE_PRIVATE=$(jq -r '.private_keys[0]' $BUILD_DIR/anvil.json)
pub const DEFAULT_PRIVATE_KEY: &str = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";

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

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct EthereumClientConfig {
#[serde(default)]
Expand All @@ -37,6 +39,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 Expand Up @@ -70,6 +74,90 @@ pub struct HttpProviderConfig {
pub gas_price_poll_ms: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "oracle_name", content = "config")]
pub enum OracleConfig {
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved
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,
}
}
}

/// Price bounds for the oracle
/// If the price is outside of these bounds, it will not be used
/// The bounds are denominated in the quote currency so in FRI here.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PriceBounds {
pub low: u128,
pub high: u128,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PragmaOracle {
#[serde(default = "default_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 PragmaOracle {
fn get_fetch_url(&self, base: String, quote: String) -> String {
format!(
"{}{}/{}?interval={:?}&aggregation={:?}",
self.api_url, base, quote, self.interval, self.aggregation_method
)
}
}

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

/// 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,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LocalWalletConfig {
#[serde(default = "default_chain_id")]
Expand All @@ -82,6 +170,10 @@ fn default_rpc_endpoint() -> String {
DEFAULT_RPC_ENDPOINT.into()
}

fn default_api_url() -> String {
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved
DEFAULT_API_URL.into()
}

fn default_chain_id() -> u64 {
DEFAULT_CHAIN_ID
}
Expand All @@ -90,6 +182,24 @@ fn default_private_key() -> String {
DEFAULT_PRIVATE_KEY.to_string()
}

impl Default for PragmaOracle {
fn default() -> Self {
Self {
api_url: default_api_url(),
api_key: String::default(),
aggregation_method: AggregationMethod::Median,
interval: Interval::OneMinute,
price_bounds: Default::default(),
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

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

impl Default for HttpProviderConfig {
fn default() -> Self {
Self { rpc_endpoint: default_rpc_endpoint(), tx_poll_interval_ms: None, gas_price_poll_ms: None }
Expand Down
56 changes: 48 additions & 8 deletions crates/client/l1-gas-price/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,31 @@ 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::config::{EthereumClientConfig, 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 +60,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 +97,49 @@ 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 res_json = response.json::<OracleApiResponse>().await;

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

// We query the Oracle API for the ETH/STRK price feed
// If the api response is successful AND the price is within the bounds
// Then we update the strk_l1_gas_price and strk_l1_data_gas_price fields
// Otherwise we log an error and we keep the previous values
match res_json {
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved
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