Skip to content

Comments

Release 0.4.0: 6-Decimal Token Support and Dynamic Decimal Handling#5

Open
dacarva wants to merge 40 commits intodevelopfrom
001-erc20-decimals
Open

Release 0.4.0: 6-Decimal Token Support and Dynamic Decimal Handling#5
dacarva wants to merge 40 commits intodevelopfrom
001-erc20-decimals

Conversation

@dacarva
Copy link
Member

@dacarva dacarva commented Dec 4, 2025

Release 0.4.0: 6-Decimal Token Support and Dynamic Decimal Handling

Summary

This release introduces comprehensive support for 6-decimal ERC20 tokens (including USDT0) and implements dynamic decimal detection throughout the Tropykus SDK. The changes ensure accurate handling of tokens with varying decimal precisions while maintaining full backward compatibility with existing 18-decimal tokens.

Key Features

Added

  • 6-Decimal Token Support: Full support for 6-decimal ERC20 tokens (USDT0) with automatic decimal detection
  • Dynamic Decimal Detection: Automatic detection and caching of token and oracle decimals for improved performance
  • PriceOracle Enhancements: New detectOracleDecimals method for automatic oracle adapter decimal detection
  • Comprehensive Test Coverage: Integration tests for 6-decimal token operations (deposit, borrow, repay, redeem, liquidity calculations)
  • Test Infrastructure: Migration to Anvil for forking RSK Mainnet network

Changed

  • Market Operations: Refactored all market operations (mint, borrow, repayBorrow, redeem, balanceOfUnderlying) to use dynamic decimal handling instead of hardcoded factors
  • Comptroller Calculations: Enhanced liquidity, supply, and borrow calculations to accurately handle mixed decimal scenarios
  • USD Value Calculations: Improved handling of 6-decimal tokens with 18-decimal and 30-decimal price oracles
  • Test Suite: Updated test assertions to use closeTo for better precision in financial calculations

Technical Details

Decimal Handling

  • Token decimals are automatically detected from ERC20 contracts
  • Oracle adapter decimals are detected and cached for performance
  • All calculations now use dynamic factors based on detected decimals
  • Supports mixed scenarios: 6-decimal tokens with 8-decimal, 18-decimal, or 30-decimal price oracles

Backward Compatibility

  • All existing 18-decimal token operations remain fully functional
  • No breaking changes to public API
  • Deprecated markets (kUSDT, kRIF, kRDOC, kSAT) continue to work with deprecation warnings

Documentation Updates

  • Clarified USDT0 (6-decimal) vs deprecated kUSDT (18-decimal rUSDT) distinction
  • Added test execution instructions with Anvil node requirements
  • Enhanced documentation on decimal handling for new token integrations

Testing

  • All tests pass with Anvil forking RSK Mainnet
  • Comprehensive test coverage for 6-decimal token operations
  • Tests verify correct behavior across multiple markets with different decimal precisions
  • Test suite requires Anvil node restart between full runs for clean state

Migration Notes

No migration required for existing integrations. The changes are fully backward compatible. New integrations using 6-decimal tokens (like USDT0) will automatically benefit from the improved decimal handling.

Related Issues

This release addresses the need for proper handling of 6-decimal tokens in the Tropykus protocol, particularly for USDT0 integration.

Summary by CodeRabbit

Release Notes v0.4.0

  • New Features

    • Added support for 6-decimal ERC20 tokens (USDT0) with automatic decimal detection.
    • Implemented dynamic decimal handling for improved compatibility with variable token types.
  • Improvements

    • Enhanced USD value calculations for mixed-decimal token environments.
    • Refined liquidity calculations across multiple market types.
  • Documentation

    • Updated README with test execution instructions.
    • Clarified distinctions between USDT0 and deprecated kUSDT tokens.

✏️ Tip: You can customize this high-level summary in your review settings.

