Skip to content

Commit

Permalink
feat: base token integration tests (#2509)
Browse files Browse the repository at this point in the history
## What ❔

- Introduce 2 new tests for base token:
    - gas calculation
    - proper application of dynamic changes to base token ratio
- Improve forced price client fluctuations implementation to limit
difference between consecutive values.

## Why ❔

- We want better integration test coverage of custom base token

## Checklist

<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->

- [x] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [x] Tests for the changes have been added / updated.
- [x] Documentation comments have been added / updated.
- [x] Code has been formatted via `zk fmt` and `zk lint`.

---------

Co-authored-by: Ivan Schasny <[email protected]>
  • Loading branch information
cytadela8 and ischasny authored Oct 30, 2024
1 parent 767c5bc commit 8db7e93
Show file tree
Hide file tree
Showing 16 changed files with 388 additions and 174 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci-core-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,8 @@ jobs:
--wallet-creation localhost \
--l1-batch-commit-data-generator-mode rollup \
--base-token-address ${{ env.CUSTOM_TOKEN_ADDRESS }} \
--base-token-price-nominator 3 \
--base-token-price-denominator 2 \
--base-token-price-nominator 314 \
--base-token-price-denominator 1000 \
--set-as-default false \
--ignore-prerequisites
Expand Down Expand Up @@ -332,8 +332,8 @@ jobs:
--wallet-creation localhost \
--l1-batch-commit-data-generator-mode validium \
--base-token-address ${{ env.CUSTOM_TOKEN_ADDRESS }} \
--base-token-price-nominator 3 \
--base-token-price-denominator 2 \
--base-token-price-nominator 314 \
--base-token-price-denominator 1000 \
--set-as-default false \
--ignore-prerequisites
Expand Down
17 changes: 13 additions & 4 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,21 @@ use serde::Deserialize;

pub const DEFAULT_TIMEOUT_MS: u64 = 10_000;

pub const DEFAULT_FORCED_NEXT_VALUE_FLUCTUATION: u32 = 3;

#[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.
/// Forced fluctuation. It defines how much percent the ratio should fluctuate from its forced
/// value. If it's None or 0, then the ForcedPriceClient will return the same quote every time
/// it's called. Otherwise, ForcedPriceClient will return quote with numerator +/- fluctuation %.
pub fluctuation: Option<u32>,
/// In order to smooth out fluctuation, consecutive values returned by forced client will not
/// differ more than next_value_fluctuation percent. If it's None, a default of 3% will be applied.
#[serde(default = "ExternalPriceApiClientConfig::default_forced_next_value_fluctuation")]
pub next_value_fluctuation: u32,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
Expand All @@ -31,6 +36,10 @@ impl ExternalPriceApiClientConfig {
DEFAULT_TIMEOUT_MS
}

fn default_forced_next_value_fluctuation() -> u32 {
DEFAULT_FORCED_NEXT_VALUE_FLUCTUATION
}

pub fn client_timeout(&self) -> Duration {
Duration::from_millis(self.client_timeout_ms)
}
Expand Down
1 change: 1 addition & 0 deletions core/lib/config/src/testonly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,7 @@ impl Distribution<configs::external_price_api_client::ExternalPriceApiClientConf
numerator: self.sample(rng),
denominator: self.sample(rng),
fluctuation: self.sample(rng),
next_value_fluctuation: self.sample(rng),
}),
}
}
Expand Down
2 changes: 2 additions & 0 deletions core/lib/env_config/src/external_price_api_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ mod tests {
numerator: Some(100),
denominator: Some(1),
fluctuation: Some(10),
next_value_fluctuation: 1,
}),
}
}
Expand All @@ -57,6 +58,7 @@ mod tests {
EXTERNAL_PRICE_API_CLIENT_FORCED_NUMERATOR=100
EXTERNAL_PRICE_API_CLIENT_FORCED_DENOMINATOR=1
EXTERNAL_PRICE_API_CLIENT_FORCED_FLUCTUATION=10
EXTERNAL_PRICE_API_CLIENT_FORCED_NEXT_VALUE_FLUCTUATION=1
"#;
lock.set_env(config);

Expand Down
84 changes: 59 additions & 25 deletions core/lib/external_price_api/src/forced_price_client.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
use std::num::NonZeroU64;
use std::{
cmp::{max, min},
num::NonZeroU64,
};

use async_trait::async_trait;
use rand::Rng;
use tokio::sync::Mutex;
use zksync_config::configs::ExternalPriceApiClientConfig;
use zksync_types::{base_token_ratio::BaseTokenAPIRatio, Address};

use crate::PriceAPIClient;

// Struct for a forced price "client" (conversion ratio is always a configured "forced" ratio).
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct ForcedPriceClient {
ratio: BaseTokenAPIRatio,
previous_numerator: Mutex<NonZeroU64>,
fluctuation: Option<u32>,
next_value_fluctuation: u32,
}

impl ForcedPriceClient {
Expand All @@ -29,42 +35,70 @@ impl ForcedPriceClient {
let fluctuation = forced_price_client_config
.fluctuation
.map(|x| x.clamp(0, 100));
let next_value_fluctuation = forced_price_client_config
.next_value_fluctuation
.clamp(0, 100);

Self {
ratio: BaseTokenAPIRatio {
let ratio = if numerator < 100 && fluctuation.is_some_and(|f| f > 0) {
// If numerator is too small we need to multiply by 100 to make sure fluctuations can be applied
BaseTokenAPIRatio {
numerator: NonZeroU64::new(numerator * 100).unwrap(),
denominator: NonZeroU64::new(denominator * 100).unwrap(),
ratio_timestamp: chrono::Utc::now(),
}
} else {
BaseTokenAPIRatio {
numerator: NonZeroU64::new(numerator).unwrap(),
denominator: NonZeroU64::new(denominator).unwrap(),
ratio_timestamp: chrono::Utc::now(),
},
}
};

Self {
ratio,
previous_numerator: Mutex::new(NonZeroU64::new(numerator).unwrap()),
fluctuation,
next_value_fluctuation,
}
}
}

#[async_trait]
impl PriceAPIClient for ForcedPriceClient {
// Returns a ratio which is 10% higher or lower than the configured forced ratio.
/// Returns the configured ratio with fluctuation applied if enabled
async fn fetch_ratio(&self, _token_address: Address) -> anyhow::Result<BaseTokenAPIRatio> {
if let Some(x) = self.fluctuation {
if x != 0 {
let mut rng = rand::thread_rng();

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);
if let Some(fluctation) = self.fluctuation {
let mut previous_numerator = self.previous_numerator.lock().await;
let mut rng = rand::thread_rng();
let numerator_range = (
max(
(self.ratio.numerator.get() as f64 * (1.0 - (fluctation as f64 / 100.0)))
.round() as u64,
(previous_numerator.get() as f64
* (1.0 - (self.next_value_fluctuation as f64 / 100.0)))
.round() as u64,
),
min(
(self.ratio.numerator.get() as f64 * (1.0 + (fluctation as f64 / 100.0)))
.round() as u64,
(previous_numerator.get() as f64
* (1.0 + (self.next_value_fluctuation as f64 / 100.0)))
.round() as u64,
),
);

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(),
});
}
let new_numerator =
NonZeroU64::new(rng.gen_range(numerator_range.0..=numerator_range.1))
.unwrap_or(self.ratio.numerator);
let adjusted_ratio = BaseTokenAPIRatio {
numerator: new_numerator,
denominator: self.ratio.denominator,
ratio_timestamp: chrono::Utc::now(),
};
*previous_numerator = new_numerator;
Ok(adjusted_ratio)
} else {
Ok(self.ratio)
}
Ok(self.ratio)
}
}
5 changes: 5 additions & 0 deletions core/lib/protobuf_config/src/external_price_api_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ impl ProtoRepr for proto::ExternalPriceApiClient {
numerator: self.forced_numerator,
denominator: self.forced_denominator,
fluctuation: self.forced_fluctuation,
next_value_fluctuation: self.forced_next_value_fluctuation.unwrap_or(
configs::external_price_api_client::DEFAULT_FORCED_NEXT_VALUE_FLUCTUATION,
),
}),
},
)
Expand All @@ -26,6 +29,7 @@ impl ProtoRepr for proto::ExternalPriceApiClient {
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);
let next_value_fluctuation = this.forced.as_ref().map(|x| x.next_value_fluctuation);

