Skip to content

Commit

Permalink
feat: update base token rate on L1 (#2589)
Browse files Browse the repository at this point in the history
Currently base token to ETH ratio is always locked to 1:1 on the L1.
This PR changes that and adds functionality to update L1 numerator and
denominator as soon as they get updated on the L2. Updates are done by
calling `IChainAdmin.setTokenMultiplier` function. The function can only
be called by a new role - `tokenMultiplierSetter`.

This PR introduces the following changes:
* new wallet for `tokenMultiplierSetter`, provisioned with ETH on
network initialisation;
* updates to old and new tooling to fully support this new role;
* updates to `base_token_ratio_persister` to do the L1 updates;

What is not included:
* Conditional updates - to save gas cost if numerator / denominator
hasn't changed since the last time;
* Support for fluctuating numerator / denominator in EN - that will be
added in a separate PR.

[PR](https://github.com/matter-labs/era-contracts/pull/683/files) to
update tolling on the `era-contracts` side.
  • Loading branch information
ischasny authored Aug 19, 2024
1 parent f9ef00e commit f84aaaf
Show file tree
Hide file tree
Showing 42 changed files with 888 additions and 121 deletions.
5 changes: 4 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions core/bin/zksync_server/src/node_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -594,8 +594,14 @@ impl MainNodeBuilder {
fn add_base_token_ratio_persister_layer(mut self) -> anyhow::Result<Self> {
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)
}
Expand Down
142 changes: 132 additions & 10 deletions core/lib/config/src/configs/base_token_adjuster.rs
Original file line number Diff line number Diff line change
@@ -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<Option<K256PrivateKey>> {
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()
}
}
16 changes: 13 additions & 3 deletions core/lib/config/src/configs/external_price_api_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,26 @@ 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<u64>,
pub denominator: Option<u64>,
/// 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<u32>,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct ExternalPriceApiClientConfig {
pub source: String,
pub base_url: Option<String>,
pub api_key: Option<String>,
#[serde(default = "ExternalPriceApiClientConfig::default_timeout")]
pub client_timeout_ms: u64,
/// Forced conversion ratio. Only used with the ForcedPriceClient.
pub forced_numerator: Option<u64>,
pub forced_denominator: Option<u64>,
pub forced: Option<ForcedPriceClientConfig>,
}

impl ExternalPriceApiClientConfig {
Expand Down
9 changes: 9 additions & 0 deletions core/lib/config/src/configs/wallets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<EthSender>,
pub state_keeper: Option<StateKeeper>,
pub token_multiplier_setter: Option<TokenMultiplierSetter>,
}

impl Wallets {
Expand All @@ -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(),
}),
}
}
}
28 changes: 25 additions & 3 deletions core/lib/config/src/testonly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -895,11 +897,20 @@ impl Distribution<configs::wallets::EthSender> for EncodeDist {
}
}

impl Distribution<configs::wallets::TokenMultiplierSetter> for EncodeDist {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> configs::wallets::TokenMultiplierSetter {
configs::wallets::TokenMultiplierSetter {
wallet: self.sample(rng),
}
}
}

impl Distribution<configs::wallets::Wallets> for EncodeDist {
fn sample<R: Rng + ?Sized>(&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)),
}
}
}
Expand Down Expand Up @@ -1024,6 +1035,14 @@ impl Distribution<configs::base_token_adjuster::BaseTokenAdjusterConfig> 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),
}
}
}
Expand Down Expand Up @@ -1051,8 +1070,11 @@ impl Distribution<configs::external_price_api_client::ExternalPriceApiClientConf
base_url: self.sample(rng),
api_key: self.sample(rng),
client_timeout_ms: self.sample(rng),
forced_numerator: self.sample(rng),
forced_denominator: self.sample(rng),
forced: Some(ForcedPriceClientConfig {
numerator: self.sample(rng),
denominator: self.sample(rng),
fluctuation: self.sample(rng),
}),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions core/lib/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const DIAMOND_INIT_CONTRACT_FILE: (&str, &str) = (
);
const GOVERNANCE_CONTRACT_FILE: (&str, &str) = ("governance", "IGovernance.sol/IGovernance.json");
const CHAIN_ADMIN_CONTRACT_FILE: (&str, &str) = ("governance", "IChainAdmin.sol/IChainAdmin.json");

const MULTICALL3_CONTRACT_FILE: (&str, &str) = ("dev-contracts", "Multicall3.sol/Multicall3.json");
const VERIFIER_CONTRACT_FILE: (&str, &str) = ("state-transition", "Verifier.sol/Verifier.json");
const _IERC20_CONTRACT_FILE: &str =
Expand Down
Loading

0 comments on commit f84aaaf

Please sign in to comment.