diff --git a/Cargo.lock b/Cargo.lock index c87269ce2d6f..18043fa25043 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8046,12 +8046,16 @@ dependencies = [ "anyhow", "async-trait", "chrono", + "hex", "rand 0.8.5", "tokio", "tracing", "zksync_config", + "zksync_contracts", "zksync_dal", + "zksync_eth_client", "zksync_external_price_api", + "zksync_node_fee_model", "zksync_types", ] @@ -9077,7 +9081,6 @@ dependencies = [ "tokio", "tracing", "vise", - "zksync_base_token_adjuster", "zksync_config", "zksync_dal", "zksync_eth_client", diff --git a/contracts b/contracts index 8670004d6daa..7ca5517510f2 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 8670004d6daa7e8c299087d62f1451a3dec4f899 +Subproject commit 7ca5517510f2534a2fc25b16c429fdd4a439b89d diff --git a/core/bin/zksync_server/src/node_builder.rs b/core/bin/zksync_server/src/node_builder.rs index 7c4503876e9d..add114c170a4 100644 --- a/core/bin/zksync_server/src/node_builder.rs +++ b/core/bin/zksync_server/src/node_builder.rs @@ -594,8 +594,14 @@ impl MainNodeBuilder { fn add_base_token_ratio_persister_layer(mut self) -> anyhow::Result { let config = try_load_config!(self.configs.base_token_adjuster); let contracts_config = self.contracts_config.clone(); - self.node - .add_layer(BaseTokenRatioPersisterLayer::new(config, contracts_config)); + let wallets = self.wallets.clone(); + let l1_chain_id = self.genesis_config.l1_chain_id; + self.node.add_layer(BaseTokenRatioPersisterLayer::new( + config, + contracts_config, + wallets, + l1_chain_id, + )); Ok(self) } diff --git a/core/lib/config/src/configs/base_token_adjuster.rs b/core/lib/config/src/configs/base_token_adjuster.rs index 4ef253989cd2..0ae451a62d9c 100644 --- a/core/lib/config/src/configs/base_token_adjuster.rs +++ b/core/lib/config/src/configs/base_token_adjuster.rs @@ -1,47 +1,169 @@ use std::time::Duration; +use anyhow::Context; use serde::Deserialize; +use zksync_basic_types::H256; +use zksync_crypto_primitives::K256PrivateKey; /// By default, the ratio persister will run every 30 seconds. -pub const DEFAULT_INTERVAL_MS: u64 = 30_000; +const DEFAULT_PRICE_POLLING_INTERVAL_MS: u64 = 30_000; /// By default, refetch ratio from db every 0.5 second -pub const DEFAULT_CACHE_UPDATE_INTERVAL: u64 = 500; +const DEFAULT_PRICE_CACHE_UPDATE_INTERVAL_MS: u64 = 500; + +/// Default max amount of gas that a L1 base token update can consume per transaction +const DEFAULT_MAX_TX_GAS: u64 = 80_000; + +/// Default priority fee per gas used to instantiate the signing client +const DEFAULT_PRIORITY_FEE_PER_GAS: u64 = 1_000_000_000; + +/// Default maximum number of attempts to get L1 transaction receipt +const DEFAULT_L1_RECEIPT_CHECKING_MAX_ATTEMPTS: u32 = 3; + +/// Default maximum number of attempts to submit L1 transaction +const DEFAULT_L1_TX_SENDING_MAX_ATTEMPTS: u32 = 3; + +/// Default number of milliseconds to sleep between receipt checking attempts +const DEFAULT_L1_RECEIPT_CHECKING_SLEEP_MS: u64 = 30_000; + +/// Default number of milliseconds to sleep between transaction sending attempts +const DEFAULT_L1_TX_SENDING_SLEEP_MS: u64 = 30_000; + +/// Default maximum acceptable priority fee in gwei to prevent sending transaction with extremely high priority fee. +const DEFAULT_MAX_ACCEPTABLE_PRIORITY_FEE_IN_GWEI: u64 = 100_000_000_000; + +/// Default value for halting on error +const DEFAULT_HALT_ON_ERROR: bool = false; #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct BaseTokenAdjusterConfig { /// How often to spark a new cycle of the ratio persister to fetch external prices and persis ratios. - #[serde(default = "BaseTokenAdjusterConfig::default_polling_interval")] + #[serde(default = "BaseTokenAdjusterConfig::default_price_polling_interval_ms")] pub price_polling_interval_ms: u64, /// We (in memory) cache the ratio fetched from db. This interval defines frequency of refetch from db. - #[serde(default = "BaseTokenAdjusterConfig::default_cache_update_interval")] + #[serde(default = "BaseTokenAdjusterConfig::default_price_cache_update_interval_ms")] pub price_cache_update_interval_ms: u64, + + /// Max amount of gas that L1 base token update can consume per transaction + #[serde(default = "BaseTokenAdjusterConfig::default_max_tx_gas")] + pub max_tx_gas: u64, + + /// Default priority fee per gas used to instantiate the signing client + #[serde(default = "BaseTokenAdjusterConfig::default_priority_fee_per_gas")] + pub default_priority_fee_per_gas: u64, + + /// Maximum acceptable priority fee in gwei to prevent sending transaction with extremely high priority fee. + #[serde(default = "BaseTokenAdjusterConfig::default_max_acceptable_priority_fee_in_gwei")] + pub max_acceptable_priority_fee_in_gwei: u64, + + /// Maximum number of attempts to get L1 transaction receipt before failing over + #[serde(default = "BaseTokenAdjusterConfig::default_l1_receipt_checking_max_attempts")] + pub l1_receipt_checking_max_attempts: u32, + + /// Number of seconds to sleep between the receipt checking attempts + #[serde(default = "BaseTokenAdjusterConfig::default_l1_receipt_checking_sleep_ms")] + pub l1_receipt_checking_sleep_ms: u64, + + /// Maximum number of attempts to submit L1 transaction before failing over + #[serde(default = "BaseTokenAdjusterConfig::default_l1_tx_sending_max_attempts")] + pub l1_tx_sending_max_attempts: u32, + + /// Number of seconds to sleep between the transaction sending attempts + #[serde(default = "BaseTokenAdjusterConfig::default_l1_tx_sending_sleep_ms")] + pub l1_tx_sending_sleep_ms: u64, + + /// Defines whether base_token_adjuster should halt the process if there was an error while + /// fetching or persisting the quote. Generally that should be set to false to not to halt + /// the server process if an external api is not available or if L1 is congested. + #[serde(default = "BaseTokenAdjusterConfig::default_halt_on_error")] + pub halt_on_error: bool, } impl Default for BaseTokenAdjusterConfig { fn default() -> Self { Self { - price_polling_interval_ms: Self::default_polling_interval(), - price_cache_update_interval_ms: Self::default_cache_update_interval(), + price_polling_interval_ms: Self::default_price_polling_interval_ms(), + price_cache_update_interval_ms: Self::default_price_cache_update_interval_ms(), + max_tx_gas: Self::default_max_tx_gas(), + default_priority_fee_per_gas: Self::default_priority_fee_per_gas(), + max_acceptable_priority_fee_in_gwei: Self::default_max_acceptable_priority_fee_in_gwei( + ), + l1_receipt_checking_max_attempts: Self::default_l1_receipt_checking_max_attempts(), + l1_receipt_checking_sleep_ms: Self::default_l1_receipt_checking_sleep_ms(), + l1_tx_sending_max_attempts: Self::default_l1_tx_sending_max_attempts(), + l1_tx_sending_sleep_ms: Self::default_l1_tx_sending_sleep_ms(), + halt_on_error: Self::default_halt_on_error(), } } } impl BaseTokenAdjusterConfig { - fn default_polling_interval() -> u64 { - DEFAULT_INTERVAL_MS + pub fn default_price_polling_interval_ms() -> u64 { + DEFAULT_PRICE_POLLING_INTERVAL_MS } pub fn price_polling_interval(&self) -> Duration { Duration::from_millis(self.price_polling_interval_ms) } - fn default_cache_update_interval() -> u64 { - DEFAULT_CACHE_UPDATE_INTERVAL + pub fn default_price_cache_update_interval_ms() -> u64 { + DEFAULT_PRICE_CACHE_UPDATE_INTERVAL_MS + } + + pub fn default_priority_fee_per_gas() -> u64 { + DEFAULT_PRIORITY_FEE_PER_GAS + } + + pub fn default_max_acceptable_priority_fee_in_gwei() -> u64 { + DEFAULT_MAX_ACCEPTABLE_PRIORITY_FEE_IN_GWEI + } + + pub fn default_halt_on_error() -> bool { + DEFAULT_HALT_ON_ERROR } pub fn price_cache_update_interval(&self) -> Duration { Duration::from_millis(self.price_cache_update_interval_ms) } + + pub fn l1_receipt_checking_sleep_duration(&self) -> Duration { + Duration::from_millis(self.l1_receipt_checking_sleep_ms) + } + + pub fn l1_tx_sending_sleep_duration(&self) -> Duration { + Duration::from_millis(self.l1_tx_sending_sleep_ms) + } + + pub fn default_l1_receipt_checking_max_attempts() -> u32 { + DEFAULT_L1_RECEIPT_CHECKING_MAX_ATTEMPTS + } + + pub fn default_l1_receipt_checking_sleep_ms() -> u64 { + DEFAULT_L1_RECEIPT_CHECKING_SLEEP_MS + } + + pub fn default_l1_tx_sending_max_attempts() -> u32 { + DEFAULT_L1_TX_SENDING_MAX_ATTEMPTS + } + + pub fn default_l1_tx_sending_sleep_ms() -> u64 { + DEFAULT_L1_TX_SENDING_SLEEP_MS + } + + pub fn default_max_tx_gas() -> u64 { + DEFAULT_MAX_TX_GAS + } + + pub fn private_key(&self) -> anyhow::Result> { + std::env::var("TOKEN_MULTIPLIER_SETTER_PRIVATE_KEY") + .ok() + .map(|pk| { + let private_key_bytes: H256 = + pk.parse().context("failed parsing private key bytes")?; + K256PrivateKey::from_bytes(private_key_bytes) + .context("private key bytes are invalid") + }) + .transpose() + } } diff --git a/core/lib/config/src/configs/external_price_api_client.rs b/core/lib/config/src/configs/external_price_api_client.rs index 06282eb8bebd..15cc7d29d848 100644 --- a/core/lib/config/src/configs/external_price_api_client.rs +++ b/core/lib/config/src/configs/external_price_api_client.rs @@ -4,6 +4,18 @@ use serde::Deserialize; pub const DEFAULT_TIMEOUT_MS: u64 = 10_000; +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct ForcedPriceClientConfig { + /// Forced conversion ratio + pub numerator: Option, + pub denominator: Option, + /// Forced fluctuation. It defines how much percent numerator / + /// denominator should fluctuate from their forced values. If it's None or 0, then ForcedPriceClient + /// will return the same quote every time it's called. Otherwise, ForcedPriceClient will return + /// forced_quote +/- forced_fluctuation % from its values. + pub fluctuation: Option, +} + #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct ExternalPriceApiClientConfig { pub source: String, @@ -11,9 +23,7 @@ pub struct ExternalPriceApiClientConfig { pub api_key: Option, #[serde(default = "ExternalPriceApiClientConfig::default_timeout")] pub client_timeout_ms: u64, - /// Forced conversion ratio. Only used with the ForcedPriceClient. - pub forced_numerator: Option, - pub forced_denominator: Option, + pub forced: Option, } impl ExternalPriceApiClientConfig { diff --git a/core/lib/config/src/configs/wallets.rs b/core/lib/config/src/configs/wallets.rs index 7b74cd441166..4cb5358c8f30 100644 --- a/core/lib/config/src/configs/wallets.rs +++ b/core/lib/config/src/configs/wallets.rs @@ -69,10 +69,16 @@ pub struct StateKeeper { pub fee_account: AddressWallet, } +#[derive(Debug, Clone, PartialEq)] +pub struct TokenMultiplierSetter { + pub wallet: Wallet, +} + #[derive(Debug, Clone, PartialEq)] pub struct Wallets { pub eth_sender: Option, pub state_keeper: Option, + pub token_multiplier_setter: Option, } impl Wallets { @@ -87,6 +93,9 @@ impl Wallets { state_keeper: Some(StateKeeper { fee_account: AddressWallet::from_address(H160::repeat_byte(0x3)), }), + token_multiplier_setter: Some(TokenMultiplierSetter { + wallet: Wallet::from_private_key_bytes(H256::repeat_byte(0x4), None).unwrap(), + }), } } } diff --git a/core/lib/config/src/testonly.rs b/core/lib/config/src/testonly.rs index 632030e8f1da..36ed650bdef0 100644 --- a/core/lib/config/src/testonly.rs +++ b/core/lib/config/src/testonly.rs @@ -12,7 +12,9 @@ use zksync_basic_types::{ use zksync_consensus_utils::EncodeDist; use zksync_crypto_primitives::K256PrivateKey; -use crate::configs::{self, eth_sender::PubdataSendingMode}; +use crate::configs::{ + self, eth_sender::PubdataSendingMode, external_price_api_client::ForcedPriceClientConfig, +}; trait Sample { fn sample(rng: &mut (impl Rng + ?Sized)) -> Self; @@ -895,11 +897,20 @@ impl Distribution for EncodeDist { } } +impl Distribution for EncodeDist { + fn sample(&self, rng: &mut R) -> configs::wallets::TokenMultiplierSetter { + configs::wallets::TokenMultiplierSetter { + wallet: self.sample(rng), + } + } +} + impl Distribution for EncodeDist { fn sample(&self, rng: &mut R) -> configs::wallets::Wallets { configs::wallets::Wallets { state_keeper: self.sample_opt(|| self.sample(rng)), eth_sender: self.sample_opt(|| self.sample(rng)), + token_multiplier_setter: self.sample_opt(|| self.sample(rng)), } } } @@ -1024,6 +1035,14 @@ impl Distribution for Enc configs::base_token_adjuster::BaseTokenAdjusterConfig { price_polling_interval_ms: self.sample(rng), price_cache_update_interval_ms: self.sample(rng), + max_tx_gas: self.sample(rng), + default_priority_fee_per_gas: self.sample(rng), + max_acceptable_priority_fee_in_gwei: self.sample(rng), + l1_receipt_checking_max_attempts: self.sample(rng), + l1_receipt_checking_sleep_ms: self.sample(rng), + l1_tx_sending_max_attempts: self.sample(rng), + l1_tx_sending_sleep_ms: self.sample(rng), + halt_on_error: self.sample(rng), } } } @@ -1051,8 +1070,11 @@ impl Distribution BaseTokenAdjusterConfig { + BaseTokenAdjusterConfig { + price_polling_interval_ms: 10_000, + price_cache_update_interval_ms: 11_000, + max_tx_gas: 1_000_000, + default_priority_fee_per_gas: 50_000, + max_acceptable_priority_fee_in_gwei: 10_000_000_000, + l1_receipt_checking_max_attempts: 5, + l1_receipt_checking_sleep_ms: 20_000, + l1_tx_sending_max_attempts: 10, + l1_tx_sending_sleep_ms: 30_000, + halt_on_error: true, + } + } + + fn expected_config_with_defaults() -> BaseTokenAdjusterConfig { + BaseTokenAdjusterConfig { + price_polling_interval_ms: 30_000, + price_cache_update_interval_ms: 500, + max_tx_gas: 80_000, + default_priority_fee_per_gas: 1_000_000_000, + max_acceptable_priority_fee_in_gwei: 100_000_000_000, + l1_receipt_checking_max_attempts: 3, + l1_receipt_checking_sleep_ms: 30_000, + l1_tx_sending_max_attempts: 3, + l1_tx_sending_sleep_ms: 30_000, + halt_on_error: false, + } + } + + #[test] + fn from_env_base_token_adjuster() { + let mut lock = MUTEX.lock(); + let config = r#" + BASE_TOKEN_ADJUSTER_PRICE_POLLING_INTERVAL_MS=10000 + BASE_TOKEN_ADJUSTER_PRICE_CACHE_UPDATE_INTERVAL_MS=11000 + BASE_TOKEN_ADJUSTER_MAX_TX_GAS=1000000 + BASE_TOKEN_ADJUSTER_DEFAULT_PRIORITY_FEE_PER_GAS=50000 + BASE_TOKEN_ADJUSTER_MAX_ACCEPTABLE_PRIORITY_FEE_IN_GWEI=10000000000 + BASE_TOKEN_ADJUSTER_L1_RECEIPT_CHECKING_MAX_ATTEMPTS=5 + BASE_TOKEN_ADJUSTER_L1_RECEIPT_CHECKING_SLEEP_MS=20000 + BASE_TOKEN_ADJUSTER_L1_TX_SENDING_MAX_ATTEMPTS=10 + BASE_TOKEN_ADJUSTER_L1_TX_SENDING_SLEEP_MS=30000 + BASE_TOKEN_ADJUSTER_HALT_ON_ERROR=true + "#; + lock.set_env(config); + + let actual = BaseTokenAdjusterConfig::from_env().unwrap(); + assert_eq!(actual, expected_config()); + } + + #[test] + fn from_env_base_token_adjuster_defaults() { + let mut lock = MUTEX.lock(); + lock.remove_env(&[ + "BASE_TOKEN_ADJUSTER_PRICE_POLLING_INTERVAL_MS", + "BASE_TOKEN_ADJUSTER_PRICE_CACHE_UPDATE_INTERVAL_MS", + "BASE_TOKEN_ADJUSTER_MAX_TX_GAS", + "BASE_TOKEN_ADJUSTER_DEFAULT_PRIORITY_FEE_PER_GAS", + "BASE_TOKEN_ADJUSTER_MAX_ACCEPTABLE_PRIORITY_FEE_IN_GWEI", + "BASE_TOKEN_ADJUSTER_L1_RECEIPT_CHECKING_MAX_ATTEMPTS", + "BASE_TOKEN_ADJUSTER_L1_RECEIPT_CHECKING_SLEEP_MS", + "BASE_TOKEN_ADJUSTER_L1_TX_SENDING_MAX_ATTEMPTS", + "BASE_TOKEN_ADJUSTER_L1_TX_SENDING_SLEEP_MS", + "BASE_TOKEN_ADJUSTER_HALT_ON_ERROR", + ]); + + let actual = BaseTokenAdjusterConfig::from_env().unwrap(); + assert_eq!(actual, expected_config_with_defaults()); + } +} diff --git a/core/lib/env_config/src/external_price_api_client.rs b/core/lib/env_config/src/external_price_api_client.rs index 7ec3782dc6b4..60ddeea83151 100644 --- a/core/lib/env_config/src/external_price_api_client.rs +++ b/core/lib/env_config/src/external_price_api_client.rs @@ -1,17 +1,31 @@ -use zksync_config::configs::ExternalPriceApiClientConfig; +use zksync_config::configs::{ + external_price_api_client::ForcedPriceClientConfig, ExternalPriceApiClientConfig, +}; use crate::{envy_load, FromEnv}; impl FromEnv for ExternalPriceApiClientConfig { fn from_env() -> anyhow::Result { - envy_load("external_price_api_client", "EXTERNAL_PRICE_API_CLIENT_") + let mut config: ExternalPriceApiClientConfig = + envy_load("external_price_api_client", "EXTERNAL_PRICE_API_CLIENT_")?; + config.forced = ForcedPriceClientConfig::from_env().ok(); + Ok(config) + } +} + +impl FromEnv for ForcedPriceClientConfig { + fn from_env() -> anyhow::Result { + envy_load( + "external_price_api_client_forced", + "EXTERNAL_PRICE_API_CLIENT_FORCED_", + ) } } #[cfg(test)] mod tests { use zksync_config::configs::external_price_api_client::{ - ExternalPriceApiClientConfig, DEFAULT_TIMEOUT_MS, + ExternalPriceApiClientConfig, ForcedPriceClientConfig, DEFAULT_TIMEOUT_MS, }; use super::*; @@ -25,8 +39,11 @@ mod tests { base_url: Some("https://pro-api.coingecko.com".to_string()), api_key: Some("qwerty12345".to_string()), client_timeout_ms: DEFAULT_TIMEOUT_MS, - forced_numerator: Some(100), - forced_denominator: Some(1), + forced: Some(ForcedPriceClientConfig { + numerator: Some(100), + denominator: Some(1), + fluctuation: Some(10), + }), } } @@ -39,6 +56,7 @@ mod tests { EXTERNAL_PRICE_API_CLIENT_API_KEY=qwerty12345 EXTERNAL_PRICE_API_CLIENT_FORCED_NUMERATOR=100 EXTERNAL_PRICE_API_CLIENT_FORCED_DENOMINATOR=1 + EXTERNAL_PRICE_API_CLIENT_FORCED_FLUCTUATION=10 "#; lock.set_env(config); diff --git a/core/lib/env_config/src/wallets.rs b/core/lib/env_config/src/wallets.rs index 19552e7481cd..3518d56f7b45 100644 --- a/core/lib/env_config/src/wallets.rs +++ b/core/lib/env_config/src/wallets.rs @@ -2,20 +2,29 @@ use std::str::FromStr; use anyhow::Context; use zksync_basic_types::{Address, H256}; -use zksync_config::configs::wallets::{AddressWallet, EthSender, StateKeeper, Wallet, Wallets}; +use zksync_config::configs::wallets::{ + AddressWallet, EthSender, StateKeeper, TokenMultiplierSetter, Wallet, Wallets, +}; use crate::FromEnv; +fn pk_from_env(env_var: &str, context: &str) -> anyhow::Result> { + std::env::var(env_var) + .ok() + .map(|pk| pk.parse::().context(context.to_string())) + .transpose() +} + impl FromEnv for Wallets { fn from_env() -> anyhow::Result { - let operator = std::env::var("ETH_SENDER_SENDER_OPERATOR_PRIVATE_KEY") - .ok() - .map(|pk| pk.parse::().context("Malformed pk")) - .transpose()?; - let blob_operator = std::env::var("ETH_SENDER_SENDER_OPERATOR_BLOBS_PRIVATE_KEY") - .ok() - .map(|pk| pk.parse::().context("Malformed pk")) - .transpose()?; + let operator = pk_from_env( + "ETH_SENDER_SENDER_OPERATOR_PRIVATE_KEY", + "Malformed operator pk", + )?; + let blob_operator = pk_from_env( + "ETH_SENDER_SENDER_OPERATOR_BLOBS_PRIVATE_KEY", + "Malformed blob operator pk", + )?; let eth_sender = if let Some(operator) = operator { let operator = Wallet::from_private_key_bytes(operator, None)?; @@ -40,9 +49,22 @@ impl FromEnv for Wallets { None }; + let token_multiplier_setter_pk = pk_from_env( + "TOKEN_MULTIPLIER_SETTER_PRIVATE_KEY", + "Malformed token multiplier setter pk", + )?; + let token_multiplier_setter = + if let Some(token_multiplier_setter_pk) = token_multiplier_setter_pk { + let wallet = Wallet::from_private_key_bytes(token_multiplier_setter_pk, None)?; + Some(TokenMultiplierSetter { wallet }) + } else { + None + }; + Ok(Self { eth_sender, state_keeper, + token_multiplier_setter, }) } } diff --git a/core/lib/external_price_api/src/forced_price_client.rs b/core/lib/external_price_api/src/forced_price_client.rs index f4b8d72b8b2c..fd166cdfd2da 100644 --- a/core/lib/external_price_api/src/forced_price_client.rs +++ b/core/lib/external_price_api/src/forced_price_client.rs @@ -11,16 +11,24 @@ use crate::PriceAPIClient; #[derive(Debug, Clone)] pub struct ForcedPriceClient { ratio: BaseTokenAPIRatio, + fluctuation: Option, } impl ForcedPriceClient { pub fn new(config: ExternalPriceApiClientConfig) -> Self { - let numerator = config - .forced_numerator + let forced_price_client_config = config + .forced + .expect("forced price client started with no config"); + + let numerator = forced_price_client_config + .numerator .expect("forced price client started with no forced numerator"); - let denominator = config - .forced_denominator + let denominator = forced_price_client_config + .denominator .expect("forced price client started with no forced denominator"); + let fluctuation = forced_price_client_config + .fluctuation + .map(|x| x.clamp(0, 100)); Self { ratio: BaseTokenAPIRatio { @@ -28,6 +36,7 @@ impl ForcedPriceClient { denominator: NonZeroU64::new(denominator).unwrap(), ratio_timestamp: chrono::Utc::now(), }, + fluctuation, } } } @@ -36,27 +45,26 @@ impl ForcedPriceClient { impl PriceAPIClient for ForcedPriceClient { // Returns a ratio which is 10% higher or lower than the configured forced ratio. async fn fetch_ratio(&self, _token_address: Address) -> anyhow::Result { - let mut rng = rand::thread_rng(); - - let numerator_range = ( - (self.ratio.numerator.get() as f64 * 0.9).round() as u64, - (self.ratio.numerator.get() as f64 * 1.1).round() as u64, - ); - - let denominator_range = ( - (self.ratio.denominator.get() as f64 * 0.9).round() as u64, - (self.ratio.denominator.get() as f64 * 1.1).round() as u64, - ); + if let Some(x) = self.fluctuation { + if x != 0 { + let mut rng = rand::thread_rng(); - let new_numerator = rng.gen_range(numerator_range.0..=numerator_range.1); - let new_denominator = rng.gen_range(denominator_range.0..=denominator_range.1); + let mut adjust_range = |value: NonZeroU64| { + let value_f64 = value.get() as f64; + let min = (value_f64 * (1.0 - x as f64 / 100.0)).round() as u64; + let max = (value_f64 * (1.0 + x as f64 / 100.0)).round() as u64; + rng.gen_range(min..=max) + }; + let new_numerator = adjust_range(self.ratio.numerator); + let new_denominator = adjust_range(self.ratio.denominator); - let adjusted_ratio = BaseTokenAPIRatio { - numerator: NonZeroU64::new(new_numerator).unwrap_or(self.ratio.numerator), - denominator: NonZeroU64::new(new_denominator).unwrap_or(self.ratio.denominator), - ratio_timestamp: chrono::Utc::now(), - }; - - Ok(adjusted_ratio) + return Ok(BaseTokenAPIRatio { + numerator: NonZeroU64::new(new_numerator).unwrap_or(self.ratio.numerator), + denominator: NonZeroU64::new(new_denominator).unwrap_or(self.ratio.denominator), + ratio_timestamp: chrono::Utc::now(), + }); + } + } + Ok(self.ratio) } } diff --git a/core/lib/protobuf_config/src/base_token_adjuster.rs b/core/lib/protobuf_config/src/base_token_adjuster.rs index 850acb4bae20..d68db5fd9796 100644 --- a/core/lib/protobuf_config/src/base_token_adjuster.rs +++ b/core/lib/protobuf_config/src/base_token_adjuster.rs @@ -10,11 +10,32 @@ impl ProtoRepr for proto::BaseTokenAdjuster { Ok(configs::base_token_adjuster::BaseTokenAdjusterConfig { price_polling_interval_ms: self .price_polling_interval_ms - .expect("price_polling_interval_ms"), - + .unwrap_or(Self::Type::default_price_polling_interval_ms()), price_cache_update_interval_ms: self .price_cache_update_interval_ms - .expect("price_cache_update_interval_ms"), + .unwrap_or(Self::Type::default_price_cache_update_interval_ms()), + max_tx_gas: self.max_tx_gas.unwrap_or(Self::Type::default_max_tx_gas()), + default_priority_fee_per_gas: self + .default_priority_fee_per_gas + .unwrap_or(Self::Type::default_priority_fee_per_gas()), + max_acceptable_priority_fee_in_gwei: self + .max_acceptable_priority_fee_in_gwei + .unwrap_or(Self::Type::default_max_acceptable_priority_fee_in_gwei()), + halt_on_error: self + .halt_on_error + .unwrap_or(Self::Type::default_halt_on_error()), + l1_receipt_checking_sleep_ms: self + .l1_receipt_checking_sleep_ms + .unwrap_or(Self::Type::default_l1_receipt_checking_sleep_ms()), + l1_receipt_checking_max_attempts: self + .l1_receipt_checking_max_attempts + .unwrap_or(Self::Type::default_l1_receipt_checking_max_attempts()), + l1_tx_sending_max_attempts: self + .l1_tx_sending_max_attempts + .unwrap_or(Self::Type::default_l1_tx_sending_max_attempts()), + l1_tx_sending_sleep_ms: self + .l1_tx_sending_sleep_ms + .unwrap_or(Self::Type::default_l1_tx_sending_sleep_ms()), }) } @@ -22,6 +43,14 @@ impl ProtoRepr for proto::BaseTokenAdjuster { Self { price_polling_interval_ms: Some(this.price_polling_interval_ms), price_cache_update_interval_ms: Some(this.price_cache_update_interval_ms), + l1_receipt_checking_sleep_ms: Some(this.l1_receipt_checking_sleep_ms), + l1_receipt_checking_max_attempts: Some(this.l1_receipt_checking_max_attempts), + l1_tx_sending_max_attempts: Some(this.l1_tx_sending_max_attempts), + l1_tx_sending_sleep_ms: Some(this.l1_tx_sending_sleep_ms), + max_tx_gas: Some(this.max_tx_gas), + default_priority_fee_per_gas: Some(this.default_priority_fee_per_gas), + max_acceptable_priority_fee_in_gwei: Some(this.max_acceptable_priority_fee_in_gwei), + halt_on_error: Some(this.halt_on_error), } } } diff --git a/core/lib/protobuf_config/src/external_price_api_client.rs b/core/lib/protobuf_config/src/external_price_api_client.rs index cd16957d55ad..e5ed809a1284 100644 --- a/core/lib/protobuf_config/src/external_price_api_client.rs +++ b/core/lib/protobuf_config/src/external_price_api_client.rs @@ -1,4 +1,4 @@ -use zksync_config::configs::{self}; +use zksync_config::configs::{self, external_price_api_client::ForcedPriceClientConfig}; use zksync_protobuf::ProtoRepr; use crate::proto::external_price_api_client as proto; @@ -13,20 +13,28 @@ impl ProtoRepr for proto::ExternalPriceApiClient { client_timeout_ms: self.client_timeout_ms.expect("client_timeout_ms"), base_url: self.base_url.clone(), api_key: self.api_key.clone(), - forced_numerator: self.forced_numerator, - forced_denominator: self.forced_denominator, + forced: Some(ForcedPriceClientConfig { + numerator: self.forced_numerator, + denominator: self.forced_denominator, + fluctuation: self.forced_fluctuation, + }), }, ) } fn build(this: &Self::Type) -> Self { + let numerator = this.forced.as_ref().and_then(|x| x.numerator); + let denominator = this.forced.as_ref().and_then(|x| x.denominator); + let fluctuation = this.forced.as_ref().and_then(|x| x.fluctuation); + Self { source: Some(this.source.clone()), base_url: this.base_url.clone(), api_key: this.api_key.clone(), client_timeout_ms: Some(this.client_timeout_ms), - forced_numerator: this.forced_numerator, - forced_denominator: this.forced_denominator, + forced_numerator: numerator, + forced_denominator: denominator, + forced_fluctuation: fluctuation, } } } diff --git a/core/lib/protobuf_config/src/proto/config/base_token_adjuster.proto b/core/lib/protobuf_config/src/proto/config/base_token_adjuster.proto index f3adad8707b5..1132858bfa6f 100644 --- a/core/lib/protobuf_config/src/proto/config/base_token_adjuster.proto +++ b/core/lib/protobuf_config/src/proto/config/base_token_adjuster.proto @@ -5,4 +5,12 @@ package zksync.config.base_token_adjuster; message BaseTokenAdjuster { optional uint64 price_polling_interval_ms = 1; optional uint64 price_cache_update_interval_ms = 2; + optional uint64 max_tx_gas = 3; + optional uint64 default_priority_fee_per_gas = 4; + optional uint64 max_acceptable_priority_fee_in_gwei = 5; + optional uint64 l1_receipt_checking_sleep_ms = 6; + optional uint32 l1_receipt_checking_max_attempts = 7; + optional uint32 l1_tx_sending_max_attempts = 8; + optional uint64 l1_tx_sending_sleep_ms = 9; + optional bool halt_on_error = 10; } diff --git a/core/lib/protobuf_config/src/proto/config/external_price_api_client.proto b/core/lib/protobuf_config/src/proto/config/external_price_api_client.proto index f47e35782e60..646bcfbd7647 100644 --- a/core/lib/protobuf_config/src/proto/config/external_price_api_client.proto +++ b/core/lib/protobuf_config/src/proto/config/external_price_api_client.proto @@ -9,4 +9,5 @@ message ExternalPriceApiClient { optional uint64 client_timeout_ms = 4; optional uint64 forced_numerator = 5; optional uint64 forced_denominator = 6; + optional uint32 forced_fluctuation = 7; } diff --git a/core/lib/protobuf_config/src/proto/config/wallets.proto b/core/lib/protobuf_config/src/proto/config/wallets.proto index fa1ffe3b9195..186b7b80b5d4 100644 --- a/core/lib/protobuf_config/src/proto/config/wallets.proto +++ b/core/lib/protobuf_config/src/proto/config/wallets.proto @@ -15,4 +15,5 @@ message Wallets { optional PrivateKeyWallet operator = 1; // Private key is required optional PrivateKeyWallet blob_operator = 2; // Private key is required optional AddressWallet fee_account = 3; // Only address required for server + optional PrivateKeyWallet token_multiplier_setter = 4; // Private key is required } diff --git a/core/lib/protobuf_config/src/wallets.rs b/core/lib/protobuf_config/src/wallets.rs index 31fa63fd2702..3769dac443d0 100644 --- a/core/lib/protobuf_config/src/wallets.rs +++ b/core/lib/protobuf_config/src/wallets.rs @@ -1,9 +1,10 @@ use anyhow::Context; use zksync_config::configs::{ self, - wallets::{AddressWallet, EthSender, StateKeeper, Wallet}, + wallets::{AddressWallet, EthSender, StateKeeper, TokenMultiplierSetter, Wallet}, }; use zksync_protobuf::{required, ProtoRepr}; +use zksync_types::{Address, K256PrivateKey}; use crate::{parse_h160, parse_h256, proto::wallets as proto}; @@ -53,34 +54,48 @@ impl ProtoRepr for proto::Wallets { None }; + let token_multiplier_setter = + if let Some(token_multiplier_setter) = &self.token_multiplier_setter { + let wallet = Wallet::from_private_key_bytes( + parse_h256( + required(&token_multiplier_setter.private_key) + .context("base_token_adjuster")?, + )?, + token_multiplier_setter + .address + .as_ref() + .and_then(|a| parse_h160(a).ok()), + )?; + Some(TokenMultiplierSetter { wallet }) + } else { + None + }; + Ok(Self::Type { eth_sender, state_keeper, + token_multiplier_setter, }) } fn build(this: &Self::Type) -> Self { + let create_pk_wallet = |addr: Address, pk: &K256PrivateKey| -> proto::PrivateKeyWallet { + proto::PrivateKeyWallet { + address: Some(format!("{:?}", addr)), + private_key: Some(hex::encode(pk.expose_secret().secret_bytes())), + } + }; + let (operator, blob_operator) = if let Some(eth_sender) = &this.eth_sender { let blob = eth_sender .blob_operator .as_ref() - .map(|blob| proto::PrivateKeyWallet { - address: Some(format!("{:?}", blob.address())), - private_key: Some(hex::encode( - blob.private_key().expose_secret().secret_bytes(), - )), - }); + .map(|blob| create_pk_wallet(blob.address(), blob.private_key())); ( - Some(proto::PrivateKeyWallet { - address: Some(format!("{:?}", eth_sender.operator.address())), - private_key: Some(hex::encode( - eth_sender - .operator - .private_key() - .expose_secret() - .secret_bytes(), - )), - }), + Some(create_pk_wallet( + eth_sender.operator.address(), + eth_sender.operator.private_key(), + )), blob, ) } else { @@ -93,10 +108,22 @@ impl ProtoRepr for proto::Wallets { .map(|state_keeper| proto::AddressWallet { address: Some(format!("{:?}", state_keeper.fee_account.address())), }); + + let token_multiplier_setter = + this.token_multiplier_setter + .as_ref() + .map(|token_multiplier_setter| { + create_pk_wallet( + token_multiplier_setter.wallet.address(), + token_multiplier_setter.wallet.private_key(), + ) + }); + Self { blob_operator, operator, fee_account, + token_multiplier_setter, } } } diff --git a/core/lib/zksync_core_leftovers/src/temp_config_store/mod.rs b/core/lib/zksync_core_leftovers/src/temp_config_store/mod.rs index 4d2606dcf12d..8224b03da071 100644 --- a/core/lib/zksync_core_leftovers/src/temp_config_store/mod.rs +++ b/core/lib/zksync_core_leftovers/src/temp_config_store/mod.rs @@ -11,7 +11,7 @@ use zksync_config::{ fri_prover_group::FriProverGroupConfig, house_keeper::HouseKeeperConfig, vm_runner::BasicWitnessInputProducerConfig, - wallets::{AddressWallet, EthSender, StateKeeper, Wallet, Wallets}, + wallets::{AddressWallet, EthSender, StateKeeper, TokenMultiplierSetter, Wallet, Wallets}, CommitmentGeneratorConfig, DatabaseSecrets, ExperimentalVmConfig, ExternalPriceApiClientConfig, FriProofCompressorConfig, FriProverConfig, FriProverGatewayConfig, FriWitnessGeneratorConfig, FriWitnessVectorGeneratorConfig, @@ -147,9 +147,15 @@ impl TempConfigStore { .expect("Must be presented in env variables"), ), }); + let token_multiplier_setter = self.base_token_adjuster_config.as_ref().and_then(|config| { + let pk = config.private_key().ok()??; + let wallet = Wallet::new(pk); + Some(TokenMultiplierSetter { wallet }) + }); Wallets { eth_sender, state_keeper, + token_multiplier_setter, } } } diff --git a/core/node/api_server/src/tx_sender/mod.rs b/core/node/api_server/src/tx_sender/mod.rs index 085f3c395dd3..18c500c0ed0f 100644 --- a/core/node/api_server/src/tx_sender/mod.rs +++ b/core/node/api_server/src/tx_sender/mod.rs @@ -521,7 +521,7 @@ impl TxSender { tracing::info!( "Submitted Tx is Unexecutable {:?} because of MaxFeePerGasTooLow {}", tx.hash(), - tx.common_data.fee.max_fee_per_gas + tx.common_data.fee.max_fee_per_gas, ); return Err(SubmitTxError::MaxFeePerGasTooLow); } diff --git a/core/node/base_token_adjuster/Cargo.toml b/core/node/base_token_adjuster/Cargo.toml index 812cacaa1f73..c21576e37327 100644 --- a/core/node/base_token_adjuster/Cargo.toml +++ b/core/node/base_token_adjuster/Cargo.toml @@ -16,6 +16,10 @@ zksync_dal.workspace = true zksync_config.workspace = true zksync_types.workspace = true zksync_external_price_api.workspace = true +zksync_contracts.workspace = true +zksync_eth_client.workspace = true +zksync_node_fee_model.workspace = true + tokio = { workspace = true, features = ["time"] } anyhow.workspace = true @@ -23,3 +27,4 @@ tracing.workspace = true chrono.workspace = true rand.workspace = true async-trait.workspace = true +hex.workspace = true diff --git a/core/node/base_token_adjuster/src/base_token_ratio_persister.rs b/core/node/base_token_adjuster/src/base_token_ratio_persister.rs index 8c94b19e0179..ed00b2b212ad 100644 --- a/core/node/base_token_adjuster/src/base_token_ratio_persister.rs +++ b/core/node/base_token_adjuster/src/base_token_ratio_persister.rs @@ -1,11 +1,19 @@ -use std::{fmt::Debug, sync::Arc, time::Duration}; +use std::{cmp::max, fmt::Debug, sync::Arc, time::Duration}; use anyhow::Context as _; use tokio::{sync::watch, time::sleep}; use zksync_config::configs::base_token_adjuster::BaseTokenAdjusterConfig; +use zksync_contracts::chain_admin_contract; use zksync_dal::{ConnectionPool, Core, CoreDal}; +use zksync_eth_client::{BoundEthInterface, Options}; use zksync_external_price_api::PriceAPIClient; -use zksync_types::{base_token_ratio::BaseTokenAPIRatio, Address}; +use zksync_node_fee_model::l1_gas_price::TxParamsProvider; +use zksync_types::{ + base_token_ratio::BaseTokenAPIRatio, + ethabi::{Contract, Token}, + web3::{contract::Tokenize, BlockNumber}, + Address, U256, +}; #[derive(Debug, Clone)] pub struct BaseTokenRatioPersister { @@ -13,20 +21,40 @@ pub struct BaseTokenRatioPersister { config: BaseTokenAdjusterConfig, base_token_address: Address, price_api_client: Arc, + eth_client: Box, + gas_adjuster: Arc, + token_multiplier_setter_account_address: Address, + chain_admin_contract: Contract, + diamond_proxy_contract_address: Address, + chain_admin_contract_address: Option
, } impl BaseTokenRatioPersister { + #[allow(clippy::too_many_arguments)] pub fn new( pool: ConnectionPool, config: BaseTokenAdjusterConfig, base_token_address: Address, price_api_client: Arc, + eth_client: Box, + gas_adjuster: Arc, + token_multiplier_setter_account_address: Address, + diamond_proxy_contract_address: Address, + chain_admin_contract_address: Option
, ) -> Self { + let chain_admin_contract = chain_admin_contract(); + Self { pool, config, base_token_address, price_api_client, + eth_client, + gas_adjuster, + token_multiplier_setter_account_address, + chain_admin_contract, + diamond_proxy_contract_address, + chain_admin_contract_address, } } @@ -42,8 +70,14 @@ impl BaseTokenRatioPersister { } if let Err(err) = self.loop_iteration().await { - return Err(err) - .context("Failed to execute a base_token_ratio_persister loop iteration"); + tracing::warn!( + "Error in the base_token_ratio_persister loop interaction {}", + err + ); + if self.config.halt_on_error { + return Err(err) + .context("Failed to execute a base_token_ratio_persister loop iteration"); + } } } @@ -54,11 +88,75 @@ impl BaseTokenRatioPersister { async fn loop_iteration(&self) -> anyhow::Result<()> { // TODO(PE-148): Consider shifting retry upon adding external API redundancy. let new_ratio = self.retry_fetch_ratio().await?; - self.persist_ratio(new_ratio).await?; - // TODO(PE-128): Update L1 ratio - Ok(()) + let max_attempts = self.config.l1_tx_sending_max_attempts; + let sleep_duration = self.config.l1_tx_sending_sleep_duration(); + let mut result: anyhow::Result<()> = Ok(()); + let mut prev_base_fee_per_gas: Option = None; + let mut prev_priority_fee_per_gas: Option = None; + + for attempt in 0..max_attempts { + let (base_fee_per_gas, priority_fee_per_gas) = + self.get_eth_fees(prev_base_fee_per_gas, prev_priority_fee_per_gas); + + result = self + .send_ratio_to_l1(new_ratio, base_fee_per_gas, priority_fee_per_gas) + .await; + if let Some(err) = result.as_ref().err() { + tracing::info!( + "Failed to update base token multiplier on L1, attempt {}, base_fee_per_gas {}, priority_fee_per_gas {}: {}", + attempt + 1, + base_fee_per_gas, + priority_fee_per_gas, + err + ); + tokio::time::sleep(sleep_duration).await; + prev_base_fee_per_gas = Some(base_fee_per_gas); + prev_priority_fee_per_gas = Some(priority_fee_per_gas); + } else { + tracing::info!( + "Updated base token multiplier on L1: numerator {}, denominator {}, base_fee_per_gas {}, priority_fee_per_gas {}", + new_ratio.numerator.get(), + new_ratio.denominator.get(), + base_fee_per_gas, + priority_fee_per_gas + ); + return result; + } + } + result + } + + fn get_eth_fees( + &self, + prev_base_fee_per_gas: Option, + prev_priority_fee_per_gas: Option, + ) -> (u64, u64) { + // Use get_blob_tx_base_fee here instead of get_base_fee to optimise for fast inclusion. + // get_base_fee might cause the transaction to be stuck in the mempool for 10+ minutes. + let mut base_fee_per_gas = self.gas_adjuster.as_ref().get_blob_tx_base_fee(); + let mut priority_fee_per_gas = self.gas_adjuster.as_ref().get_priority_fee(); + if let Some(x) = prev_priority_fee_per_gas { + // Increase `priority_fee_per_gas` by at least 20% to prevent "replacement transaction under-priced" error. + priority_fee_per_gas = max(priority_fee_per_gas, (x * 6) / 5 + 1); + } + + if let Some(x) = prev_base_fee_per_gas { + // same for base_fee_per_gas but 10% + base_fee_per_gas = max(base_fee_per_gas, x + (x / 10) + 1); + } + + // Extra check to prevent sending transaction will extremely high priority fee. + if priority_fee_per_gas > self.config.max_acceptable_priority_fee_in_gwei { + panic!( + "Extremely high value of priority_fee_per_gas is suggested: {}, while max acceptable is {}", + priority_fee_per_gas, + self.config.max_acceptable_priority_fee_in_gwei + ); + } + + (base_fee_per_gas, priority_fee_per_gas) } async fn retry_fetch_ratio(&self) -> anyhow::Result { @@ -112,4 +210,88 @@ impl BaseTokenRatioPersister { Ok(id) } + + async fn send_ratio_to_l1( + &self, + api_ratio: BaseTokenAPIRatio, + base_fee_per_gas: u64, + priority_fee_per_gas: u64, + ) -> anyhow::Result<()> { + let fn_set_token_multiplier = self + .chain_admin_contract + .function("setTokenMultiplier") + .context("`setTokenMultiplier` function must be present in the ChainAdmin contract")?; + + let calldata = fn_set_token_multiplier + .encode_input( + &( + Token::Address(self.diamond_proxy_contract_address), + Token::Uint(api_ratio.numerator.get().into()), + Token::Uint(api_ratio.denominator.get().into()), + ) + .into_tokens(), + ) + .context("failed encoding `setTokenMultiplier` input")?; + + let nonce = (*self.eth_client) + .as_ref() + .nonce_at_for_account( + self.token_multiplier_setter_account_address, + BlockNumber::Pending, + ) + .await + .with_context(|| "failed getting transaction count")? + .as_u64(); + + let options = Options { + gas: Some(U256::from(self.config.max_tx_gas)), + nonce: Some(U256::from(nonce)), + max_fee_per_gas: Some(U256::from(base_fee_per_gas + priority_fee_per_gas)), + max_priority_fee_per_gas: Some(U256::from(priority_fee_per_gas)), + ..Default::default() + }; + + let signed_tx = self + .eth_client + .sign_prepared_tx_for_addr( + calldata, + self.chain_admin_contract_address.unwrap(), + options, + ) + .await + .context("cannot sign a `setTokenMultiplier` transaction")?; + + let hash = (*self.eth_client) + .as_ref() + .send_raw_tx(signed_tx.raw_tx) + .await + .context("failed sending `setTokenMultiplier` transaction")?; + + let max_attempts = self.config.l1_receipt_checking_max_attempts; + let sleep_duration = self.config.l1_receipt_checking_sleep_duration(); + for _i in 0..max_attempts { + let maybe_receipt = (*self.eth_client) + .as_ref() + .tx_receipt(hash) + .await + .context("failed getting receipt for `setTokenMultiplier` transaction")?; + if let Some(receipt) = maybe_receipt { + if receipt.status == Some(1.into()) { + return Ok(()); + } + return Err(anyhow::Error::msg(format!( + "`setTokenMultiplier` transaction {:?} failed with status {:?}", + hex::encode(hash), + receipt.status + ))); + } else { + tokio::time::sleep(sleep_duration).await; + } + } + + Err(anyhow::Error::msg(format!( + "Unable to retrieve `setTokenMultiplier` transaction status in {} attempts", + max_attempts + ))) + } } diff --git a/core/node/base_token_adjuster/src/base_token_ratio_provider.rs b/core/node/base_token_adjuster/src/base_token_ratio_provider.rs index a89c2d909a15..e16ea16ff0f5 100644 --- a/core/node/base_token_adjuster/src/base_token_ratio_provider.rs +++ b/core/node/base_token_adjuster/src/base_token_ratio_provider.rs @@ -9,13 +9,9 @@ use async_trait::async_trait; use tokio::sync::watch; use zksync_config::BaseTokenAdjusterConfig; use zksync_dal::{ConnectionPool, Core, CoreDal}; +use zksync_node_fee_model::BaseTokenRatioProvider; use zksync_types::fee_model::BaseTokenConversionRatio; -#[async_trait] -pub trait BaseTokenRatioProvider: Debug + Send + Sync + 'static { - fn get_conversion_ratio(&self) -> BaseTokenConversionRatio; -} - #[derive(Debug, Clone)] pub struct DBBaseTokenRatioProvider { pub pool: ConnectionPool, diff --git a/core/node/base_token_adjuster/src/lib.rs b/core/node/base_token_adjuster/src/lib.rs index 2340ca56c2a7..96169727e5fa 100644 --- a/core/node/base_token_adjuster/src/lib.rs +++ b/core/node/base_token_adjuster/src/lib.rs @@ -1,8 +1,6 @@ pub use self::{ base_token_ratio_persister::BaseTokenRatioPersister, - base_token_ratio_provider::{ - BaseTokenRatioProvider, DBBaseTokenRatioProvider, NoOpRatioProvider, - }, + base_token_ratio_provider::{DBBaseTokenRatioProvider, NoOpRatioProvider}, }; mod base_token_ratio_persister; diff --git a/core/node/fee_model/Cargo.toml b/core/node/fee_model/Cargo.toml index 643e87b9c27e..09048515e7a0 100644 --- a/core/node/fee_model/Cargo.toml +++ b/core/node/fee_model/Cargo.toml @@ -18,7 +18,6 @@ zksync_config.workspace = true zksync_eth_client.workspace = true zksync_utils.workspace = true zksync_web3_decl.workspace = true -zksync_base_token_adjuster.workspace = true bigdecimal.workspace = true tokio = { workspace = true, features = ["time"] } diff --git a/core/node/fee_model/src/lib.rs b/core/node/fee_model/src/lib.rs index f65239912523..217ed71e38cb 100644 --- a/core/node/fee_model/src/lib.rs +++ b/core/node/fee_model/src/lib.rs @@ -1,13 +1,12 @@ -use std::{fmt, sync::Arc}; +use std::{fmt, fmt::Debug, sync::Arc}; use anyhow::Context as _; use async_trait::async_trait; -use zksync_base_token_adjuster::BaseTokenRatioProvider; use zksync_dal::{ConnectionPool, Core, CoreDal}; use zksync_types::{ fee_model::{ - BatchFeeInput, FeeModelConfig, FeeModelConfigV2, FeeParams, FeeParamsV1, FeeParamsV2, - L1PeggedBatchFeeModelInput, PubdataIndependentBatchFeeModelInput, + BaseTokenConversionRatio, BatchFeeInput, FeeModelConfig, FeeModelConfigV2, FeeParams, + FeeParamsV1, FeeParamsV2, L1PeggedBatchFeeModelInput, PubdataIndependentBatchFeeModelInput, }, U256, }; @@ -17,6 +16,13 @@ use crate::l1_gas_price::GasAdjuster; pub mod l1_gas_price; +/// Trait responsible for providing numerator and denominator for adjusting gas price that is denominated +/// in a non-eth base token +#[async_trait] +pub trait BaseTokenRatioProvider: Debug + Send + Sync + 'static { + fn get_conversion_ratio(&self) -> BaseTokenConversionRatio; +} + /// Trait responsible for providing fee info for a batch #[async_trait] pub trait BatchFeeModelInputProvider: fmt::Debug + 'static + Send + Sync { @@ -287,7 +293,6 @@ mod tests { use std::num::NonZeroU64; use l1_gas_price::GasAdjusterClient; - use zksync_base_token_adjuster::NoOpRatioProvider; use zksync_config::{configs::eth_sender::PubdataSendingMode, GasAdjusterConfig}; use zksync_eth_client::{clients::MockSettlementLayer, BaseFees}; use zksync_types::{commitment::L1BatchCommitmentMode, fee_model::BaseTokenConversionRatio}; @@ -590,6 +595,24 @@ mod tests { assert_eq!(input.fair_pubdata_price, 1_000_000 * GWEI); } + #[derive(Debug, Clone)] + struct DummyTokenRatioProvider { + ratio: BaseTokenConversionRatio, + } + + impl DummyTokenRatioProvider { + pub fn new(ratio: BaseTokenConversionRatio) -> Self { + Self { ratio } + } + } + + #[async_trait] + impl BaseTokenRatioProvider for DummyTokenRatioProvider { + fn get_conversion_ratio(&self) -> BaseTokenConversionRatio { + self.ratio + } + } + #[tokio::test] async fn test_get_fee_model_params() { struct TestCase { @@ -700,7 +723,7 @@ mod tests { let gas_adjuster = setup_gas_adjuster(case.input_l1_gas_price, case.input_l1_pubdata_price).await; - let base_token_ratio_provider = NoOpRatioProvider::new(case.conversion_ratio); + let base_token_ratio_provider = DummyTokenRatioProvider::new(case.conversion_ratio); let config = FeeModelConfig::V2(FeeModelConfigV2 { minimal_l2_gas_price: case.input_minimal_l2_gas_price, diff --git a/core/node/node_framework/src/implementations/layers/base_token/base_token_ratio_persister.rs b/core/node/node_framework/src/implementations/layers/base_token/base_token_ratio_persister.rs index d15f9bea0e25..23e403e7b6fa 100644 --- a/core/node/node_framework/src/implementations/layers/base_token/base_token_ratio_persister.rs +++ b/core/node/node_framework/src/implementations/layers/base_token/base_token_ratio_persister.rs @@ -1,8 +1,15 @@ use zksync_base_token_adjuster::BaseTokenRatioPersister; -use zksync_config::{configs::base_token_adjuster::BaseTokenAdjusterConfig, ContractsConfig}; +use zksync_config::{ + configs::{base_token_adjuster::BaseTokenAdjusterConfig, wallets::Wallets}, + ContractsConfig, +}; +use zksync_eth_client::clients::PKSigningClient; +use zksync_types::L1ChainId; use crate::{ implementations::resources::{ + eth_interface::EthInterfaceResource, + l1_tx_params::TxParamsResource, pools::{MasterPool, PoolResource}, price_api_client::PriceAPIClientResource, }, @@ -20,6 +27,8 @@ use crate::{ pub struct BaseTokenRatioPersisterLayer { config: BaseTokenAdjusterConfig, contracts_config: ContractsConfig, + wallets_config: Wallets, + l1_chain_id: L1ChainId, } #[derive(Debug, FromContext)] @@ -28,6 +37,8 @@ pub struct Input { pub master_pool: PoolResource, #[context(default)] pub price_api_client: PriceAPIClientResource, + pub eth_client: EthInterfaceResource, + pub tx_params: TxParamsResource, } #[derive(Debug, IntoContext)] @@ -38,10 +49,17 @@ pub struct Output { } impl BaseTokenRatioPersisterLayer { - pub fn new(config: BaseTokenAdjusterConfig, contracts_config: ContractsConfig) -> Self { + pub fn new( + config: BaseTokenAdjusterConfig, + contracts_config: ContractsConfig, + wallets_config: Wallets, + l1_chain_id: L1ChainId, + ) -> Self { Self { config, contracts_config, + wallets_config, + l1_chain_id, } } } @@ -63,12 +81,37 @@ impl WiringLayer for BaseTokenRatioPersisterLayer { .contracts_config .base_token_addr .expect("base token address is not set"); + let diamond_proxy_contract_address = self.contracts_config.diamond_proxy_addr; + let chain_admin_contract_address = self.contracts_config.chain_admin_addr; + let token_multiplier_setter_wallet = self + .wallets_config + .token_multiplier_setter + .expect("base token adjuster wallet is not set") + .wallet; + + let tms_private_key = token_multiplier_setter_wallet.private_key(); + let tms_address = token_multiplier_setter_wallet.address(); + let EthInterfaceResource(query_client) = input.eth_client; + + let signing_client = PKSigningClient::new_raw( + tms_private_key.clone(), + self.contracts_config.diamond_proxy_addr, + self.config.default_priority_fee_per_gas, + #[allow(clippy::useless_conversion)] + self.l1_chain_id.into(), + query_client.clone().for_component("base_token_adjuster"), + ); let persister = BaseTokenRatioPersister::new( master_pool, self.config, base_token_addr, price_api_client.0, + Box::new(signing_client), + input.tx_params.0, + tms_address, + diamond_proxy_contract_address, + chain_admin_contract_address, ); Ok(Output { persister }) diff --git a/core/node/node_framework/src/implementations/resources/base_token_ratio_provider.rs b/core/node/node_framework/src/implementations/resources/base_token_ratio_provider.rs index 6699d5dfc70b..6eb9ef413322 100644 --- a/core/node/node_framework/src/implementations/resources/base_token_ratio_provider.rs +++ b/core/node/node_framework/src/implementations/resources/base_token_ratio_provider.rs @@ -1,6 +1,7 @@ use std::sync::Arc; -use zksync_base_token_adjuster::{BaseTokenRatioProvider, NoOpRatioProvider}; +use zksync_base_token_adjuster::NoOpRatioProvider; +use zksync_node_fee_model::BaseTokenRatioProvider; use crate::resource::Resource; diff --git a/core/tests/ts-integration/tests/base-token.test.ts b/core/tests/ts-integration/tests/base-token.test.ts index 51d88f7dd52a..adb32def5b07 100644 --- a/core/tests/ts-integration/tests/base-token.test.ts +++ b/core/tests/ts-integration/tests/base-token.test.ts @@ -26,6 +26,21 @@ describe('base ERC20 contract checks', () => { isETHBasedChain = zksync.utils.isAddressEq(baseToken, zksync.utils.ETH_ADDRESS_IN_CONTRACTS); }); + test('Base token ratio is updated on L1', async () => { + if (isETHBasedChain) { + return; + } + + const zksyncAddress = await alice._providerL2().getMainContractAddress(); + const zksyncContract = new ethers.Contract(zksyncAddress, zksync.utils.ZKSYNC_MAIN_ABI, alice.ethWallet()); + const numerator = Number(await zksyncContract.baseTokenGasPriceMultiplierNominator()); + const denominator = Number(await zksyncContract.baseTokenGasPriceMultiplierDenominator()); + + // checking that the numerator and denominator don't have their default values + expect(numerator).toBe(3); + expect(denominator).toBe(2); + }); + test('Can perform a deposit', async () => { const amount = 1n; // 1 wei is enough. const gasPrice = await scaledGasPrice(alice); diff --git a/etc/env/base/base_token_adjuster.toml b/etc/env/base/base_token_adjuster.toml index b1b997eb67ac..a5f5782a0e6d 100644 --- a/etc/env/base/base_token_adjuster.toml +++ b/etc/env/base/base_token_adjuster.toml @@ -4,5 +4,6 @@ # How often to poll external price feeds for the base token price. price_polling_interval_ms = "30000" - price_cache_update_interval_ms = "2000" +max_tx_gas = "80000" +default_priority_fee_per_gas = "1000000" diff --git a/etc/env/base/external_price_api.toml b/etc/env/base/external_price_api.toml index 635195fd7608..bb22e86c432b 100644 --- a/etc/env/base/external_price_api.toml +++ b/etc/env/base/external_price_api.toml @@ -3,6 +3,8 @@ [external_price_api_client] # What source to use for the external price API. Currently only options are "forced", "no-op", and "coingecko". -source = "no-op" +source = "forced" -client_timeout_ms = 10000 +[external_price_api_client.forced] +numerator = 3 +denominator = 2 diff --git a/etc/env/base/private.toml b/etc/env/base/private.toml index e6367e013519..ae511f96106e 100644 --- a/etc/env/base/private.toml +++ b/etc/env/base/private.toml @@ -23,3 +23,7 @@ secrets_path = "etc/env/consensus_secrets.yaml" [misc] # Private key for the fee seller account fee_account_private_key = "0x27593fea79697e947890ecbecce7901b0008345e5d7259710d0dd5e500d040be" + + +[token_multiplier_setter] +private_key = "0xd08aa7ae1bb5ddd46c3c2d8cdb5894ab9f54dec467233686ca42629e826ac4c6" \ No newline at end of file diff --git a/etc/env/file_based/general.yaml b/etc/env/file_based/general.yaml index 90a509638c61..9df7358c08cd 100644 --- a/etc/env/file_based/general.yaml +++ b/etc/env/file_based/general.yaml @@ -292,9 +292,14 @@ prover_job_monitor: base_token_adjuster: price_polling_interval_ms: 30000 price_cache_update_interval_ms: 2000 + max_tx_gas: 80000 + default_priority_fee_per_gas: 1000000 external_price_api_client: - source: "no-op" + source: "forced" client_timeout_ms: 10000 + forced_numerator: 3 + forced_denominator: 2 + house_keeper: l1_batch_metrics_reporting_interval_ms: 10000 diff --git a/etc/env/file_based/wallets.yaml b/etc/env/file_based/wallets.yaml index 5d85e379e8aa..51861f8c03bf 100644 --- a/etc/env/file_based/wallets.yaml +++ b/etc/env/file_based/wallets.yaml @@ -13,3 +13,6 @@ deployer: governor: private_key: 0x0324a1a769864837a67b051112e19b47c3ef0d2b300a7a9e3eb83a36156956f9 address: 0xF8A3188d179133204bFE984d5275D926D140953b +token_multiplier_setter: + private_key: 0xd08aa7ae1bb5ddd46c3c2d8cdb5894ab9f54dec467233686ca42629e826ac4c6 + address: 0x5711E991397FCa8F5651c9Bb6FA06b57e4a4DCC0 diff --git a/etc/reth/chaindata/reth_config b/etc/reth/chaindata/reth_config index 5709c09b89fd..24e15c4b35bd 100644 --- a/etc/reth/chaindata/reth_config +++ b/etc/reth/chaindata/reth_config @@ -72,6 +72,9 @@ }, "e706e60ab5dc512c36a4646d719b889f398cbbcb": { "balance": "0x4B3B4CA85A86C47A098A224000000000" + }, + "5711E991397FCa8F5651c9Bb6FA06b57e4a4DCC0": { + "balance": "0x4B3B4CA85A86C47A098A224000000000" } }, "number": "0x0", diff --git a/infrastructure/zk/src/contract.ts b/infrastructure/zk/src/contract.ts index b9b4a1861c0c..ba9fe08041db 100644 --- a/infrastructure/zk/src/contract.ts +++ b/infrastructure/zk/src/contract.ts @@ -2,6 +2,8 @@ import { Command } from 'commander'; import * as utils from 'utils'; import * as env from './env'; import fs from 'fs'; +import { Wallet } from 'ethers'; +import path from 'path'; export async function build(): Promise { await utils.spawn('yarn l1-contracts build'); @@ -222,10 +224,24 @@ export async function registerHyperchain({ await utils.confirmAction(); const privateKey = process.env.GOVERNOR_PRIVATE_KEY; + let tokenMultiplierSetterAddress = process.env.TOKEN_MULTIPLIER_SETTER_ADDRESS; + + if (baseTokenName && !tokenMultiplierSetterAddress) { + const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, `etc/test_config/constant`); + const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); + // this is one of the rich accounts + tokenMultiplierSetterAddress = Wallet.fromMnemonic( + process.env.MNEMONIC ?? ethTestConfig.mnemonic, + "m/44'/60'/0'/0/2" + ).address; + console.log(`Defaulting token multiplier setter address to ${tokenMultiplierSetterAddress}`); + } + const args = [ privateKey ? `--private-key ${privateKey}` : '', baseTokenName ? `--base-token-name ${baseTokenName}` : '', - deploymentMode == DeploymentMode.Validium ? '--validium-mode' : '' + deploymentMode == DeploymentMode.Validium ? '--validium-mode' : '', + tokenMultiplierSetterAddress ? `--token-multiplier-setter-address ${tokenMultiplierSetterAddress}` : '' ]; await utils.spawn(`yarn l1-contracts register-hyperchain ${args.join(' ')} | tee registerHyperchain.log`); const deployLog = fs.readFileSync('registerHyperchain.log').toString(); @@ -314,6 +330,10 @@ command .description('register hyperchain') .option('--base-token-name ', 'base token name') .option('--deployment-mode ', 'deploy contracts in Validium mode') + .option( + '--token-multiplier-setter-address ', + 'address of the token multiplier setter' + ) .action(registerHyperchain); command .command('deploy-l2-through-l1') diff --git a/infrastructure/zk/src/hyperchain_wizard.ts b/infrastructure/zk/src/hyperchain_wizard.ts index 166ad2b19f5f..6a9348c7203b 100644 --- a/infrastructure/zk/src/hyperchain_wizard.ts +++ b/infrastructure/zk/src/hyperchain_wizard.ts @@ -130,7 +130,7 @@ async function setHyperchainMetadata(runObservability: boolean) { const results: any = await enquirer.prompt(questions); // TODO(EVM-574): add random chainId generation here if user does not want to pick chainId. - let deployer, governor, ethOperator, blobOperator, feeReceiver: ethers.Wallet | undefined; + let deployer, governor, ethOperator, blobOperator, feeReceiver, tokenMultiplierSetter: ethers.Wallet | undefined; let feeReceiverAddress, l1Rpc, l1Id, databaseUrl, databaseProverUrl; if (results.l1Chain !== BaseNetwork.LOCALHOST || results.l1Chain !== BaseNetwork.LOCALHOST_CUSTOM) { @@ -204,6 +204,7 @@ async function setHyperchainMetadata(runObservability: boolean) { ethOperator = ethers.Wallet.createRandom(); feeReceiver = ethers.Wallet.createRandom(); blobOperator = ethers.Wallet.createRandom(); + tokenMultiplierSetter = ethers.Wallet.createRandom(); feeReceiverAddress = feeReceiver.address; } else { console.log(warning('The private keys for these wallets must be different from each other!\n')); @@ -237,6 +238,13 @@ async function setHyperchainMetadata(runObservability: boolean) { name: 'feeReceiver', type: 'input', required: true + }, + { + message: + 'Private key of the token multiplier setter (the one who can update base token nominator and denominator on L1)', + name: 'tokenMultiplierSetter', + type: 'input', + required: true } ]; @@ -266,6 +274,12 @@ async function setHyperchainMetadata(runObservability: boolean) { throw Error(error('Blob Operator private key is invalid')); } + try { + tokenMultiplierSetter = new ethers.Wallet(keyResults.tokenMultiplierSetter); + } catch (e) { + throw Error(error('Token Multiplier Setter private key is invalid')); + } + if (!utils.isAddress(keyResults.feeReceiver)) { throw Error(error('Fee Receiver address is not a valid address')); } @@ -299,6 +313,7 @@ async function setHyperchainMetadata(runObservability: boolean) { governor = new ethers.Wallet(richWallets[1].privateKey); ethOperator = new ethers.Wallet(richWallets[2].privateKey); blobOperator = new ethers.Wallet(richWallets[3].privateKey); + tokenMultiplierSetter = new ethers.Wallet(richWallets[4].privateKey); feeReceiver = undefined; feeReceiverAddress = richWallets[4].address; @@ -313,6 +328,7 @@ async function setHyperchainMetadata(runObservability: boolean) { printAddressInfo('ETH Operator', ethOperator.address); printAddressInfo('Blob Operator', blobOperator.address); printAddressInfo('Fee receiver', feeReceiverAddress); + printAddressInfo('Token multiplier setter', tokenMultiplierSetter.address); console.log( warning( @@ -380,6 +396,7 @@ async function setHyperchainMetadata(runObservability: boolean) { env.modify('GOVERNOR_ADDRESS', governor.address, process.env.ENV_FILE!); env.modify('CHAIN_STATE_KEEPER_FEE_ACCOUNT_ADDR', feeReceiverAddress, process.env.ENV_FILE!); env.modify('ETH_SENDER_SENDER_PROOF_SENDING_MODE', 'SkipEveryProof', process.env.ENV_FILE!); + env.modify('TOKEN_MULTIPLIER_SETTER_ADDRESS', tokenMultiplierSetter.address, process.env.ENV_FILE!); if (feeReceiver) { env.modify('FEE_RECEIVER_PRIVATE_KEY', feeReceiver.privateKey, process.env.ENV_FILE!); diff --git a/zk_toolbox/crates/config/src/wallet_creation.rs b/zk_toolbox/crates/config/src/wallet_creation.rs index 249d1662a933..a27d55f6f46b 100644 --- a/zk_toolbox/crates/config/src/wallet_creation.rs +++ b/zk_toolbox/crates/config/src/wallet_creation.rs @@ -58,5 +58,6 @@ pub fn create_localhost_wallets( blob_operator: Wallet::from_mnemonic(ð_mnemonic.test_mnemonic, &base_path, 2)?, fee_account: Wallet::from_mnemonic(ð_mnemonic.test_mnemonic, &base_path, 3)?, governor: Wallet::from_mnemonic(ð_mnemonic.test_mnemonic, &base_path, 4)?, + token_multiplier_setter: Wallet::from_mnemonic(ð_mnemonic.test_mnemonic, &base_path, 5)?, }) } diff --git a/zk_toolbox/crates/config/src/wallets.rs b/zk_toolbox/crates/config/src/wallets.rs index 460c4e3574a3..a2e5be87440a 100644 --- a/zk_toolbox/crates/config/src/wallets.rs +++ b/zk_toolbox/crates/config/src/wallets.rs @@ -15,6 +15,7 @@ pub struct WalletsConfig { pub blob_operator: Wallet, pub fee_account: Wallet, pub governor: Wallet, + pub token_multiplier_setter: Wallet, } impl WalletsConfig { @@ -26,6 +27,7 @@ impl WalletsConfig { blob_operator: Wallet::random(rng), fee_account: Wallet::random(rng), governor: Wallet::random(rng), + token_multiplier_setter: Wallet::random(rng), } } @@ -37,6 +39,7 @@ impl WalletsConfig { blob_operator: Wallet::empty(), fee_account: Wallet::empty(), governor: Wallet::empty(), + token_multiplier_setter: Wallet::empty(), } } pub fn deployer_private_key(&self) -> Option { diff --git a/zk_toolbox/crates/zk_inception/src/accept_ownership.rs b/zk_toolbox/crates/zk_inception/src/accept_ownership.rs index 567506aef670..ad37f7cff4dd 100644 --- a/zk_toolbox/crates/zk_inception/src/accept_ownership.rs +++ b/zk_toolbox/crates/zk_inception/src/accept_ownership.rs @@ -19,8 +19,9 @@ use crate::{ lazy_static! { static ref ACCEPT_ADMIN: BaseContract = BaseContract::from( parse_abi(&[ - "function acceptOwner(address governor, address target) public", - "function acceptAdmin(address admin, address target) public" + "function governanceAcceptOwner(address governor, address target) public", + "function chainAdminAcceptAdmin(address admin, address target) public", + "function chainSetTokenMultiplierSetter(address chainAdmin, address target) public" ]) .unwrap(), ); @@ -42,7 +43,7 @@ pub async fn accept_admin( forge_args.resume = false; let calldata = ACCEPT_ADMIN - .encode("acceptAdmin", (admin, target_address)) + .encode("chainAdminAcceptAdmin", (admin, target_address)) .unwrap(); let foundry_contracts_path = ecosystem_config.path_to_foundry(); let forge = Forge::new(&foundry_contracts_path) @@ -57,6 +58,40 @@ pub async fn accept_admin( accept_ownership(shell, governor, forge).await } +pub async fn set_token_multiplier_setter( + shell: &Shell, + ecosystem_config: &EcosystemConfig, + governor: Option, + chain_admin_address: Address, + target_address: Address, + forge_args: &ForgeScriptArgs, + l1_rpc_url: String, +) -> anyhow::Result<()> { + // Resume for accept admin doesn't work properly. Foundry assumes that if signature of the function is the same, + // than it's the same call, but because we are calling this function multiple times during the init process, + // code assumes that doing only once is enough, but actually we need to accept admin multiple times + let mut forge_args = forge_args.clone(); + forge_args.resume = false; + + let calldata = ACCEPT_ADMIN + .encode( + "chainSetTokenMultiplierSetter", + (chain_admin_address, target_address), + ) + .unwrap(); + let foundry_contracts_path = ecosystem_config.path_to_foundry(); + let forge = Forge::new(&foundry_contracts_path) + .script( + &ACCEPT_GOVERNANCE_SCRIPT_PARAMS.script(), + forge_args.clone(), + ) + .with_ffi() + .with_rpc_url(l1_rpc_url) + .with_broadcast() + .with_calldata(&calldata); + update_token_multiplier_setter(shell, governor, forge).await +} + pub async fn accept_owner( shell: &Shell, ecosystem_config: &EcosystemConfig, @@ -71,7 +106,7 @@ pub async fn accept_owner( forge_args.resume = false; let calldata = ACCEPT_ADMIN - .encode("acceptOwner", (governor_contract, target_address)) + .encode("governanceAcceptOwner", (governor_contract, target_address)) .unwrap(); let foundry_contracts_path = ecosystem_config.path_to_foundry(); let forge = Forge::new(&foundry_contracts_path) @@ -98,3 +133,14 @@ async fn accept_ownership( spinner.finish(); Ok(()) } + +async fn update_token_multiplier_setter( + shell: &Shell, + governor: Option, + mut forge: ForgeScript, +) -> anyhow::Result<()> { + forge = fill_forge_private_key(forge, governor)?; + check_the_balance(&forge).await?; + forge.run(shell)?; + Ok(()) +} diff --git a/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs b/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs index b3b43c75c36a..69a2f2d940f1 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/chain/init.rs @@ -19,7 +19,7 @@ use types::{BaseToken, L1Network, WalletCreation}; use xshell::Shell; use crate::{ - accept_ownership::accept_admin, + accept_ownership::{accept_admin, set_token_multiplier_setter}, commands::chain::{ args::init::{InitArgs, InitArgsFinal}, deploy_l2_contracts, deploy_paymaster, @@ -30,6 +30,7 @@ use crate::{ msg_initializing_chain, MSG_ACCEPTING_ADMIN_SPINNER, MSG_CHAIN_INITIALIZED, MSG_CHAIN_NOT_FOUND_ERR, MSG_DISTRIBUTING_ETH_SPINNER, MSG_GENESIS_DATABASE_ERR, MSG_MINT_BASE_TOKEN_SPINNER, MSG_REGISTERING_CHAIN_SPINNER, MSG_SELECTED_CONFIG, + MSG_UPDATING_TOKEN_MULTIPLIER_SETTER_SPINNER, }, utils::forge::{check_the_balance, fill_forge_private_key}, }; @@ -101,6 +102,23 @@ pub async fn init( .await?; spinner.finish(); + let spinner = Spinner::new(MSG_UPDATING_TOKEN_MULTIPLIER_SETTER_SPINNER); + set_token_multiplier_setter( + shell, + ecosystem_config, + chain_config.get_wallets_config()?.governor_private_key(), + contracts_config.l1.chain_admin_addr, + ecosystem_config + .get_wallets() + .unwrap() + .token_multiplier_setter + .address, + &init_args.forge_args.clone(), + init_args.l1_rpc_url.clone(), + ) + .await?; + spinner.finish(); + deploy_l2_contracts::deploy_l2_contracts( shell, chain_config, diff --git a/zk_toolbox/crates/zk_inception/src/messages.rs b/zk_toolbox/crates/zk_inception/src/messages.rs index 402ee0718e88..5a86260b16b6 100644 --- a/zk_toolbox/crates/zk_inception/src/messages.rs +++ b/zk_toolbox/crates/zk_inception/src/messages.rs @@ -75,6 +75,8 @@ pub(super) const MSG_DEPLOYING_ECOSYSTEM_CONTRACTS_SPINNER: &str = "Deploying ecosystem contracts..."; pub(super) const MSG_REGISTERING_CHAIN_SPINNER: &str = "Registering chain..."; pub(super) const MSG_ACCEPTING_ADMIN_SPINNER: &str = "Accepting admin..."; +pub(super) const MSG_UPDATING_TOKEN_MULTIPLIER_SETTER_SPINNER: &str = + "Updating token multiplier setter..."; pub(super) const MSG_RECREATE_ROCKS_DB_ERRROR: &str = "Failed to create rocks db path"; pub(super) const MSG_ERA_OBSERVABILITY_ALREADY_SETUP: &str = "Era observability already setup"; pub(super) const MSG_DOWNLOADING_ERA_OBSERVABILITY_SPINNER: &str =