From 7bea008b172a8fe1a4bfcf6bed4b2a0dcad4003d Mon Sep 17 00:00:00 2001 From: Vitaliy Bezzubchenko Date: Mon, 10 Nov 2025 12:59:45 -0800 Subject: [PATCH 1/4] Fix: market deserialization error for interest and fees fileds. --- common/src/accumulator.rs | 11 +++++++++++ common/src/borrow.rs | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/common/src/accumulator.rs b/common/src/accumulator.rs index c3b42f46..aeb73eab 100644 --- a/common/src/accumulator.rs +++ b/common/src/accumulator.rs @@ -13,6 +13,17 @@ pub struct Accumulator { pub pending_estimate: FungibleAssetAmount, } +impl Default for Accumulator { + fn default() -> Self { + Self { + total: 0.into(), + fraction_as_u128_dividend: U128(0), + next_snapshot_index: 0, + pending_estimate: 0.into(), + } + } +} + impl Accumulator { pub fn new(next_snapshot_index: u32) -> Self { Self { diff --git a/common/src/borrow.rs b/common/src/borrow.rs index 3a2227a2..6a52bd30 100644 --- a/common/src/borrow.rs +++ b/common/src/borrow.rs @@ -60,13 +60,17 @@ pub enum LiquidationReason { pub struct BorrowPosition { pub started_at_block_timestamp_ms: Option, pub collateral_asset_deposit: CollateralAssetAmount, + #[serde(default)] borrow_asset_principal: BorrowAssetAmount, + #[serde(default)] pub interest: Accumulator, + #[serde(default)] pub fees: BorrowAssetAmount, #[serde(default)] borrow_asset_in_flight: BorrowAssetAmount, #[serde(default)] collateral_asset_in_flight: CollateralAssetAmount, + #[serde(default)] pub liquidation_lock: CollateralAssetAmount, } From 1f38e4320310f9581f5b8e0af5505953fd7dd6e0 Mon Sep 17 00:00:00 2001 From: Vitaliy Bezzubchenko Date: Mon, 10 Nov 2025 13:07:41 -0800 Subject: [PATCH 2/4] Add test --- common/src/accumulator.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/src/accumulator.rs b/common/src/accumulator.rs index aeb73eab..56b9a520 100644 --- a/common/src/accumulator.rs +++ b/common/src/accumulator.rs @@ -95,6 +95,14 @@ impl AccumulationRecord { mod tests { use super::*; + #[test] + fn default_accumulator() { + let a = Accumulator::::default(); + + assert_eq!(a.get_total(), 0.into()); + assert_eq!(a.get_next_snapshot_index(), 0); + } + #[test] fn fraction() { let mut a = Accumulator::::new(1); From 3712b00ed4436858a2b41dba9496db04568eae6d Mon Sep 17 00:00:00 2001 From: Vitaliy Bezzubchenko Date: Mon, 10 Nov 2025 13:09:27 -0800 Subject: [PATCH 3/4] Add test --- common/src/accumulator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/accumulator.rs b/common/src/accumulator.rs index 56b9a520..c42dbbe1 100644 --- a/common/src/accumulator.rs +++ b/common/src/accumulator.rs @@ -98,7 +98,7 @@ mod tests { #[test] fn default_accumulator() { let a = Accumulator::::default(); - + assert_eq!(a.get_total(), 0.into()); assert_eq!(a.get_next_snapshot_index(), 0); } From a21a45ad4c4b32de9849e9b0591c441681993548 Mon Sep 17 00:00:00 2001 From: Vitaliy Bezzubchenko Date: Tue, 11 Nov 2025 09:47:12 -0800 Subject: [PATCH 4/4] Add alias for interest instead of default --- common/src/accumulator.rs | 19 ------ common/src/borrow.rs | 121 +++++++++++++++++++++++++++++++++++++- 2 files changed, 119 insertions(+), 21 deletions(-) diff --git a/common/src/accumulator.rs b/common/src/accumulator.rs index c42dbbe1..c3b42f46 100644 --- a/common/src/accumulator.rs +++ b/common/src/accumulator.rs @@ -13,17 +13,6 @@ pub struct Accumulator { pub pending_estimate: FungibleAssetAmount, } -impl Default for Accumulator { - fn default() -> Self { - Self { - total: 0.into(), - fraction_as_u128_dividend: U128(0), - next_snapshot_index: 0, - pending_estimate: 0.into(), - } - } -} - impl Accumulator { pub fn new(next_snapshot_index: u32) -> Self { Self { @@ -95,14 +84,6 @@ impl AccumulationRecord { mod tests { use super::*; - #[test] - fn default_accumulator() { - let a = Accumulator::::default(); - - assert_eq!(a.get_total(), 0.into()); - assert_eq!(a.get_next_snapshot_index(), 0); - } - #[test] fn fraction() { let mut a = Accumulator::::new(1); diff --git a/common/src/borrow.rs b/common/src/borrow.rs index 6a52bd30..852f3f26 100644 --- a/common/src/borrow.rs +++ b/common/src/borrow.rs @@ -60,9 +60,8 @@ pub enum LiquidationReason { pub struct BorrowPosition { pub started_at_block_timestamp_ms: Option, pub collateral_asset_deposit: CollateralAssetAmount, - #[serde(default)] borrow_asset_principal: BorrowAssetAmount, - #[serde(default)] + #[serde(alias = "borrow_asset_fees")] pub interest: Accumulator, #[serde(default)] pub fees: BorrowAssetAmount, @@ -815,3 +814,121 @@ impl<'a> BorrowPositionGuard<'a> { .emit(); } } + +#[cfg(test)] +mod tests { + use super::*; + use near_sdk::serde_json; + + #[test] + fn test_borrow_position_deserialize_new_format() { + // New market format with interest field + let json = r#"{ + "started_at_block_timestamp_ms": "1699564800000", + "collateral_asset_deposit": "1000000000000000000000000", + "borrow_asset_principal": "100000000", + "interest": { + "total": "0", + "fraction_as_u128_dividend": "0", + "next_snapshot_index": 42, + "pending_estimate": "0" + }, + "fees": "500000", + "borrow_asset_in_flight": "50000000", + "collateral_asset_in_flight": "0", + "liquidation_lock": "0" + }"#; + + let position: BorrowPosition = + serde_json::from_str(json).expect("Failed to deserialize new format"); + assert_eq!(position.fees, BorrowAssetAmount::new(500_000)); + assert_eq!( + position.get_borrow_asset_principal(), + BorrowAssetAmount::new(50_000_000 + 100_000_000) + ); + } + + #[test] + fn test_borrow_position_deserialize_old_format_with_borrow_asset_fees() { + // Old market format with borrow_asset_fees instead of interest + let json = r#"{ + "started_at_block_timestamp_ms": "1699564800000", + "collateral_asset_deposit": "1000000000000000000000000", + "borrow_asset_principal": "100000000", + "borrow_asset_fees": { + "total": "0", + "fraction_as_u128_dividend": "0", + "next_snapshot_index": 42, + "pending_estimate": "0" + }, + "fees": "500000", + "borrow_asset_in_flight": "0", + "collateral_asset_in_flight": "0", + "liquidation_lock": "0" + }"#; + + let position: BorrowPosition = + serde_json::from_str(json).expect("Failed to deserialize old format"); + assert_eq!(position.fees, BorrowAssetAmount::new(500_000)); + assert_eq!( + position.get_borrow_asset_principal(), + BorrowAssetAmount::new(100_000_000) + ); + } + + #[test] + fn test_borrow_position_deserialize_mixed_old_new_format() { + // Mixed format: old field name for interest (borrow_asset_fees), new field names for others + let json = r#"{ + "started_at_block_timestamp_ms": "1699564800000", + "collateral_asset_deposit": "1000000000000000000000000", + "borrow_asset_principal": "100000000", + "borrow_asset_fees": { + "total": "0", + "fraction_as_u128_dividend": "0", + "next_snapshot_index": 42, + "pending_estimate": "0" + }, + "fees": "500000", + "borrow_asset_in_flight": "0", + "collateral_asset_in_flight": "0", + "liquidation_lock": "0" + }"#; + + let position: BorrowPosition = + serde_json::from_str(json).expect("Failed to deserialize mixed format"); + assert_eq!(position.fees, BorrowAssetAmount::new(500_000)); + assert_eq!( + position.get_borrow_asset_principal(), + BorrowAssetAmount::new(100_000_000) + ); + assert_eq!( + position.get_total_collateral_amount(), + CollateralAssetAmount::new(1_000_000_000_000_000_000_000_000) + ); + } + + #[test] + fn test_borrow_position_deserialize_defaults() { + // Minimal JSON with only required fields, others should use defaults + let json = r#"{ + "collateral_asset_deposit": "1000000000000000000000000", + "borrow_asset_principal": "100000000", + "interest": { + "total": "0", + "fraction_as_u128_dividend": "0", + "next_snapshot_index": 42, + "pending_estimate": "0" + } + }"#; + + let position: BorrowPosition = + serde_json::from_str(json).expect("Failed to deserialize with defaults"); + assert_eq!(position.started_at_block_timestamp_ms, None); + assert_eq!(position.fees, BorrowAssetAmount::new(0)); + assert_eq!( + position.get_total_collateral_amount(), + CollateralAssetAmount::new(1_000_000_000_000_000_000_000_000) + ); + } +}