Self {
source: Some(this.source.clone()),
Expand All @@ -35,6 +39,7 @@ impl ProtoRepr for proto::ExternalPriceApiClient {
forced_numerator: numerator,
forced_denominator: denominator,
forced_fluctuation: fluctuation,
forced_next_value_fluctuation: next_value_fluctuation,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ message ExternalPriceApiClient {
optional uint64 forced_numerator = 5;
optional uint64 forced_denominator = 6;
optional uint32 forced_fluctuation = 7;
optional uint32 forced_next_value_fluctuation = 8;
}
10 changes: 8 additions & 2 deletions core/node/base_token_adjuster/src/base_token_l1_behaviour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,16 @@ impl BaseTokenL1Behaviour {
if receipt.status == Some(1.into()) {
return Ok(receipt.gas_used);
}
let reason = (*l1_params.eth_client)
.as_ref()
.failure_reason(hash)
.await
.context("failed getting failure reason of `setTokenMultiplier` transaction")?;
return Err(anyhow::Error::msg(format!(
"`setTokenMultiplier` transaction {:?} failed with status {:?}",
"`setTokenMultiplier` transaction {:?} failed with status {:?}, reason: {:?}",
hex::encode(hash),
receipt.status
receipt.status,
reason
)));
} else {
tokio::time::sleep(sleep_duration).await;
Expand Down
3 changes: 2 additions & 1 deletion core/tests/ts-integration/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ async function loadTestEnvironmentFromFile(fileConfig: FileConfig): Promise<Test
baseTokenAddress: contracts.l1.base_token_addr
});

l2Node = await mainNodeSpawner.spawnMainNode();
await mainNodeSpawner.killAndSpawnMainNode();
l2Node = mainNodeSpawner.mainNode;
}

const l2Provider = new zksync.Provider(l2NodeUrl);
Expand Down
Loading

0 comments on commit 8db7e93

Please sign in to comment.