…ts. This includes the deletion of data model, implementation plan, quickstart guide, research notes, and other related files, streamlining the project structure as part of the deprecation process.
… Updated chainId validation, account generation, and comptroller deployment to utilize Unitroller. Documented test suite baseline and root cause investigation for failing tests.
…ormance and error handling. Update tests to utilize Rootstock Mainnet addresses and ensure compatibility with fresh unitroller deployments. Fix failing tests related to market listing and entry, enhancing overall test reliability.
…ts to remove skipped status. Adjusted test cases to ensure proper execution and accuracy, including changes to expected values and added precision in assertions. Updated borrowing logic to reflect correct amounts in tests.
…ability on forked networks. Updated test cases to correctly set pending implementations and verify comptroller addresses, ensuring compatibility with Rootstock Mainnet addresses.
…rket statuses. Corrected deprecation reasons for kRDOC/cRDOC and updated test addresses in deprecation.spec.js to ensure consistency and pass all tests successfully.
…ckstart.md file exists and is readable. Introduced explicit test to verify file content, resulting in all quickstart validation tests passing.
…restart requirement to prevent state issues. Update Unitroller tests to ensure transaction mining before verification, resulting in all tests passing. Documented final test suite baseline and verified test infrastructure configuration.
…cle integration. Enhance decimal detection and conversion utilities, ensuring backward compatibility with existing 18-decimal tokens. Update relevant Market and PriceOracle methods to handle mixed decimal calculations accurately. Add comprehensive integration tests for deposit, borrow, and USD value calculations, verifying correct behavior across scenarios.
…plementing decimal utility functions. Mark tasks T001 to T008 as completed, ensuring readiness for subsequent development phases.
…detectOracleDecimals` method to identify decimals for USDT and Moc adapters, and initialize `adapterDecimalsMap` for caching results. Update tasks in specs to reflect completion of these features.
… 18-decimal (MoC) and 30-decimal (USDT) price oracles. Update `detectOracleDecimals` method for accurate decimal detection and caching. Revise USD value calculations to handle mixed decimal scenarios correctly, ensuring backward compatibility with existing 18-decimal tokens. Add comprehensive integration tests for various operations, including deposit, borrow, and liquidity calculations, verifying correct behavior across all scenarios.
…ndex.js`. Update tasks in the specs to reflect the completion of decimal detection and adapter initialization for USDT and MoC, ensuring accurate handling of mixed decimal scenarios.
…zing the use of USDT0 (6-decimal) instead of the deprecated kUSDT (18-decimal). Revise README, specs, and quickstart files to clarify the handling of 6-decimal tokens with price oracles, ensuring accurate representation and backward compatibility. Enhance clarity on deprecated markets and their implications for new integrations.
…kets.spec.js`, including decimal detection, deposit, balance query, borrow, and repay functionalities. Mark the 'Markets operations' suite as skipped to focus on new tests for USDT0 token handling.
…asses to support dynamic decimal detection for ERC20 tokens. Implement async decimal initialization and parsing for accurate token amount handling. Update tests to verify correct behavior with 6-decimal tokens, ensuring backward compatibility and accurate USD value calculations.
…ait for improved readability and performance. Implement dynamic decimal detection for token and oracle values, ensuring accurate USD calculations. Update related tests to reflect changes in balance querying and precision handling for 6-decimal tokens.
…it for improved readability and dynamic decimal detection. Implement new borrow method in CErc20 class for borrowing amounts with proper token parsing. Update tests to verify correct balance calculations after borrowing, ensuring accurate handling of 6-decimal tokens.
…amic token factor calculation based on token decimals. Update repayBorrow method to use parsed token amounts instead of hardcoded values, ensuring accurate handling of token operations. Enhance tests to verify correct balance calculations after repayments with 6-decimal tokens.
…ecimal handling. Enhance tests to verify correct 6-decimal parsing during redemption, including partial and full redemptions, ensuring accurate balance calculations for users.
…ing for token operations. Update methods to replace hardcoded factors with calculations based on token decimals, ensuring accurate parsing and formatting for various token types. Enhance JSDoc comments for clarity on method functionalities and decimal handling. Update tests to verify correct behavior with 6-decimal tokens across all operations.
…ns with 6-decimal and 18-decimal tokens. Skip the previous 6-decimal token detection tests and add new tests for account liquidity, borrowing, and supply calculations across different decimal precisions, ensuring accurate handling of token operations.
…dling for token operations. Update getAccountLiquidity method to calculate market-specific liquidity with accurate token and oracle decimal detection. Refactor related methods for improved readability and precision. Update tests to verify correct behavior with 6-decimal and 18-decimal tokens across various operations.
…ne total supply calculations across markets. Replace redundant variables with a single priceUnderlying variable for improved clarity. Update tests to reflect changes in expected total supply values for accurate decimal handling.
… clarity and accuracy in borrow calculations. Replace underlyingBorrowAmount with priceUnderlying for better handling of target market pricing. Update tests to include new CRBTC market and ensure correct total borrows and supplies across multiple markets with accurate decimal handling.
…namic decimal handling for underlying amounts. Update liquidity tests to include new CRBTC market, ensuring accurate liquidity calculations across multiple markets. Adjust maxAllowedToWithdraw test for correct collateral factor application and liquidity assessment.
…ts for market operations and decimal detection. Update assertions to use closeTo for better precision in liquidity and borrowing calculations. Ensure accurate handling of token operations across different decimal precisions.
…0 tokens and dynamic decimal detection. Refactor relevant classes and methods for improved decimal handling, ensuring backward compatibility. Enhance documentation and test infrastructure, including comprehensive integration tests for new token operations.
@dacarva dacarva requested a review from NataliaMunozC December 4, 2025 20:57
@coderabbitai
Copy link

coderabbitai bot commented Dec 4, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Release version 0.4.0 introduces dynamic token decimal detection and handling across the protocol, particularly supporting 6-decimal ERC20 tokens like USDT. Core methods convert to async, price oracles normalize decimals, and tests migrate to Anvil for RSK Mainnet forking with comprehensive multi-market decimal precision coverage.

Changes

Cohort / File(s) Summary
Version Bumps
lerna.json, package.json, packages/tropykus/package.json
Updated version from 0.3.0 (or 0.2.17) to 0.4.0 across workspace and package manifests
Configuration & Metadata
.gitignore, packages/tropykus/src/deprecation-config.js
Added .DS_Store ignore entry; updated kRDOC deprecation reason from "never listed" to "delisted"
Documentation
README.md, CHANGELOG.md
Added "Running Tests" section for Anvil setup; introduced 0.4.0 release notes documenting decimal support, async refactoring, and test infrastructure changes; clarified USDT0 vs kUSDT distinctions
Specification Files (Deleted)
specs/001-deprecate-delisted-markets/*
Removed entire feature spec directory including plan.md, spec.md, tasks.md, quickstart.md, research.md, data-model.md, and contracts subdocumentation—feature implementation now merged into codebase
Contract Artifacts
packages/tropykus/artifacts/MockPriceOracleAdapterUSDT.json, packages/tropykus/artifacts/MockPriceProviderMoC.json, packages/tropykus/artifacts/PriceOracleAdapterMoc.json, packages/tropykus/artifacts/PriceOracleAdapterUSDT.json
Added new mock and production contract artifact descriptors for USDT/MoC price oracle adapters with constructor, event, and view/mutating function signatures
Decimal Utilities
packages/tropykus/src/utils/decimals.js
New utility module providing getTokenDecimals, parseTokenAmount, and formatTokenAmount helpers for dynamic decimal normalization across token scales
Price Oracle
packages/tropykus/src/PriceOracle.js
Converted to async; added detectOracleDecimals for adapter-specific precision detection (USDT 30-decimal, MoC 18-decimal); refactored getUnderlyingPrice to normalize prices via detected decimals; updated constructor parameter naming
Market Implementation
packages/tropykus/src/Market.js
Extensive async refactoring with per-token and per-oracle decimal detection; balance/borrow/supply methods now return structured objects including underlying, USD, and FixedNumber representations; enhanced maxAllowedToWithdraw/maxAllowedToDeposit with multi-market decimal-aware logic
CErc20 Market
packages/tropykus/src/Markets/CErc20.js
Added token decimal detection with caching; dynamic parseTokenAmount usage replacing hardcoded 18-decimal assumptions; new async borrow, redeem, and transferUnderlying methods; updated balanceOfUnderlyingInWallet to fetch both token and oracle decimals
Comptroller
packages/tropykus/src/Comptroller.js
Converted key methods (getAccountLiquidity, getHypotheticalAccountLiquidity, getTotalBorrowsInAllMarkets, getTotalSupplyInAllMarkets) to async with per-market decimal detection; parallelized market processing; removed kRDocAddress support; enriched return objects with USD and underlying values
SDK Entry Point
packages/tropykus/src/index.js
Updated market deployment to dynamically fetch underlying token decimals for initialExchangeRate parsing instead of fixed 1e18 assumptions
Core Tests
packages/tropykus/test/00-tropykus.spec.js, packages/tropykus/test/01-comptroller.spec.js, packages/tropykus/test/03-unitroller.spec.js
Migrated to Anvil-based testing with fresh Unitroller deployment; updated to mainnet fork addresses (Rootstock); replaced static comptroller references with dynamic proxy instantiation; broadened chainId expectations
Market Integration Tests
packages/tropykus/test/02-markets.spec.js
Comprehensive refactor deploying Unitroller, price oracle adapters (MoC, USDT), and token contracts dynamically; added 6-decimal token (USDT0) test suite; multi-market scenarios with liquidity and conversion validation; replaced hard-coded market addresses
Deprecation & Validation Tests
packages/tropykus/test/deprecation.spec.js, packages/tropykus/test/quickstart-validation.spec.js, packages/tropykus/test/utils/deprecation.spec.js
Relaxed warning count assertions; added quickstart.md existence checks; updated deprecated market addresses to new test values
New Test Suites
packages/tropykus/test/price-oracle.spec.js, packages/tropykus/test/utils/decimals.spec.js
Added comprehensive PriceOracle tests validating detectOracleDecimals caching, DECIMAL_MULTIPLIER handling, and multi-adapter scenarios; added decimals utility tests covering getTokenDecimals, parseTokenAmount, formatTokenAmount across 18/6/8/2/0 decimal scales

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant Oracle as PriceOracle
    participant Adapter as OracleAdapter<br/>(USDT/MoC)
    participant Market as Market
    participant Token as ERC20 Token
    participant Comptroller as Comptroller

    App->>Market: Get balance (async)
    Market->>Token: Call decimals()
    Token-->>Market: tokenDecimals (6 or 18)
    Market->>Adapter: detectOracleDecimals()
    Adapter->>Adapter: Check DECIMAL_MULTIPLIER
    Adapter-->>Market: oracleDecimals (18 or 30)
    Note over Market: Cache decimals
    Market->>Market: Fetch raw balance
    Market->>Oracle: Get underlying price
    Oracle->>Adapter: Fetch price (raw)
    Adapter-->>Oracle: rawPrice
    Oracle->>Oracle: Normalize by oracleDecimals
    Oracle-->>Market: humanReadablePrice
    Market->>Market: Calculate balance in USD<br/>(balance / 10^tokenDecimals) * price
    Market-->>App: { underlying, usd, fixedNumber }

    App->>Comptroller: Get account liquidity (async)
    Comptroller->>Market: For each market, get decimals & price
    Market->>Market: Per-market decimal detection
    Comptroller->>Comptroller: Aggregate liquidity<br/>across mixed decimals
    Comptroller-->>App: { liquidity, shortfall }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • Comptroller.js, Market.js, CErc20.js: Extensive async refactoring with per-market decimal detection, parallel processing logic, and enriched return object structures; verify correct decimal handling across USDT (6), DOC (18), and RBTC scenarios
  • PriceOracle.js: New detectOracleDecimals caching mechanism and adapter-specific DECIMAL_MULTIPLIER probing; ensure cache consistency across multiple adapter types
  • Test suite migration (02-markets.spec.js): Large-scale refactor replacing static addresses with dynamic Unitroller/adapter deployment; verify Anvil RPC interactions and mock adapter wiring
  • Decimal utility functions: Validate fallback behavior to 18 decimals when detection fails; check BigNumber type handling and formatUnits/parseUnits correctness
  • Price oracle adapter artifacts: Confirm new contract ABIs align with expected method signatures for DECIMAL_MULTIPLIER and assetPrices across MoC and USDT adapters

Possibly related PRs

  • tropykusjs#4: Touches the same deprecation-related code and documentation (deprecation-config.js, market warning logic, README entries), indicating parallel deprecation feature refinement

Suggested reviewers

  • NataliaMunozC

Poem

🐰 Hops with glee—decimals now dance,
USDT sixes get their chance,
Async flows through every seam,
Anvil forking dreams supreme!
One price, many scales aligned—
Precision blooms in code designed. 🌱

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding 6-decimal token support and dynamic decimal handling for version 0.4.0, which aligns with the core objectives and file changes throughout the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

Comment @coderabbitai help to get the list of available commands and usage tips.

@dacarva
Copy link
Member Author

dacarva commented Dec 4, 2025

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 4, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/tropykus/test/deprecation.spec.js (1)

247-259: Assertion contradicts the comment and test intent.

The comment states "No new warnings" but the assertion now allows for additional warnings by using greaterThanOrEqual. This change could mask issues where extra warnings are incorrectly triggered during market operations.

If additional warnings are expected in this scenario, update the comment to reflect this. Otherwise, restore the original .to.equal(initialCallCount) assertion to enforce that no new warnings are emitted.

Apply this diff to restore the original strict assertion:

-      expect(consoleWarnStub.callCount).to.be.greaterThanOrEqual(initialCallCount); // No new warnings
+      expect(consoleWarnStub.callCount).to.equal(initialCallCount); // No new warnings

Or, if additional warnings are expected, update the comment:

-      expect(consoleWarnStub.callCount).to.be.greaterThanOrEqual(initialCallCount); // No new warnings
+      expect(consoleWarnStub.callCount).to.be.greaterThanOrEqual(initialCallCount); // May have additional warnings in extended flows
🧹 Nitpick comments (16)
packages/tropykus/test/quickstart-validation.spec.js (1)

32-36: Consider consolidating validation or making string checks more flexible.

The test duplicates validation already performed in the before hook (lines 27-29 ensure quickstartContent is a non-empty string). Additionally, checking for the exact string "Deprecate Delisted Markets" may be brittle if documentation wording evolves.

Consider either:

  • Removing the redundant checks (lines 33-34) since the before hook guarantees them, or
  • Using a more flexible pattern match like /deprecat.*delist/i if only semantic presence matters
packages/tropykus/src/index.js (1)

136-157: Confirm initialExchangeRate scaling vs contract expectations and consider reusing a shared decimals helper.

The new logic to fetch underlying token decimals and then compute:

underlyingDecimals = await erc20Contract.callStatic.decimals();
// ...
ethers.utils.parseUnits(args.initialExchangeRate.toString(), underlyingDecimals)

is crucial for 6‑decimal tokens, but there are two points worth double‑checking:

  1. Exchange rate mantissa units:
    The comment says “Exchange rate is always in 18 decimals in the contract”, but here the mantissa is scaled with underlyingDecimals. For typical CErc20/CToken patterns, the initial exchange rate mantissa has its own fixed scaling (often 18 or 18 + underlyingDecimals - 8). If your CErc20/CRDOC implementation still expects that style of mantissa, scaling by just underlyingDecimals will be off by several orders of magnitude for non‑18‑dec tokens. Please re‑verify the constructor signature and docs for CErc20Immutable/CRDOC and ensure the exponent passed into parseUnits exactly matches what the contract expects.
    If the contract was intentionally changed to expect a mantissa in underlyingDecimals, consider updating the comment to avoid the “always 18 decimals” wording.

  2. Reuse of decimal utilities:
    The PR adds decimal utilities (utils/decimals.js) and broader dynamic‑decimal handling in Markets/Comptroller. To avoid duplicating the decimals() call + try/catch + defaulting to 18 logic here, it may be cleaner to route this through the same shared helper (if one already exists) so behavior and fallbacks stay consistent across the codebase.

packages/tropykus/test/price-oracle.spec.js (2)

110-124: Unused helper function.

The deployMockToken helper is defined but usdt0Token deployed in line 355 is never used in any assertions. Consider either removing the unused variable or adding assertions that validate the token properties.

     beforeEach(async () => {
-      // Deploy USDT0 mock token (6 decimals like USDT)
-      usdt0Token = await deployMockToken('USDT0', 'USDT0', 6);
-
       // Deploy a mock cToken (market) for USDT0

222-229: Brittle error message assertion.

The test checks that the error message includes 'DECIMAL_MULTIPLIER', but error messages may vary across ethers.js versions or node implementations. Consider checking for a more general error condition or just verifying that an error was thrown.

       try {
         await mocAdapterContract.callStatic.DECIMAL_MULTIPLIER();
         expect.fail('DECIMAL_MULTIPLIER should not exist on MoC adapter');
       } catch (error) {
         // Expected - MoC adapter doesn't have DECIMAL_MULTIPLIER
-        expect(error.message).to.include('DECIMAL_MULTIPLIER');
+        // Error is expected when calling non-existent function
+        expect(error).to.exist;
       }
packages/tropykus/test/01-comptroller.spec.js (2)

29-31: Redundant variable assignment.

crbtcAddress is assigned the same value as krbtcAddress and the comment indicates they're the same market. Consider using krbtcAddress directly where needed to reduce confusion.

 const csatAddress = '0xd2ec53e8dd00d204d3d9313af5474eb9f5188ef6'; // kSAT/cSAT (deprecated, mainnet)
-const crbtcAddress = krbtcAddress; // kRBTC (use the same as krbtcAddress since it's the active kRBTC market)

58-59: Test assertion is too permissive.

Expecting "at least 0 markets" will always pass, even if there's a bug that returns an empty array when markets should exist. Since you're forking mainnet, there should be a known minimum number of markets.

-        // On Rootstock Mainnet, there should be at least some markets
-        expect(markets.length).to.be.at.least(0);
+        // On Rootstock Mainnet, there should be at least some markets
+        // Based on the README, there are active markets on mainnet
+        expect(markets.length).to.be.at.least(1);
packages/tropykus/src/Markets/CErc20.js (3)

82-91: Unused private method.

_getTokenFactor() is defined but never called anywhere in this file. Consider removing it or using it in methods like balanceOfUnderlyingInWallet where similar logic is duplicated.

Either remove the unused method:

-  /**
-   * Get the token factor (10^tokenDecimals) as a FixedNumber
-   * @private
-   * @returns {Promise<FixedNumber>} The token factor
-   */
-  async _getTokenFactor() {
-    const decimals = await this._ensureDecimals();
-    const tokenFactorValue = BigNumber.from(10).pow(decimals).toString();
-    return FixedNumber.fromString(tokenFactorValue, format);
-  }

Or use it in balanceOfUnderlyingInWallet to reduce duplication:

-    const tokenFactorValue = BigNumber.from(10).pow(decimals).toString();
-    ...
-    const tokenFactor = FixedNumber.fromString(tokenFactorValue, format);
+    const tokenFactor = await this._getTokenFactor();

24-28: Constructor initiates async operation without error handling.

The constructor kicks off _initializeDecimals() but doesn't handle potential errors. If the async operation fails, the rejection will be unhandled until _ensureDecimals() is called. Consider adding error handling to prevent unhandled promise rejections during construction.

     this.tokenDecimals = null; // Will be initialized async in _initializeDecimals()
-    this._decimalsPromise = this._initializeDecimals();
+    this._decimalsPromise = this._initializeDecimals().catch((error) => {
+      console.warn(`[CErc20] Failed to initialize decimals: ${error.message}, will retry on demand`);
+      this.tokenDecimals = null; // Reset to trigger retry
+      return 18; // Return default
+    });

333-344: Minor: redeem with maxValue=false should validate amount.

When maxValue is false, amount could be null or undefined (as shown in the example await cusdt.redeem(account, null, true)). If someone accidentally calls redeem(account, null, false), parseTokenAmount will receive "null" as a string, causing unexpected behavior.

   async redeem(account, amount, maxValue = false) {
     if (maxValue) {
       const kTokens = await this.instance.callStatic.balanceOf(account.address);
       return this.instance.connect(account.signer)
         .redeem(kTokens, { gasLimit: this.tropykus.gasLimit });
     }
+    if (amount == null || amount <= 0) {
+      throw new Error('Amount must be a positive number when maxValue is false');
+    }
     const decimals = await this._ensureDecimals();
     const parsedAmount = parseTokenAmount(amount.toString(), decimals);
packages/tropykus/test/02-markets.spec.js (3)

292-297: USDT token deployed with 18 decimals instead of 6.

The usdtToken is deployed with 18 decimals (line 295), but the variable name and context suggest it should represent a USDT-like token which typically has 6 decimals. This may cause confusion and doesn't align with the PR's goal of testing 6-decimal token handling. Consider changing to 6 decimals for consistency, or rename the variable to clarify it's an 18-decimal test token.

      usdtToken = await usdtTokenFactory.deploy(
-       ethers.utils.parseEther('1000000'), // 1M tokens
+       ethers.utils.parseUnits('1000000', 6), // 1M tokens with 6 decimals
        'USDT Token',
-       18,
+       6, // 6 decimals like real USDT
        'USDT',
      );

606-621: afterEach cleanup sets variables to null but doesn't await pending transactions.

Setting variables to null in afterEach doesn't prevent test interference if there are pending blockchain transactions from the previous test. Consider adding a mechanism to wait for transaction confirmations or relying on Anvil's snapshot/revert functionality for proper test isolation.


1884-1891: Test contains TDD placeholder logic that may always pass.

The conditional check if (cusdt0.tokenDecimals !== undefined) creates a test that passes regardless of whether the feature is implemented. If tokenDecimals is undefined, the test falls back to checking erc20Instance.decimals() which tests the underlying token, not the market's decimal detection. This may mask missing functionality.

Consider using a stricter assertion or marking the test as pending until the feature is fully implemented.

packages/tropykus/src/Comptroller.js (2)

63-75: Silent fallback when underlying() call fails may hide issues.

Creating a CToken with null underlying address when underlying() fails could lead to unexpected behavior downstream. The comment mentions "deprecated markets," but this catch-all approach might mask other errors (e.g., network issues, incorrect ABI). Consider logging a warning or differentiating between expected failures (deprecated markets) and unexpected errors.

            } catch (error) {
-             // If underlying() call fails (e.g., market is deprecated), still create a CToken
-             // but without underlying address - this handles deprecated markets gracefully
+             // If underlying() call fails (e.g., market is deprecated or is CRBTC),
+             // log a warning and create a CToken without underlying address
+             console.warn(`Could not fetch underlying for market ${marketAddress}: ${error.message}`);
              return new CToken(this.tropykus, marketAddress, null);
            }

367-390: Nested try-catch blocks are redundant.

The outer try-catch (lines 367-390) catches errors from creating the market contract, but the inner try-catch (lines 375-386) already handles errors from the underlying() call. The outer catch sets tokenDecimals = 18, which is the same default used throughout. This nesting adds complexity without benefit.

-   try {
-     // Check if marketAddress is a CRBTC market (native currency, always 18 decimals)
-     // For ERC20 markets, we need to get the underlying token address and check decimals
-     const marketContract = new ethers.Contract(
-       marketAddress,
-       CErc20ImmutableArtifact.abi,
-       this.tropykus.provider,
-     );
-     try {
-       const underlyingAddress = await marketContract.callStatic.underlying();
-       const underlyingContract = new ethers.Contract(
-         underlyingAddress,
-         ['function decimals() view returns (uint8)'],
-         this.tropykus.provider,
-       );
-       tokenDecimals = await underlyingContract.callStatic.decimals();
-     } catch (error) {
-       // If underlying() fails (e.g., CRBTC market), default to 18 decimals
-       tokenDecimals = 18;
-     }
-   } catch (error) {
-     // If market contract creation fails, default to 18 decimals
-     tokenDecimals = 18;
-   }
+   // Check if marketAddress is a CRBTC market (native currency, always 18 decimals)
+   // For ERC20 markets, we need to get the underlying token address and check decimals
+   const marketContract = new ethers.Contract(
+     marketAddress,
+     CErc20ImmutableArtifact.abi,
+     this.tropykus.provider,
+   );
+   try {
+     const underlyingAddress = await marketContract.callStatic.underlying();
+     const underlyingContract = new ethers.Contract(
+       underlyingAddress,
+       ['function decimals() view returns (uint8)'],
+       this.tropykus.provider,
+     );
+     tokenDecimals = await underlyingContract.callStatic.decimals();
+   } catch (error) {
+     // If underlying() fails (e.g., CRBTC market), default to 18 decimals
+     tokenDecimals = 18;
+   }
packages/tropykus/src/Market.js (2)

509-536: balanceOfUnderlyingInWallet does not use dynamic decimal detection.

This method uses a hardcoded factor (1e18) for both the balance and price, unlike the refactored methods that detect token and oracle decimals. For CRBTC (native currency), this is correct, but if this method is called for ERC20 markets, it will produce incorrect results.

Looking at the method, it uses this.tropykus.provider.getBalance() which is specifically for native currency balance, so this appears intentional for CRBTC. However, the method documentation doesn't clarify this limitation. Consider adding a note or creating an override in CErc20.


1006-1016: Potential precision loss when rounding underlying to token decimals.

The toFixed(tokenDecimals) call converts a floating-point number to a string with fixed decimal places, which can introduce rounding errors before parsing. For very large or very small amounts, this may cause precision issues.

Consider using a BigNumber-based approach throughout to avoid floating-point conversion:

-   const underlyingNumber = Number(underlying._value);
-   const underlyingRounded = underlyingNumber.toFixed(tokenDecimals);
-   const underlyingAmountParsed = parseTokenAmount(underlyingRounded, tokenDecimals);
+   // Use FixedNumber to maintain precision during rounding
+   const underlyingAmountParsed = parseTokenAmount(underlying._value, tokenDecimals);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 977c017 and 30b2d87.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (35)
  • .gitignore (1 hunks)
  • CHANGELOG.md (1 hunks)
  • README.md (3 hunks)
  • lerna.json (1 hunks)
  • package.json (1 hunks)
  • packages/tropykus/artifacts/MockPriceOracleAdapterUSDT.json (1 hunks)
  • packages/tropykus/artifacts/MockPriceProviderMoC.json (1 hunks)
  • packages/tropykus/artifacts/PriceOracleAdapterMoc.json (1 hunks)
  • packages/tropykus/artifacts/PriceOracleAdapterUSDT.json (1 hunks)
  • packages/tropykus/package.json (1 hunks)
  • packages/tropykus/src/Comptroller.js (3 hunks)
  • packages/tropykus/src/Market.js (5 hunks)
  • packages/tropykus/src/Markets/CErc20.js (4 hunks)
  • packages/tropykus/src/PriceOracle.js (2 hunks)
  • packages/tropykus/src/deprecation-config.js (1 hunks)
  • packages/tropykus/src/index.js (2 hunks)
  • packages/tropykus/src/utils/decimals.js (1 hunks)
  • packages/tropykus/test/00-tropykus.spec.js (1 hunks)
  • packages/tropykus/test/01-comptroller.spec.js (2 hunks)
  • packages/tropykus/test/02-markets.spec.js (35 hunks)
  • packages/tropykus/test/03-unitroller.spec.js (2 hunks)
  • packages/tropykus/test/deprecation.spec.js (1 hunks)
  • packages/tropykus/test/price-oracle.spec.js (1 hunks)
  • packages/tropykus/test/quickstart-validation.spec.js (1 hunks)
  • packages/tropykus/test/utils/decimals.spec.js (1 hunks)
  • packages/tropykus/test/utils/deprecation.spec.js (5 hunks)
  • specs/001-deprecate-delisted-markets/checklists/requirements.md (0 hunks)
  • specs/001-deprecate-delisted-markets/contracts/deprecation-api.md (0 hunks)
  • specs/001-deprecate-delisted-markets/contracts/deprecation-config.json (0 hunks)
  • specs/001-deprecate-delisted-markets/data-model.md (0 hunks)
  • specs/001-deprecate-delisted-markets/plan.md (0 hunks)
  • specs/001-deprecate-delisted-markets/quickstart.md (0 hunks)
  • specs/001-deprecate-delisted-markets/research.md (0 hunks)
  • specs/001-deprecate-delisted-markets/spec.md (0 hunks)
  • specs/001-deprecate-delisted-markets/tasks.md (0 hunks)
💤 Files with no reviewable changes (9)
  • specs/001-deprecate-delisted-markets/quickstart.md
  • specs/001-deprecate-delisted-markets/tasks.md
  • specs/001-deprecate-delisted-markets/plan.md
  • specs/001-deprecate-delisted-markets/data-model.md
  • specs/001-deprecate-delisted-markets/checklists/requirements.md
  • specs/001-deprecate-delisted-markets/contracts/deprecation-api.md
  • specs/001-deprecate-delisted-markets/research.md
  • specs/001-deprecate-delisted-markets/spec.md
  • specs/001-deprecate-delisted-markets/contracts/deprecation-config.json
🧰 Additional context used
🧬 Code graph analysis (4)
packages/tropykus/test/00-tropykus.spec.js (2)
packages/tropykus/src/index.js (1)
  • Tropykus (16-244)
packages/tropykus/src/Unitroller.js (1)
  • Unitroller (4-58)
packages/tropykus/test/03-unitroller.spec.js (1)
packages/tropykus/src/Unitroller.js (1)
  • Unitroller (4-58)
packages/tropykus/test/utils/decimals.spec.js (1)
packages/tropykus/src/utils/decimals.js (4)
  • decimals (26-26)
  • getTokenDecimals (15-49)
  • parseTokenAmount (67-69)
  • formatTokenAmount (87-94)
packages/tropykus/src/Markets/CErc20.js (1)
packages/tropykus/src/utils/decimals.js (3)
  • getTokenDecimals (15-49)
  • decimals (26-26)
  • parseTokenAmount (67-69)
🪛 markdownlint-cli2 (0.18.1)
README.md

40-40: Blank line inside blockquote

(MD028, no-blanks-blockquote)

🔇 Additional comments (33)
.gitignore (1)

497-499: Standard macOS system file entry—LGTM.

Adding .DS_Store to the ignore list is a widely adopted best practice for macOS development environments.

packages/tropykus/test/quickstart-validation.spec.js (1)

14-30: LGTM! Good defensive checks.

The enhanced error handling in the before hook properly validates file existence, readability, and content before running tests. The descriptive error messages will help diagnose issues quickly.

lerna.json (1)

2-2: Version metadata bump looks consistent with the 0.4.0 release.

lerna.json version aligns with the root and package versions and the new 0.4.0 changelog entry.

package.json (1)

3-3: Root package version correctly aligned to 0.4.0.

Matches lerna.json and the documented 0.4.0 release; no functional impact.

packages/tropykus/package.json (1)

3-3: Library package.json version correctly bumped to 0.4.0.

Keeps the published package version consistent with the monorepo and changelog.

CHANGELOG.md (1)

8-37: 0.4.0 changelog entry is clear and well-aligned with the implementation.

The Added/Changed/Documentation/Notes sections accurately describe the new 6‑decimal support, dynamic decimal handling, oracle decimal detection, Anvil test setup, and backward‑compatibility guarantees. No issues from a changelog/semver perspective.

packages/tropykus/artifacts/PriceOracleAdapterUSDT.json (1)

1-162: PriceOracleAdapterUSDT artifact structure and ABI look consistent for deployment/use.

The artifact includes the expected constructor, guardian management, price provider wiring, DECIMAL_MULTIPLIER, and assetPrices view, plus bytecode/deployedBytecode and link references, matching how other Hardhat artifacts in the repo are typically shaped. No issues spotted here.

packages/tropykus/artifacts/MockPriceOracleAdapterUSDT.json (1)

1-162: LGTM!

This artifact file is properly structured and contains the expected components (ABI, bytecode, deployedBytecode, linkReferences). Artifact files are typically auto-generated by build tools.

packages/tropykus/artifacts/PriceOracleAdapterMoc.json (1)

1-147: LGTM!

This artifact file is properly structured with all required components. Artifact files are auto-generated by the build process.

packages/tropykus/test/utils/deprecation.spec.js (1)

8-11: LGTM!

The address constants have been updated consistently throughout the test file. The test logic remains intact with only the test data (addresses) being updated.

packages/tropykus/src/utils/decimals.js (3)

15-49: Good error handling with appropriate fallbacks.

The function properly handles various edge cases including null inputs, missing decimals() function, BigNumber/string return types, and invalid decimal values. The fallback to 18 decimals is standard for ERC20 tokens.


67-69: LGTM!

Simple and effective wrapper around ethers.utils.parseUnits. The toString() call ensures both string and numeric inputs are handled correctly.


87-94: LGTM!

The function correctly handles both BigNumber and string inputs by checking the type before conversion. The use of ethers.utils.formatUnits ensures proper formatting.

packages/tropykus/test/03-unitroller.spec.js (2)

29-63: Excellent test refactoring.

Deploying fresh contracts avoids permission issues on forked networks and makes the test more reliable. The test properly:

  • Deploys fresh Unitroller and Comptroller instances
  • Verifies initial state before changes
  • Waits for transaction confirmation
  • Validates the final state

65-88: LGTM!

The test correctly uses tropykus.setComptroller to deploy and configure a new Comptroller implementation, then verifies it was set as the active implementation. The fresh Unitroller deployment ensures the test is self-contained.

packages/tropykus/test/utils/decimals.spec.js (1)

1-316: Excellent test coverage!

This test suite comprehensively covers all three decimal utility functions with:

  • Multiple decimal configurations (0, 2, 6, 8, 18)
  • Error handling and fallback scenarios
  • Type handling (BigNumber, string, number)
  • Edge cases (very small/large amounts, zero values)
  • Integration round-trip tests

The use of mocks and stubs is appropriate, and the test structure is clear and maintainable.

packages/tropykus/artifacts/MockPriceProviderMoC.json (1)

1-97: LGTM!

This artifact file is properly structured with all required components. Artifact files are auto-generated by the build process.

packages/tropykus/test/00-tropykus.spec.js (2)

22-26: LGTM!

Good update to accept multiple chain IDs for cross-network test compatibility. The conversion to Number() handles BigNumber returns correctly.


43-71: LGTM!

Deploying a fresh Unitroller for testing is the correct approach to avoid permission issues when forking mainnet. The test properly validates the proxy-implementation relationship.

packages/tropykus/src/PriceOracle.js (1)

35-85: LGTM!

The decimal detection logic is well-structured:

  • Null/undefined handling with fallback to 18 decimals
  • Caching with normalized lowercase addresses prevents duplicate queries
  • DECIMAL_MULTIPLIER probe correctly distinguishes USDT (30 decimals) from MoC (18 decimals) adapters
  • Error handling gracefully falls back to 18 decimals for unknown adapters
packages/tropykus/test/price-oracle.spec.js (1)

132-347: LGTM!

Comprehensive test coverage for detectOracleDecimals:

  • Tests both USDT (30 decimals) and MoC (18 decimals) adapters
  • Validates caching behavior and address normalization
  • Handles null/undefined inputs gracefully
  • Verifies DECIMAL_MULTIPLIER detection logic
packages/tropykus/test/01-comptroller.spec.js (1)

76-110: LGTM!

Good pattern for testing in a forked environment — deploying a fresh Unitroller avoids permission issues with the mainnet admin. The conditional logic handles both empty and populated market scenarios correctly.

packages/tropykus/src/Markets/CErc20.js (2)

152-178: LGTM!

Good fix for the delta calculation in repayBorrow. Using dynamic token decimals instead of hardcoded 1e18 ensures correct behavior for non-18-decimal tokens like USDT (6 decimals).


262-308: LGTM!

Well-implemented dynamic decimal handling for both token and oracle precision. Using Promise.all for parallel fetching of balance and price is efficient. The FixedNumber calculations maintain precision throughout.

packages/tropykus/test/02-markets.spec.js (1)

2393-2404: LGTM - Comprehensive liquidity assertions across multiple markets.

The test correctly verifies account liquidity across markets with different decimal precisions (6 for USDT0, 18 for DOC and RBTC). The assertions properly validate both USD and underlying values with appropriate tolerance using closeTo().

packages/tropykus/src/Comptroller.js (2)

268-341: LGTM - Well-structured market-specific liquidity calculation with proper decimal handling.

The implementation correctly:

  • Detects token decimals from underlying contract with fallback to 18 for CRBTC
  • Detects oracle decimals from the price oracle adapter
  • Uses BigNumber.pow() to avoid scientific notation issues
  • Calculates liquidity in both USD and underlying terms with proper factor conversion
  • Processes balance, price, and collateral factor in parallel for efficiency

474-517: LGTM - Efficient parallel processing with proper decimal-aware calculations.

The getTotalBorrowsInAllMarkets implementation correctly:

  • Processes markets in parallel using Promise.all
  • Detects both token and oracle decimals per-market
  • Converts borrows to human-readable format before USD calculation
  • Properly handles the target market price for underlying conversion
packages/tropykus/src/Market.js (6)

54-102: LGTM - Proper dynamic decimal detection for balance calculations.

The balanceOfUnderlying method correctly:

  • Detects token decimals via _ensureDecimals() or falls back to tokenDecimals property
  • Detects oracle decimals from the price adapter
  • Uses BigNumber.pow() for factor calculation to avoid scientific notation
  • Performs parallel contract calls for efficiency
  • Converts both balance and price to human-readable before USD calculation

314-332: redeem method uses parseEther for redeemUnderlying.

The redeemUnderlying call at line 325 uses ethers.utils.parseEther(amount.toString()) which assumes 18 decimals. For tokens with different decimal precisions, this will cause incorrect redemption amounts. Verify CErc20 override handles this correctly.


756-805: LGTM - Correct handling of cToken vs underlying decimal separation.

The getMarketTotalSupply method correctly distinguishes:

  • cToken totalSupply: Always 18 decimals (divided by cTokenFactor)
  • Exchange rate: Always 18 decimals (divided by cTokenFactor)
  • Oracle price: Uses detected oracle decimals
  • The underlying calculation correctly multiplies cTokens × exchangeRate

1213-1253: LGTM - Event-based supply aggregation with proper decimal handling.

The suppliedLast24Hours method correctly:

  • Detects token and oracle decimals
  • Aggregates mint amounts from events
  • Converts from token decimals to human-readable
  • Calculates USD value using properly converted price

294-304: CErc20 class properly overrides borrow() with decimal-aware parsing — no issue.

The CErc20 class at line 201-207 overrides the borrow() method and uses parseTokenAmount(amount.toString(), decimals) after retrieving the actual token decimals via this._ensureDecimals(). This correctly handles both 6-decimal tokens (like CUSDT) and 18-decimal tokens (like CDOC), so the base Market class's use of parseEther is safely overridden in subclasses that need it.


278-286: CErc20 correctly overrides mint() with proper decimal handling — no changes needed.

The Market.js mint method correctly uses parseEther for CRBTC (native currency, always 18 decimals). CErc20 properly overrides this method with parseTokenAmount(amount.toString(), decimals), which calls ethers.utils.parseUnits with the token's actual decimals fetched from the contract via _ensureDecimals(). This correctly supports 6-decimal tokens like USDT and other non-18-decimal tokens.

Comment on lines +605 to +607
if (market.address === marketAddress.toLowerCase()) {
priceUnderlying = priceHumanReadable;
}
Copy link

@coderabbitai coderabbitai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Comparison uses strict equality on non-normalized address.

Line 605 compares market.address with marketAddress.toLowerCase(), but market.address may not be lowercase. This could cause the condition to never match, resulting in priceUnderlying remaining at 0 and incorrect underlying calculations.

-     if (market.address === marketAddress.toLowerCase()) {
+     if (market.address.toLowerCase() === marketAddress.toLowerCase()) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (market.address === marketAddress.toLowerCase()) {
priceUnderlying = priceHumanReadable;
}
if (market.address.toLowerCase() === marketAddress.toLowerCase()) {
priceUnderlying = priceHumanReadable;
}
🤖 Prompt for AI Agents
In packages/tropykus/src/Comptroller.js around lines 605 to 607, the comparison
uses strict equality between market.address and marketAddress.toLowerCase()
while market.address may not be lowercased; normalize both sides before
comparing (e.g., ensure market.address exists then compare
market.address.toLowerCase() === marketAddress.toLowerCase()) so the match
succeeds and priceUnderlying is set correctly.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this is important

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NataliaMunozC, you're absolutely right—this is important!

The issue identified by CodeRabbit is a real bug: on line 605 in getTotalSupplyInAllMarkets, the comparison market.address === marketAddress.toLowerCase() will fail if market.address isn't already lowercase. This means priceUnderlying would remain 0, leading to incorrect underlying value calculations when a specific marketAddress is provided.

Interestingly, this same comparison is done correctly earlier in the file: In getTotalBorrowsInAllMarkets (around line 519 in the diff), the code properly normalizes both sides:

if (market.address.toLowerCase() === marketAddress.toLowerCase()) {

So there's an inconsistency between these two similar methods. The fix is straightforward—apply the same normalization in getTotalSupplyInAllMarkets:

if (market.address.toLowerCase() === marketAddress.toLowerCase()) {
  priceUnderlying = priceHumanReadable;
}

This will ensure both methods handle address comparisons consistently and correctly. I'd recommend applying CodeRabbit's suggestion to fix this.

Comment on lines +23 to 27
// kRDOC/cRDOC market address - deprecated because market was delisted from protocol
'0x0000000000000000000000000000000000000000': {
deprecated: true,
reason: 'Market never listed (kRDOC/cRDOC)',
reason: 'Market delisted from protocol (kRDOC/cRDOC)',
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

kRDOC/cRDOC deprecation reason is inconsistent with existing docs.

Here the reason is “Market delisted from protocol (kRDOC/cRDOC)”, but:

  • README still describes cRDOC as “deprecated (never listed)”.
  • The 0.3.0 changelog entry for kRDOC says “Market never listed in the protocol”.

This inconsistency can be confusing for users trying to understand why kRDOC/cRDOC is deprecated. Consider choosing a single canonical reason (“never listed” vs “delisted”) and aligning:

  • This config entry
  • The README cRDOC deprecation note
  • (Optionally) a brief clarification in a newer changelog entry if the status truly changed over time.
🤖 Prompt for AI Agents
packages/tropykus/src/deprecation-config.js lines 23-27: the deprecation reason
for the kRDOC/cRDOC entry currently says "Market delisted from protocol
(kRDOC/cRDOC)" which conflicts with README and the 0.3.0 changelog that state it
was "never listed"; pick a single canonical reason (preferably "never listed" to
match existing docs) and update this config entry to use that canonical
phrasing, then update the README cRDOC deprecation note (and optionally add a
short changelog clarification if the historical status actually changed) so all
three places (this config, README, changelog) are consistent.

Comment on lines +121 to +128
const rawPrice = await this.instance.callStatic.getUnderlyingPrice(marketAddress);

// Divide by the correct factor based on adapter type to get human-readable price
// MoC: divide by 1e18, USDT: divide by 1e30
const divisor = BigNumber.from(10).pow(oracleDecimals);
const price = BigNumber.from(rawPrice).div(divisor);

return Number(price.toString());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential precision loss when dividing large BigNumbers.

BigNumber.div() performs integer division, truncating any fractional component. For prices that aren't exact multiples of the divisor, this could lose significant precision. For example, a price of 1.5e30 divided by 1e30 would correctly yield 1, but 1.5e30 doesn't exist as a BigNumber — fractional values are truncated.

Additionally, converting to Number() at line 128 may lose precision for prices with more than 15-16 significant digits.

Consider using FixedNumber for price calculations (consistent with CErc20.js) or returning the raw BigNumber to preserve precision:

-    const divisor = BigNumber.from(10).pow(oracleDecimals);
-    const price = BigNumber.from(rawPrice).div(divisor);
-    
-    return Number(price.toString());
+    const divisor = FixedNumber.fromString(
+      BigNumber.from(10).pow(oracleDecimals).toString(), 
+      'fixed80x18'
+    );
+    const price = FixedNumber.from(rawPrice.toString(), 'fixed80x18')
+      .divUnsafe(divisor);
+    
+    return Number(price._value);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/tropykus/src/PriceOracle.js around lines 121-128, the current use of
BigNumber.div causes integer truncation and the final Number(...) conversion can
lose precision; replace the integer division with a fixed-point calculation (use
ethers FixedNumber or a decimal library) to divide rawPrice by
10**oracleDecimals with preserved fractional precision and return a string (or
FixedNumber) instead of converting to Number, or alternatively return the raw
BigNumber/decimal string to callers so no precision is lost.

Comment on lines 560 to 564
alice = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/1`);
bob = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/2`);
carlos = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/3`);
david = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/4`);
eve = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/4`);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Variable eve is assigned the same derivation path as david.

