diff --git a/crates/sui-bridge-indexer/src/main.rs b/crates/sui-bridge-indexer/src/main.rs index a42fc23b04ab75..8ee9b52b42a30a 100644 --- a/crates/sui-bridge-indexer/src/main.rs +++ b/crates/sui-bridge-indexer/src/main.rs @@ -27,8 +27,11 @@ use mysten_metrics::start_prometheus_server; use sui_bridge::metrics::BridgeMetrics; use sui_bridge::sui_bridge_watchdog::{ - eth_bridge_status::EthBridgeStatus, eth_vault_balance::EthVaultBalance, - metrics::WatchdogMetrics, sui_bridge_status::SuiBridgeStatus, BridgeWatchDog, + eth_bridge_status::EthBridgeStatus, + eth_vault_balance::{EthereumVaultBalance, VaultAsset}, + metrics::WatchdogMetrics, + sui_bridge_status::SuiBridgeStatus, + BridgeWatchDog, }; use sui_bridge_indexer::config::IndexerConfig; use sui_bridge_indexer::metrics::BridgeIndexerMetrics; @@ -143,15 +146,33 @@ async fn start_watchdog( let watchdog_metrics = WatchdogMetrics::new(registry); let eth_provider = Arc::new(new_metered_eth_provider(&config.eth_rpc_url, bridge_metrics.clone()).unwrap()); - let (_committee_address, _limiter_address, vault_address, _config_address, weth_address) = - get_eth_contract_addresses(eth_bridge_proxy_address, ð_provider).await?; + let ( + _committee_address, + _limiter_address, + vault_address, + _config_address, + weth_address, + usdt_address, + ) = get_eth_contract_addresses(eth_bridge_proxy_address, ð_provider).await?; - let eth_vault_balance = EthVaultBalance::new( + let eth_vault_balance = EthereumVaultBalance::new( eth_provider.clone(), vault_address, weth_address, + VaultAsset::WETH, watchdog_metrics.eth_vault_balance.clone(), - ); + ) + .await + .unwrap_or_else(|e| panic!("Failed to create eth vault balance: {}", e)); + let usdt_vault_balance = EthereumVaultBalance::new( + eth_provider.clone(), + vault_address, + usdt_address, + VaultAsset::USDT, + watchdog_metrics.usdt_vault_balance.clone(), + ) + .await + .unwrap_or_else(|e| panic!("Failed to create usdt vault balance: {}", e)); let eth_bridge_status = EthBridgeStatus::new( eth_provider, @@ -163,6 +184,7 @@ async fn start_watchdog( SuiBridgeStatus::new(sui_client, watchdog_metrics.sui_bridge_paused.clone()); let observables: Vec> = vec![ Box::new(eth_vault_balance), + Box::new(usdt_vault_balance), Box::new(eth_bridge_status), Box::new(sui_bridge_status), ]; diff --git a/crates/sui-bridge-watchdog/eth_vault_balance.rs b/crates/sui-bridge-watchdog/eth_vault_balance.rs index dfc359e0cb393c..b3ca56c344188e 100644 --- a/crates/sui-bridge-watchdog/eth_vault_balance.rs +++ b/crates/sui-bridge-watchdog/eth_vault_balance.rs @@ -12,37 +12,48 @@ use sui_bridge::metered_eth_provider::MeteredEthHttpProvier; use tokio::time::Duration; use tracing::{error, info}; -const TEN_ZEROS: u64 = 10_u64.pow(10); +#[derive(Debug)] +pub enum VaultAsset { + WETH, + USDT, +} -pub struct EthVaultBalance { +pub struct EthereumVaultBalance { coin_contract: EthERC20>, + asset: VaultAsset, + decimals: u8, vault_address: EthAddress, - ten_zeros: U256, metric: IntGauge, } -impl EthVaultBalance { +impl EthereumVaultBalance { pub fn new( provider: Arc>, vault_address: EthAddress, coin_address: EthAddress, // for now this only support one coin which is WETH + asset: VaultAsset, metric: IntGauge, - ) -> Self { - let ten_zeros = U256::from(TEN_ZEROS); + ) -> anyhow::Result { let coin_contract = EthERC20::new(coin_address, provider); - Self { + let decimals = coin_contract + .decimals() + .call() + .await + .map_err(|e| anyhow::anyhow!("Failed to get decimals from token contract: {e}"))?; + Ok(Self { coin_contract, vault_address, - ten_zeros, + decimals, + asset, metric, - } + }) } } #[async_trait] -impl Observable for EthVaultBalance { +impl Observable for EthereumVaultBalance { fn name(&self) -> &str { - "EthVaultBalance" + "EthereumVaultBalance" } async fn observe_and_report(&self) { @@ -55,13 +66,20 @@ impl Observable for EthVaultBalance { Ok(balance) => { // Why downcasting is safe: // 1. On Ethereum we only take the first 8 decimals into account, - // meaning the trailing 10 digits can be ignored + // meaning the trailing 10 digits can be ignored. For other assets, + // we will also assume this max level of precision for metrics purposes. // 2. i64::MAX is 9_223_372_036_854_775_807, with 8 decimal places is // 92_233_720_368. We likely won't see any balance higher than this // in the next 12 months. - let balance = (balance / self.ten_zeros).as_u64() as i64; - self.metric.set(balance); - info!("Eth Vault Balance: {:?}", balance); + // For USDT, for example, this will be 10^6 - 8 = 10^(-2) = 0.01, + // therefore we will add 2 zeroes of precision. + let denom = U256::from(10).pow(self.decimals - 8); + let normalized_balance = (balance / denom).as_u64() as i64; + self.metric.set(normalized_balance); + info!( + "{:?} Vault Balance: {:?} ({:?} {:?})", + self.asset, balance, normalized_balance, self.asset, + ); } Err(e) => { error!("Error getting balance from vault: {:?}", e); diff --git a/crates/sui-bridge/src/config.rs b/crates/sui-bridge/src/config.rs index 12464b171c6217..eca21929434496 100644 --- a/crates/sui-bridge/src/config.rs +++ b/crates/sui-bridge/src/config.rs @@ -261,8 +261,14 @@ impl BridgeNodeConfig { .interval(std::time::Duration::from_millis(2000)), ); let chain_id = provider.get_chainid().await?; - let (committee_address, limiter_address, vault_address, config_address, _weth_address) = - get_eth_contract_addresses(bridge_proxy_address, &provider).await?; + let ( + committee_address, + limiter_address, + vault_address, + config_address, + _weth_address, + _usdt_address, + ) = get_eth_contract_addresses(bridge_proxy_address, &provider).await?; let config = EthBridgeConfig::new(config_address, provider.clone()); if self.run_client && self.eth.eth_contracts_start_block_fallback.is_none() { diff --git a/crates/sui-bridge/src/node.rs b/crates/sui-bridge/src/node.rs index 671f2b358f3c03..c2625ec72047b4 100644 --- a/crates/sui-bridge/src/node.rs +++ b/crates/sui-bridge/src/node.rs @@ -5,7 +5,7 @@ use crate::config::WatchdogConfig; use crate::crypto::BridgeAuthorityPublicKeyBytes; use crate::metered_eth_provider::MeteredEthHttpProvier; use crate::sui_bridge_watchdog::eth_bridge_status::EthBridgeStatus; -use crate::sui_bridge_watchdog::eth_vault_balance::EthVaultBalance; +use crate::sui_bridge_watchdog::eth_vault_balance::{EthereumVaultBalance, VaultAsset}; use crate::sui_bridge_watchdog::metrics::WatchdogMetrics; use crate::sui_bridge_watchdog::sui_bridge_status::SuiBridgeStatus; use crate::sui_bridge_watchdog::total_supplies::TotalSupplies; @@ -158,17 +158,35 @@ async fn start_watchdog( sui_client: Arc, ) { let watchdog_metrics = WatchdogMetrics::new(registry); - let (_committee_address, _limiter_address, vault_address, _config_address, weth_address) = - get_eth_contract_addresses(eth_bridge_proxy_address, ð_provider) - .await - .unwrap_or_else(|e| panic!("get_eth_contract_addresses should not fail: {}", e)); + let ( + _committee_address, + _limiter_address, + vault_address, + _config_address, + weth_address, + usdt_address, + ) = get_eth_contract_addresses(eth_bridge_proxy_address, ð_provider) + .await + .unwrap_or_else(|e| panic!("get_eth_contract_addresses should not fail: {}", e)); - let eth_vault_balance = EthVaultBalance::new( + let eth_vault_balance = EthereumVaultBalance::new( eth_provider.clone(), vault_address, weth_address, + VaultAsset::WETH, watchdog_metrics.eth_vault_balance.clone(), - ); + ) + .await + .unwrap_or_else(|e| panic!("Failed to create eth vault balance: {}", e)); + let usdt_vault_balance = EthereumVaultBalance::new( + eth_provider.clone(), + vault_address, + usdt_address, + VaultAsset::USDT, + watchdog_metrics.usdt_vault_balance.clone(), + ) + .await + .unwrap_or_else(|e| panic!("Failed to create usdt vault balance: {}", e)); let eth_bridge_status = EthBridgeStatus::new( eth_provider, @@ -183,6 +201,7 @@ async fn start_watchdog( let mut observables: Vec> = vec![ Box::new(eth_vault_balance), + Box::new(usdt_vault_balance), Box::new(eth_bridge_status), Box::new(sui_bridge_status), ]; diff --git a/crates/sui-bridge/src/sui_bridge_watchdog/eth_vault_balance.rs b/crates/sui-bridge/src/sui_bridge_watchdog/eth_vault_balance.rs index b43b7538067d40..ceec1efc81b3f6 100644 --- a/crates/sui-bridge/src/sui_bridge_watchdog/eth_vault_balance.rs +++ b/crates/sui-bridge/src/sui_bridge_watchdog/eth_vault_balance.rs @@ -12,37 +12,48 @@ use std::sync::Arc; use tokio::time::Duration; use tracing::{error, info}; -const TEN_ZEROS: u64 = 10_u64.pow(10); +#[derive(Debug)] +pub enum VaultAsset { + WETH, + USDT, +} -pub struct EthVaultBalance { +pub struct EthereumVaultBalance { coin_contract: EthERC20>, + asset: VaultAsset, + decimals: u8, vault_address: EthAddress, - ten_zeros: U256, metric: IntGauge, } -impl EthVaultBalance { - pub fn new( +impl EthereumVaultBalance { + pub async fn new( provider: Arc>, vault_address: EthAddress, coin_address: EthAddress, // for now this only support one coin which is WETH + asset: VaultAsset, metric: IntGauge, - ) -> Self { - let ten_zeros = U256::from(TEN_ZEROS); + ) -> anyhow::Result { let coin_contract = EthERC20::new(coin_address, provider); - Self { + let decimals = coin_contract + .decimals() + .call() + .await + .map_err(|e| anyhow::anyhow!("Failed to get decimals from token contract: {e}"))?; + Ok(Self { coin_contract, vault_address, - ten_zeros, + decimals, + asset, metric, - } + }) } } #[async_trait] -impl Observable for EthVaultBalance { +impl Observable for EthereumVaultBalance { fn name(&self) -> &str { - "EthVaultBalance" + "EthereumVaultBalance" } async fn observe_and_report(&self) { @@ -55,13 +66,20 @@ impl Observable for EthVaultBalance { Ok(balance) => { // Why downcasting is safe: // 1. On Ethereum we only take the first 8 decimals into account, - // meaning the trailing 10 digits can be ignored + // meaning the trailing 10 digits can be ignored. For other assets, + // we will also assume this max level of precision for metrics purposes. // 2. i64::MAX is 9_223_372_036_854_775_807, with 8 decimal places is // 92_233_720_368. We likely won't see any balance higher than this // in the next 12 months. - let balance = (balance / self.ten_zeros).as_u64() as i64; - self.metric.set(balance); - info!("Eth Vault Balance: {:?}", balance); + // For USDT, for example, this will be 10^6 - 8 = 10^(-2) = 0.01, + // therefore we will add 2 zeroes of precision. + let denom = U256::from(10).pow((self.decimals - 8).into()); + let normalized_balance = (balance / denom).as_u64() as i64; + self.metric.set(normalized_balance); + info!( + "{:?} Vault Balance: {:?} ({:?} {:?})", + self.asset, balance, normalized_balance, self.asset, + ); } Err(e) => { error!("Error getting balance from vault: {:?}", e); diff --git a/crates/sui-bridge/src/sui_bridge_watchdog/metrics.rs b/crates/sui-bridge/src/sui_bridge_watchdog/metrics.rs index 8fea209d7f43f2..58684a75e70175 100644 --- a/crates/sui-bridge/src/sui_bridge_watchdog/metrics.rs +++ b/crates/sui-bridge/src/sui_bridge_watchdog/metrics.rs @@ -9,6 +9,7 @@ use prometheus::{ #[derive(Clone, Debug)] pub struct WatchdogMetrics { pub eth_vault_balance: IntGauge, + pub usdt_vault_balance: IntGauge, pub total_supplies: IntGaugeVec, pub eth_bridge_paused: IntGauge, pub sui_bridge_paused: IntGauge, @@ -23,6 +24,12 @@ impl WatchdogMetrics { registry, ) .unwrap(), + usdt_vault_balance: register_int_gauge_with_registry!( + "bridge_usdt_vault_balance", + "Current balance of usdt eth vault", + registry, + ) + .unwrap(), total_supplies: register_int_gauge_vec_with_registry!( "bridge_total_supplies", "Current total supplies of coins on Sui based on Treasury Cap", diff --git a/crates/sui-bridge/src/utils.rs b/crates/sui-bridge/src/utils.rs index d6f7ca487e191a..e937705cb40367 100644 --- a/crates/sui-bridge/src/utils.rs +++ b/crates/sui-bridge/src/utils.rs @@ -107,13 +107,22 @@ pub fn generate_bridge_client_key_and_write_to_file( pub async fn get_eth_contract_addresses( bridge_proxy_address: EthAddress, provider: &Arc>, -) -> anyhow::Result<(EthAddress, EthAddress, EthAddress, EthAddress, EthAddress)> { +) -> anyhow::Result<( + EthAddress, + EthAddress, + EthAddress, + EthAddress, + EthAddress, + EthAddress, +)> { let sui_bridge = EthSuiBridge::new(bridge_proxy_address, provider.clone()); + let bridge_config = EthBridgeConfig::new(bridge_proxy_address, provider.clone()); let committee_address: EthAddress = sui_bridge.committee().call().await?; let limiter_address: EthAddress = sui_bridge.limiter().call().await?; let vault_address: EthAddress = sui_bridge.vault().call().await?; let vault = EthBridgeVault::new(vault_address, provider.clone()); let weth_address: EthAddress = vault.w_eth().call().await?; + let usdt_address: EthAddress = bridge_config.token_address_of(4).call().await?; let committee = EthBridgeCommittee::new(committee_address, provider.clone()); let config_address: EthAddress = committee.config().call().await?; @@ -123,6 +132,7 @@ pub async fn get_eth_contract_addresses