From 44dc210152dda9c6f64a91573b1f66e9f481bd9e Mon Sep 17 00:00:00 2001 From: Bridgerz Date: Mon, 2 Dec 2024 19:41:19 +0000 Subject: [PATCH] [Bridge] Support other vault metrics (#20431) (#20484) ## Description As in title. ## Test plan How did you test the new or updated feature? --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: - [ ] REST API: ## Description Describe the changes or additions included in this PR. ## Test plan How did you test the new or updated feature? --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: - [ ] REST API: Co-authored-by: William Smith --- crates/sui-bridge-indexer/src/main.rs | 34 +++++++-- .../sui-bridge-watchdog/eth_vault_balance.rs | 69 ++++++++++++----- crates/sui-bridge/src/config.rs | 10 ++- crates/sui-bridge/src/node.rs | 33 +++++++-- .../sui_bridge_watchdog/eth_vault_balance.rs | 74 ++++++++++++++----- .../src/sui_bridge_watchdog/metrics.rs | 7 ++ crates/sui-bridge/src/utils.rs | 16 +++- 7 files changed, 187 insertions(+), 56 deletions(-) diff --git a/crates/sui-bridge-indexer/src/main.rs b/crates/sui-bridge-indexer/src/main.rs index a42fc23b04ab7..8ee9b52b42a30 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 dfc359e0cb393..798be9cde7cc6 100644 --- a/crates/sui-bridge-watchdog/eth_vault_balance.rs +++ b/crates/sui-bridge-watchdog/eth_vault_balance.rs @@ -12,56 +12,89 @@ 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) { - match self + let balance: Result = self .coin_contract .balance_of(self.vault_address) .call() - .await - { + .await; + match balance { 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 normalized_balance: U256 = match self.decimals.checked_sub(8) { + // In this case, there are more decimals than needed, so we need to + // remove trailing decimals. + Some(delta) if delta > 0 => balance + .checked_div(U256::from(10).pow(U256::from(delta))) + .expect("Division by zero should be impossible here"), + // In this case, there are fewer decimals than needed, so we need to + // add zeroes. + None => { + // this should be guaranteed to be positive + let delta = 8 - self.decimals; + balance + .checked_mul(U256::from(10).pow(U256::from(delta))) + .expect("Integer overflow") + } + // in this case, the token contract has the target precision + // so we don't need to do anything. + Some(_) => balance, + }; + self.metric.set(normalized_balance.as_u128() as i64); + + info!("{:?} Vault Balance: {:?}", self.asset, normalized_balance,); } 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 12464b171c621..eca2192943449 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 671f2b358f3c0..c2625ec72047b 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 b43b7538067d4..6ea931bbe4a4f 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,56 +12,90 @@ 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) { - match self + let balance: Result< + U256, + ethers::contract::ContractError>, + > = self .coin_contract .balance_of(self.vault_address) .call() - .await - { + .await; + match balance { 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 normalized_balance: U256 = match self.decimals.checked_sub(8) { + // In this case, there are more decimals than needed, so we need to + // remove trailing decimals. + Some(delta) if delta > 0 => balance + .checked_div(U256::from(10).pow(U256::from(delta))) + .expect("Division by zero should be impossible here"), + // In this case, there are fewer decimals than needed, so we need to + // add zeroes. + None => { + let delta = 8 - self.decimals; + balance + .checked_mul(U256::from(10).pow(U256::from(delta))) + .expect("Integer overflow") + } + // in this case, the token contract has the target precision + // so we don't need to do anything. + Some(_) => balance, + }; + self.metric.set(normalized_balance.as_u128() as i64); + + info!("{:?} Vault Balance: {:?}", self.asset, normalized_balance,); } 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 8fea209d7f43f..58684a75e7017 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 d6f7ca487e191..9daccc67ac6d9 100644 --- a/crates/sui-bridge/src/utils.rs +++ b/crates/sui-bridge/src/utils.rs @@ -107,15 +107,24 @@ 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 committee_address: EthAddress = sui_bridge.committee().call().await?; + let committee = EthBridgeCommittee::new(committee_address, provider.clone()); + let config_address: EthAddress = committee.config().call().await?; + let bridge_config = EthBridgeConfig::new(config_address, provider.clone()); 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 committee = EthBridgeCommittee::new(committee_address, provider.clone()); - let config_address: EthAddress = committee.config().call().await?; + let usdt_address: EthAddress = bridge_config.token_address_of(4).call().await?; Ok(( committee_address, @@ -123,6 +132,7 @@ pub async fn get_eth_contract_addresses