Both david and eve are derived from the same HD path m/44'/60'/0'/0/4, making them the same account. This appears to be a copy-paste error.

      david = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/4`);
-     eve = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/4`);
+     eve = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/5`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
alice = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/1`);
bob = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/2`);
carlos = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/3`);
david = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/4`);
eve = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/4`);
alice = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/1`);
bob = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/2`);
carlos = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/3`);
david = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/4`);
eve = tropykus.getAccountFromMnemonic(mnemonic, `m/44'/60'/0'/0/5`);
🤖 Prompt for AI Agents
In packages/tropykus/test/02-markets.spec.js around lines 560 to 564, eve is
created using the same HD derivation path as david (`m/44'/60'/0'/0/4`) causing
both to be identical; change eve's derivation path to the next index (e.g.
`m/44'/60'/0'/0/5`) so eve is a distinct account, and run the tests to confirm
no other references rely on the duplicated path.

Comment on lines +2370 to +2377
// Clear all variables
usdt0Token = null;
cusdt0 = null;
cdoc = null;
newComptroller = null;
alice = null;
markets = null;
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

afterEach cleanup is incomplete - missing crbtc and bob cleanup.

The afterEach block in 'Multi-market operations' doesn't set crbtc and bob to null, unlike the 'Markets operations' block which cleans up all variables. This inconsistency could lead to memory leaks or test interference.

    afterEach(async () => {
      // Clear all variables
      usdt0Token = null;
      cusdt0 = null;
      cdoc = null;
+     crbtc = null;
      newComptroller = null;
      alice = null;
+     bob = null;
      markets = null;
    });
