From 1e8fb09995d90624cc237aef855644f2a3a63f9a Mon Sep 17 00:00:00 2001 From: Nikolaos Dymitriadis Date: Tue, 9 Sep 2025 12:32:31 +0200 Subject: [PATCH 1/8] saturating_add for BlockNumber --- toolkit/data-sources/db-sync/src/block/mod.rs | 2 +- toolkit/data-sources/db-sync/src/governed_map/mod.rs | 3 +-- toolkit/sidechain/domain/src/lib.rs | 7 +++++++ toolkit/utils/db-sync-sqlx/src/lib.rs | 8 +++++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/toolkit/data-sources/db-sync/src/block/mod.rs b/toolkit/data-sources/db-sync/src/block/mod.rs index ca517bf491..709659eb54 100644 --- a/toolkit/data-sources/db-sync/src/block/mod.rs +++ b/toolkit/data-sources/db-sync/src/block/mod.rs @@ -255,7 +255,7 @@ impl BlockDataSourceImpl { Ok(block .zip(latest_block) .filter(|(block, latest_block)| { - block.block_no.0 + self.security_parameter <= latest_block.block_no.0 + block.block_no.saturating_add(self.security_parameter) <= latest_block.block_no && self.is_block_time_valid(block, reference_timestamp) }) .map(|(block, _)| block)) diff --git a/toolkit/data-sources/db-sync/src/governed_map/mod.rs b/toolkit/data-sources/db-sync/src/governed_map/mod.rs index 193af54aeb..4e837fcaa5 100644 --- a/toolkit/data-sources/db-sync/src/governed_map/mod.rs +++ b/toolkit/data-sources/db-sync/src/governed_map/mod.rs @@ -216,8 +216,7 @@ impl GovernedMapDataSource for GovernedMapDataSourceCachedImpl { Some(block) => BlockNumber(block.number.0), None => up_to_block_number, }; - let since_block_plus = - BlockNumber(since_block_number.unwrap_or(BlockNumber(0)).0 + self.cache_size as u32); + let since_block_plus = since_block_number.unwrap_or_default().saturating_add(self.cache_size); let max_search_block = min(latest_stable_block, max(up_to_block_number, since_block_plus)); let changes = self diff --git a/toolkit/sidechain/domain/src/lib.rs b/toolkit/sidechain/domain/src/lib.rs index c09183b709..064134f6c2 100644 --- a/toolkit/sidechain/domain/src/lib.rs +++ b/toolkit/sidechain/domain/src/lib.rs @@ -162,6 +162,13 @@ impl Display for McBlockNumber { } } +impl McBlockNumber { + /// Adds `rhs` to the block number without overflow + pub fn saturating_add>(self, rhs: Rhs) -> Self { + Self(self.0.saturating_add(rhs.into())) + } +} + #[derive( Default, Debug, diff --git a/toolkit/utils/db-sync-sqlx/src/lib.rs b/toolkit/utils/db-sync-sqlx/src/lib.rs index 1cc2cfcf4d..3a4dc1c7ff 100644 --- a/toolkit/utils/db-sync-sqlx/src/lib.rs +++ b/toolkit/utils/db-sync-sqlx/src/lib.rs @@ -82,10 +82,16 @@ macro_rules! sqlx_implementations_for_wrapper { } /// Cardano block number -#[derive(Debug, Copy, Ord, PartialOrd, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Ord, PartialOrd, Clone, PartialEq, Eq, Default)] pub struct BlockNumber(pub u32); sqlx_implementations_for_wrapper!(i32, "INT4", BlockNumber, McBlockNumber); +impl BlockNumber { + pub fn saturating_add>(self, rhs: Rhs) -> Self { + Self(self.0.saturating_add(rhs.into())) + } +} + /// Cardano epoch number #[derive(Debug, Copy, Clone, PartialEq)] pub struct EpochNumber(pub u32); From 3a84a474b26af7e80aba5d369beaef729ce0cc9f Mon Sep 17 00:00:00 2001 From: Nikolaos Dymitriadis Date: Tue, 9 Sep 2025 12:07:02 +0200 Subject: [PATCH 2/8] Use Ord for slot and block comparisons --- toolkit/data-sources/db-sync/src/block/mod.rs | 4 ++-- toolkit/data-sources/db-sync/src/governed_map/mod.rs | 8 ++++---- toolkit/sidechain/domain/src/lib.rs | 2 ++ .../sidechain-block-search/src/impl_compare_strategy.rs | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/toolkit/data-sources/db-sync/src/block/mod.rs b/toolkit/data-sources/db-sync/src/block/mod.rs index 709659eb54..71621d66fe 100644 --- a/toolkit/data-sources/db-sync/src/block/mod.rs +++ b/toolkit/data-sources/db-sync/src/block/mod.rs @@ -277,8 +277,8 @@ impl BlockDataSourceImpl { let latest_block_num = latest_block.block_no.0; let stable_block_num = latest_block_num.saturating_sub(self.security_parameter); - let to_block_no = BlockNumber(from_block_no.0.saturating_add(size).min(stable_block_num)); - let blocks = if to_block_no.0 > from_block_no.0 { + let to_block_no = from_block_no.saturating_add(size).min(BlockNumber(stable_block_num)); + let blocks = if to_block_no > from_block_no { db_model::get_blocks_by_numbers(&self.pool, from_block_no, to_block_no).await? } else { vec![from_block.clone()] diff --git a/toolkit/data-sources/db-sync/src/governed_map/mod.rs b/toolkit/data-sources/db-sync/src/governed_map/mod.rs index 4e837fcaa5..726c82fceb 100644 --- a/toolkit/data-sources/db-sync/src/governed_map/mod.rs +++ b/toolkit/data-sources/db-sync/src/governed_map/mod.rs @@ -306,8 +306,8 @@ fn filter_changes_in_range( changes .into_iter() .filter(|change| { - change.block_no.0 <= up_to_block.0 - && since_block.map(|b| change.block_no.0 > b.0).unwrap_or(true) + change.block_no <= up_to_block + && since_block.map(|b| change.block_no > b).unwrap_or(true) }) .map(|change| (change.key, change.value)) .collect() @@ -326,8 +326,8 @@ impl Cache { return None; }; - if highest_block_number.0 < up_to_block.0 - || since_block.map(|b| b.0 < lowest_block_number.0).unwrap_or(false) + if highest_block_number < up_to_block + || since_block.map(|b| b < lowest_block_number).unwrap_or(false) { return None; } diff --git a/toolkit/sidechain/domain/src/lib.rs b/toolkit/sidechain/domain/src/lib.rs index 064134f6c2..5a0ad3b288 100644 --- a/toolkit/sidechain/domain/src/lib.rs +++ b/toolkit/sidechain/domain/src/lib.rs @@ -206,6 +206,8 @@ impl Display for McSlotNumber { TypeInfo, Hash, MaxEncodedLen, + PartialOrd, + Ord, )] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize, FromStr))] /// Partner Chain slot number diff --git a/toolkit/sidechain/sidechain-block-search/src/impl_compare_strategy.rs b/toolkit/sidechain/sidechain-block-search/src/impl_compare_strategy.rs index 7dd6c8c690..b8f5cda527 100644 --- a/toolkit/sidechain/sidechain-block-search/src/impl_compare_strategy.rs +++ b/toolkit/sidechain/sidechain-block-search/src/impl_compare_strategy.rs @@ -58,9 +58,9 @@ where block_info: &BlockInfo, ) -> Result { let slot_of_block = block_info.get_slot_of_block(block)?; - let ordering = if slot_of_block.0 < self.slot_range.start.0 { + let ordering = if slot_of_block < self.slot_range.start { Ordering::Less - } else if slot_of_block.0 >= self.slot_range.end.0 { + } else if slot_of_block >= self.slot_range.end { Ordering::Greater } else { Ordering::Equal From c9594e7529480869efde9e04cce5775ddd8fc296 Mon Sep 17 00:00:00 2001 From: Nikolaos Dymitriadis Date: Tue, 9 Sep 2025 12:23:39 +0200 Subject: [PATCH 3/8] impl Sub for NativeTokenAmount --- toolkit/data-sources/db-sync/src/bridge/mod.rs | 2 +- toolkit/data-sources/db-sync/src/db_model.rs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/toolkit/data-sources/db-sync/src/bridge/mod.rs b/toolkit/data-sources/db-sync/src/bridge/mod.rs index b5b108c267..a321a1b4bf 100644 --- a/toolkit/data-sources/db-sync/src/bridge/mod.rs +++ b/toolkit/data-sources/db-sync/src/bridge/mod.rs @@ -116,7 +116,7 @@ fn utxo_to_transfer( where RecipientAddress: for<'a> TryFrom<&'a [u8]>, { - let token_delta = (utxo.tokens_out.0 as i128) - (utxo.tokens_in.0 as i128); + let token_delta = utxo.tokens_out.0 - utxo.tokens_in.0; if token_delta <= 0 { return None; diff --git a/toolkit/data-sources/db-sync/src/db_model.rs b/toolkit/data-sources/db-sync/src/db_model.rs index 0f225ebc64..fadc9e1ad9 100644 --- a/toolkit/data-sources/db-sync/src/db_model.rs +++ b/toolkit/data-sources/db-sync/src/db_model.rs @@ -12,6 +12,7 @@ use sidechain_domain::{ use sqlx::{ Decode, PgPool, Pool, Postgres, database::Database, error::BoxDynError, postgres::PgTypeInfo, }; +use std::ops::Sub; use std::{cell::OnceCell, str::FromStr, sync::Arc}; use tokio::sync::Mutex; @@ -197,6 +198,13 @@ impl From for sidechain_domain::NativeTokenAmount { } } +impl Sub for NativeTokenAmount { + type Output = i128; + fn sub(self, rhs: NativeTokenAmount) -> Self::Output { + return (self.0 as i128) - (rhs.0 as i128); + } +} + impl sqlx::Type for NativeTokenAmount { fn type_info() -> ::TypeInfo { PgTypeInfo::with_name("NUMERIC") From 549fe42807adce56e61f1c39d630f235f0e3e9c8 Mon Sep 17 00:00:00 2001 From: Nikolaos Dymitriadis Date: Tue, 9 Sep 2025 12:43:04 +0200 Subject: [PATCH 4/8] saturating_sub for McBlockNumber and BlockNumber --- toolkit/data-sources/db-sync/src/block/mod.rs | 7 +++---- toolkit/sidechain/domain/src/lib.rs | 5 +++++ toolkit/utils/db-sync-sqlx/src/lib.rs | 4 ++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/toolkit/data-sources/db-sync/src/block/mod.rs b/toolkit/data-sources/db-sync/src/block/mod.rs index 71621d66fe..c4b853e5f6 100644 --- a/toolkit/data-sources/db-sync/src/block/mod.rs +++ b/toolkit/data-sources/db-sync/src/block/mod.rs @@ -75,7 +75,7 @@ impl BlockDataSourceImpl { let reference_timestamp = BlockDataSourceImpl::timestamp_to_db_type(reference_timestamp)?; let latest = self.get_latest_block_info().await?; let offset = self.security_parameter + self.block_stability_margin; - let stable = BlockNumber(latest.number.0.saturating_sub(offset)); + let stable = latest.number.saturating_sub(offset).into(); let block = self.get_latest_block(stable, reference_timestamp).await?; Ok(block.map(From::from)) } @@ -274,10 +274,9 @@ impl BlockDataSourceImpl { .ok_or(InternalDataSourceError( "No latest block when filling the caches.".to_string(), ))?; - let latest_block_num = latest_block.block_no.0; - let stable_block_num = latest_block_num.saturating_sub(self.security_parameter); + let stable_block_num = latest_block.block_no.saturating_sub(self.security_parameter); - let to_block_no = from_block_no.saturating_add(size).min(BlockNumber(stable_block_num)); + let to_block_no = from_block_no.saturating_add(size).min(stable_block_num); let blocks = if to_block_no > from_block_no { db_model::get_blocks_by_numbers(&self.pool, from_block_no, to_block_no).await? } else { diff --git a/toolkit/sidechain/domain/src/lib.rs b/toolkit/sidechain/domain/src/lib.rs index 5a0ad3b288..177d1cd177 100644 --- a/toolkit/sidechain/domain/src/lib.rs +++ b/toolkit/sidechain/domain/src/lib.rs @@ -167,6 +167,11 @@ impl McBlockNumber { pub fn saturating_add>(self, rhs: Rhs) -> Self { Self(self.0.saturating_add(rhs.into())) } + + /// Subtracts `rhs` from the block number without overflow + pub fn saturating_sub>(self, rhs: Rhs) -> Self { + Self(self.0.saturating_sub(rhs.into())) + } } #[derive( diff --git a/toolkit/utils/db-sync-sqlx/src/lib.rs b/toolkit/utils/db-sync-sqlx/src/lib.rs index 3a4dc1c7ff..41f3dd881a 100644 --- a/toolkit/utils/db-sync-sqlx/src/lib.rs +++ b/toolkit/utils/db-sync-sqlx/src/lib.rs @@ -90,6 +90,10 @@ impl BlockNumber { pub fn saturating_add>(self, rhs: Rhs) -> Self { Self(self.0.saturating_add(rhs.into())) } + + pub fn saturating_sub>(self, rhs: Rhs) -> Self { + Self(self.0.saturating_sub(rhs.into())) + } } /// Cardano epoch number From 86e8e1918b0c57aa8f63bfe96bb41e7697e48182 Mon Sep 17 00:00:00 2001 From: Nikolaos Dymitriadis Date: Wed, 10 Sep 2025 10:31:29 +0200 Subject: [PATCH 5/8] use checked_sub for NativeTokenAmount --- toolkit/data-sources/db-sync/src/bridge/mod.rs | 2 +- toolkit/data-sources/db-sync/src/db_model.rs | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/toolkit/data-sources/db-sync/src/bridge/mod.rs b/toolkit/data-sources/db-sync/src/bridge/mod.rs index a321a1b4bf..14e41c100c 100644 --- a/toolkit/data-sources/db-sync/src/bridge/mod.rs +++ b/toolkit/data-sources/db-sync/src/bridge/mod.rs @@ -116,7 +116,7 @@ fn utxo_to_transfer( where RecipientAddress: for<'a> TryFrom<&'a [u8]>, { - let token_delta = utxo.tokens_out.0 - utxo.tokens_in.0; + let token_delta = utxo.tokens_out.checked_sub(utxo.tokens_in)?; if token_delta <= 0 { return None; diff --git a/toolkit/data-sources/db-sync/src/db_model.rs b/toolkit/data-sources/db-sync/src/db_model.rs index fadc9e1ad9..4864710eb3 100644 --- a/toolkit/data-sources/db-sync/src/db_model.rs +++ b/toolkit/data-sources/db-sync/src/db_model.rs @@ -12,7 +12,6 @@ use sidechain_domain::{ use sqlx::{ Decode, PgPool, Pool, Postgres, database::Database, error::BoxDynError, postgres::PgTypeInfo, }; -use std::ops::Sub; use std::{cell::OnceCell, str::FromStr, sync::Arc}; use tokio::sync::Mutex; @@ -190,7 +189,7 @@ pub(crate) struct DatumChangeOutput { pub action: GovernedMapAction, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Copy)] pub(crate) struct NativeTokenAmount(pub u128); impl From for sidechain_domain::NativeTokenAmount { fn from(value: NativeTokenAmount) -> Self { @@ -198,10 +197,9 @@ impl From for sidechain_domain::NativeTokenAmount { } } -impl Sub for NativeTokenAmount { - type Output = i128; - fn sub(self, rhs: NativeTokenAmount) -> Self::Output { - return (self.0 as i128) - (rhs.0 as i128); +impl NativeTokenAmount { + pub fn checked_sub(self, rhs: NativeTokenAmount) -> Option { + i128::try_from(self.0).ok()?.checked_sub(i128::try_from(rhs.0).ok()?) } } From a861dead9cf530ed12e19bd4e0114858a42bf874 Mon Sep 17 00:00:00 2001 From: Nikolaos Dymitriadis Date: Wed, 10 Sep 2025 10:39:26 +0200 Subject: [PATCH 6/8] impl Copy for DelegatorStakeAmount --- toolkit/sidechain/domain/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/sidechain/domain/src/lib.rs b/toolkit/sidechain/domain/src/lib.rs index 177d1cd177..346e98a4a0 100644 --- a/toolkit/sidechain/domain/src/lib.rs +++ b/toolkit/sidechain/domain/src/lib.rs @@ -1472,7 +1472,7 @@ impl alloc::fmt::Debug for DelegatorKey { } /// Amount of Lovelace staked by a Cardano delegator to a single stake pool -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] pub struct DelegatorStakeAmount(pub u64); impl> From for DelegatorStakeAmount { From d6833f24d192a330a45988a1cce0ddd876627e4b Mon Sep 17 00:00:00 2001 From: Nikolaos Dymitriadis Date: Wed, 10 Sep 2025 10:39:37 +0200 Subject: [PATCH 7/8] clippy fix --- toolkit/smart-contracts/offchain/tests/integration_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/smart-contracts/offchain/tests/integration_tests.rs b/toolkit/smart-contracts/offchain/tests/integration_tests.rs index 2d0776ae05..5f9a2664e5 100644 --- a/toolkit/smart-contracts/offchain/tests/integration_tests.rs +++ b/toolkit/smart-contracts/offchain/tests/integration_tests.rs @@ -407,7 +407,7 @@ async fn governance_action_can_be_initiated_by_non_governance() { run_assemble_and_sign(tx_to_sign, &[GOVERNANCE_AUTHORITY_KEY], &client).await; } -async fn initialize<'a>(container: &ContainerAsync) -> OgmiosClients { +async fn initialize(container: &ContainerAsync) -> OgmiosClients { let ogmios_port = container.get_host_port_ipv4(1337).await.unwrap(); println!("Ogmios port: {}", ogmios_port); From f5f210f90b0fc3eb6f55b5f95b427f8df8035bf9 Mon Sep 17 00:00:00 2001 From: Nikolaos Dymitriadis Date: Thu, 11 Sep 2025 10:27:21 +0200 Subject: [PATCH 8/8] better checked sub --- toolkit/data-sources/db-sync/src/bridge/mod.rs | 6 +++--- toolkit/data-sources/db-sync/src/db_model.rs | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/toolkit/data-sources/db-sync/src/bridge/mod.rs b/toolkit/data-sources/db-sync/src/bridge/mod.rs index 14e41c100c..0670144baa 100644 --- a/toolkit/data-sources/db-sync/src/bridge/mod.rs +++ b/toolkit/data-sources/db-sync/src/bridge/mod.rs @@ -116,13 +116,13 @@ fn utxo_to_transfer( where RecipientAddress: for<'a> TryFrom<&'a [u8]>, { - let token_delta = utxo.tokens_out.checked_sub(utxo.tokens_in)?; + let token_delta = utxo.tokens_out.checked_sub_i128(utxo.tokens_in)?; - if token_delta <= 0 { + if token_delta.is_zero() { return None; } - let token_amount = token_delta as u64; + let token_amount = token_delta.0 as u64; let transfer = match TokenTransferDatum::try_from(utxo.datum.0.clone()) { Ok(TokenTransferDatum::V1(TokenTransferDatumV1::UserTransfer { receiver })) => { diff --git a/toolkit/data-sources/db-sync/src/db_model.rs b/toolkit/data-sources/db-sync/src/db_model.rs index 4864710eb3..8610693be1 100644 --- a/toolkit/data-sources/db-sync/src/db_model.rs +++ b/toolkit/data-sources/db-sync/src/db_model.rs @@ -198,8 +198,12 @@ impl From for sidechain_domain::NativeTokenAmount { } impl NativeTokenAmount { - pub fn checked_sub(self, rhs: NativeTokenAmount) -> Option { - i128::try_from(self.0).ok()?.checked_sub(i128::try_from(rhs.0).ok()?) + pub(crate) fn checked_sub_i128(self, rhs: NativeTokenAmount) -> Option { + self.0.checked_sub(rhs.0).map(NativeTokenAmount) + } + + pub(crate) fn is_zero(&self) -> bool { + self.0 == 0 } }