🤖 Prompt for AI Agents
In packages/tropykus/test/02-markets.spec.js around lines 2370 to 2377, the
afterEach cleanup for the "Multi-market operations" tests omits resetting crbtc
and bob to null; update that afterEach block to also set crbtc = null and bob =
null so all test-scoped variables are cleared consistently to prevent state
leakage between tests.

Comment on lines +20 to +42
## Running Tests

Before running the test suite, you need to start a local Anvil node that forks the RSK Mainnet network:

```bash
anvil --fork-url https://public-node.rsk.co --chain-id 30 --port 8545
```

This command:
- Forks the RSK Mainnet network (`https://public-node.rsk.co`)
- Sets the chain ID to 30 (RSK Mainnet)
- Runs the local node on port 8545 (default)

Once Anvil is running, you can run the tests in a separate terminal:

```bash
yarn test
```

> **Note**: Make sure Anvil is running before executing tests, as the test suite requires a local blockchain node to be available at `http://127.0.0.1:8545`.

> **⚠️ Important**: Before running the full test suite (`yarn test`), you **MUST** restart the Anvil node to ensure a clean state. This prevents nonce conflicts, state pollution, and transaction errors from previous test runs. To restart Anvil, stop the current process and start a fresh instance with the same command above.

Copy link

@coderabbitai coderabbitai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Test instructions and USDT0 clarifications read well; just fix the markdownlint blockquote warning.

The new “Running Tests” section and the Anvil notes are clear and match the new Anvil-based test setup, and the USDT0 vs kUSDT decimal explanation later in the README is very helpful.

markdownlint (MD028) is flagging a “Blank line inside blockquote” around the Anvil notes (lines 39–41). There appears to be an empty blockquote line between the two quoted notes. You can fix this by either:

  • Removing the empty quoted line entirely, or
  • Making sure each blockquote paragraph is consecutive without a blank > line in between.

Content-wise this section is good; this is just to keep markdownlint happy.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

40-40: Blank line inside blockquote

(MD028, no-blanks-blockquote)

🤖 Prompt for AI Agents
In README.md around lines 20 to 42, markdownlint MD028 flags a blank line inside
a blockquote in the Anvil notes; remove the empty quoted line or merge the two
blockquote paragraphs so there is no `>`-prefixed blank line between them
(ensure consecutive blockquote lines or collapse into a single blockquote
paragraph) to satisfy markdownlint.

Copy link

@NataliaMunozC NataliaMunozC Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the blank line

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NataliaMunozC, here's the fix for the markdownlint warning. You need to remove the empty line between the two blockquote notes.

Current code (with the blank line issue):

> **Note**: Make sure Anvil is running before executing tests, as the test suite requires a local blockchain node to be available at `http://127.0.0.1:8545`.

> **⚠️ Important**: Before running the full test suite (`yarn test`), you **MUST** restart the Anvil node to ensure a clean state. This prevents nonce conflicts, state pollution, and transaction errors from previous test runs. To restart Anvil, stop the current process and start a fresh instance with the same command above.

Fixed version (remove the empty line between blockquotes):

> **Note**: Make sure Anvil is running before executing tests, as the test suite requires a local blockchain node to be available at `http://127.0.0.1:8545`.
> **⚠️ Important**: Before running the full test suite (`yarn test`), you **MUST** restart the Anvil node to ensure a clean state. This prevents nonce conflicts, state pollution, and transaction errors from previous test runs. To restart Anvil, stop the current process and start a fresh instance with the same command above.

Alternatively, you could merge them into a single blockquote with a blank line inside (but this is less clear):

> **Note**: Make sure Anvil is running before executing tests, as the test suite requires a local blockchain node to be available at `http://127.0.0.1:8545`.
>
> **⚠️ Important**: Before running the full test suite (`yarn test`), you **MUST** restart the Anvil node to ensure a clean state. This prevents nonce conflicts, state pollution, and transaction errors from previous test runs. To restart Anvil, stop the current process and start a fresh instance with the same command above.

I recommend the first approach (consecutive blockquotes with no empty line between them) as it's cleaner and satisfies markdownlint.

… naming conventions. Change package name from 'tropykusjs' to '@tropykus/tropykuslibs' in package.json, README.md, and related files. Update versioning to '0.4.0-alpha' in lerna.json and package.json. Adjust scripts for publishing and checksum calculations to reflect new package name. Ensure all references are consistent across the project.
…cToken references and add USDT0 market details in README. Include new .claude directory in .gitignore. Remove old README.md from tropykus package.
… publish script to determine npm tag based on version type, supporting prerelease tags (alpha, beta, rc) for better package management.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants