Skip to content

Comments

Integrate Chainlink OracleManager for token price feeds and USD normalization in Jobs contract. Add OracleManager and IOracleManager contracts, update Job struct for usd_budget, wire conversions in job creation, payout, refund. Robust fallback/stale handling. [core oracle integration]#45

Merged
Cybermaxi7 merged 1 commit intoStarkHive:mainfrom
Cybermaxi7:feature/oracle-integration
Jun 5, 2025

Conversation

@Cybermaxi7
Copy link
Contributor

@Cybermaxi7 Cybermaxi7 commented Jun 5, 2025

Integrate Chainlink OracleManager for Token Price Feeds and USD Normalization

Overview

This PR introduces a modular on-chain OracleManager contract and integrates it with the Jobs contract to enable real-time token price feeds (ETH/USD, USDC/USD, STRK/USD) using Chainlink oracles. All job budgets and payments are now normalized to USD (18 decimals), improving accuracy and transparency for users and the frontend.


Key Changes

1. OracleManager Contract

  • New contract: OracleManager.cairo
  • Handles Chainlink price feeds and fallback manual prices (admin-set).
  • Provides real-time price retrieval, staleness checks, and robust fallback logic.
  • Utility functions for token-to-USD conversion, always normalized to 18 decimals.
  • Emits events for price feed updates, fallback price changes, and conversions.

2. IOracleManager Interface

  • New interface: IOracleManager.cairo
  • Standardizes external API for price retrieval, conversion, and status checks.

3. Jobs Contract Integration

  • OracleManager address stored in Jobs contract storage.
  • Job struct updated to include usd_budget field for normalized USD value.
  • On job creation, budget is normalized to USD using OracleManager and stored.
  • On payout and refund, real-time conversion is performed using OracleManager.
  • Robust handling for missing, stale, or fallback price data.
  • Placeholders for emitting conversion events (ready for frontend sync).

4. Types Update

  • Job struct in types.cairo updated to include usd_budget: u256.

Why?

  • Ensures accurate, up-to-date pricing for all jobs and payments.
  • Enables consistent USD-based search, filtering, and fee calculation.
  • Provides modular, reusable price conversion logic for future integrations (e.g., FeeManager).
  • Improves transparency and reliability for both clients and freelancers.

Next Steps

  • Implement event emission for conversions in Jobs contract.
  • Add comprehensive tests for edge cases (missing/stale/fallback/zero price).
  • (Optional) Integrate FeeManager for USD-based fee calculations.

Checklist

  • OracleManager contract with Chainlink and fallback support
  • IOracleManager interface
  • Jobs contract integration (USD normalization, storage, and payout conversion)
  • Job struct updated for USD budget
  • Conversion event emission in Jobs (TODO)
  • Tests for edge cases (TODO)

Closes #38


Please review and provide feedback!

Summary by CodeRabbit

  • New Features

    • Introduced an OracleManager contract for managing token price feeds, supporting Chainlink integration, manual fallback prices, and price normalization to USD.
    • Added the ability to convert token amounts to USD values with 18 decimals precision and status reporting.
    • Jobs now store and display a normalized USD budget value for each job.
    • Admin functions allow setting price feeds and fallback prices, with events emitted for updates and conversions.
  • Improvements

    • Enhanced job creation, approval, and cancellation to validate and convert token budgets to USD using real-time or fallback prices, ensuring accurate and up-to-date value representation.

…lization in Jobs contract. Add OracleManager and IOracleManager contracts, update Job struct for usd_budget, wire conversions in job creation, payout, refund. Robust fallback/stale handling. [core oracle integration]
@coderabbitai
Copy link

coderabbitai bot commented Jun 5, 2025

Walkthrough

This change introduces an OracleManager contract for modular, on-chain price feeds using Chainlink oracles with fallback mechanisms. The Jobs contract is updated to convert token budgets to normalized USD values via OracleManager during job creation, approval, and cancellation. An interface for OracleManager is added, and the Job struct now stores a USD budget.

Changes

File(s) Change Summary
src/base/types.cairo Added usd_budget: u256 field to the Job struct.
src/contracts/Jobs.cairo Integrated OracleManager for price conversion; added oracle_manager storage; updated job flows to store/validate USD budgets; added setter for OracleManager address.
src/contracts/OracleManager.cairo Introduced new OracleManager contract with Chainlink integration, fallback logic, normalization utilities, events, and admin controls.
src/interfaces/IOracleManager.cairo Added IOracleManager interface and OracleStatus enum for price feed status and conversion utilities.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant JobsContract
    participant OracleManager
    participant ChainlinkOracle

    User->>JobsContract: create_job(token, budget, ...)
    JobsContract->>OracleManager: to_usd(token, budget)
    OracleManager->>ChainlinkOracle: get_price(token)
    ChainlinkOracle-->>OracleManager: price, decimals, valid
    alt Price valid & not stale
        OracleManager-->>JobsContract: usd_value, Ok
    else Price invalid or stale
        OracleManager-->>JobsContract: fallback_usd_value, Fallback/Stale
    end
    JobsContract-->>User: job_id (with usd_budget stored)
Loading

Assessment against linked issues

Objective Addressed Explanation
Integrate Chainlink price feeds for supported tokens (ETH, USDC, STRK) (#38)
On-chain utility contract for converting token values to USD (#38)
Fallback oracle or manual update mechanism for resilience (#38)
Normalization of job budgets to fiat equivalent with 18 decimals (#38)
Real-time token price reference in escrow, milestone payouts, and refunds (#38)
Events emitted for feed updates and conversions (#38)
Read-only function to expose current exchange rates for frontend/UI (#38)
Handle missing or stale price data gracefully (#38)
Modular price conversion logic usable by multiple contracts (#38)

Possibly related PRs

Suggested reviewers

  • Xaxxoo

Poem

In fields of code where contracts grow,
A rabbit hops where oracles flow.
Now jobs know their worth in USD,
With fallback plans for certainty.
Chainlink whispers, values true—
“Hop on, dear devs, your budgets grew!”
🐇💸


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 7

🧹 Nitpick comments (6)
src/contracts/Jobs.cairo (3)

172-172: Implement conversion event emission

The TODO indicates missing event emission for USD conversions, which is important for transparency and frontend synchronization.

Would you like me to help implement the ConversionPerformed event and its emission logic?


443-446: Consider using or removing the computed USD value

The usd_value is computed but never used. If you only need to validate price availability, consider adding a validation-only method to OracleManager.

If the USD value isn't needed, you could add a simpler validation method to the OracleManager interface:

fn validate_price(self: @TContractState, token: felt252) -> OracleStatus;

Alternatively, consider emitting the USD value in the proposed conversion event for tracking purposes.


136-141: Refactor duplicated oracle validation logic

The pattern of converting token to symbol, calling OracleManager, and validating status is repeated across multiple functions.

Consider extracting this into a helper function:

fn validate_and_convert_to_usd(
    self: @ContractState, 
    token: ContractAddress, 
    amount: u256
) -> u256 {
    let token_symbol: felt252 = token.into(); // Or use proper mapping
    let oracle_manager_addr = self.oracle_manager.read();
    let oracle_manager = IOracleManagerTrait { contract_address: oracle_manager_addr };
    let (usd_value, status) = oracle_manager.to_usd(token_symbol, amount);
    assert(status == OracleStatus::Ok || status == OracleStatus::Fallback, 'Invalid price');
    usd_value
}

Also applies to: 440-445, 466-471

src/contracts/OracleManager.cairo (3)

70-75: Track the Chainlink integration TODO.

The stub function needs to be replaced with actual Chainlink oracle integration.

Would you like me to open an issue to track the Chainlink oracle integration implementation?


51-58: Add event emission for Chainlink feed updates.

For consistency with set_fallback_price, emit an event when Chainlink feeds are updated.

 #[external]
 fn set_chainlink_feed(ref self: ContractState, token: felt252, feed: ContractAddress, decimals: u8) {
     let admin = self.admin.read();
     assert(get_caller_address() == admin, 'only_admin');
     self.chainlink_feeds.write(token, feed);
     self.decimals.write(token, decimals);
+    emit PriceFeedUpdated(token, 0, decimals, 'chainlink_feed_set');
 }

60-68: Consider adding price bounds validation for fallback prices.

The admin can set arbitrary fallback prices without validation, which could be misused.

Consider adding reasonable bounds checking:

 #[external]
 fn set_fallback_price(ref self: ContractState, token: felt252, price: u256) {
     let admin = self.admin.read();
     assert(get_caller_address() == admin, 'only_admin');
+    // Example: Ensure price is within reasonable bounds (e.g., $0.01 to $1M with 18 decimals)
+    assert(price >= 10000000000000000 && price <= 1000000000000000000000000, 'price_out_of_bounds');
     self.fallback_prices.write(token, price);
     self.last_update.write(token, get_block_timestamp());
     emit FallbackPriceUpdated(token, price, admin);
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59d39c5 and c51f25f.

📒 Files selected for processing (4)
  • src/base/types.cairo (1 hunks)
  • src/contracts/Jobs.cairo (7 hunks)
  • src/contracts/OracleManager.cairo (1 hunks)
  • src/interfaces/IOracleManager.cairo (1 hunks)
🔇 Additional comments (3)
src/base/types.cairo (1)

55-55: LGTM!

The addition of the usd_budget field is properly implemented and documented.

src/interfaces/IOracleManager.cairo (1)

1-24: Well-structured interface design!

The IOracleManager interface is properly defined with comprehensive functions for price feed management and clear status handling through the OracleStatus enum.

src/contracts/OracleManager.cairo (1)

111-128:

Details

✅ Verification successful

Update function signature to support mutable operations.

This function calls get_price which needs to write to storage, so it must take a mutable reference.

 #[external]
-fn to_usd(self: @ContractState, token: felt252, amount: u256) -> (u256, OracleStatus) {
+fn to_usd(ref self: ContractState, token: felt252, amount: u256) -> (u256, OracleStatus) {
     let (price, decimals, status) = self.get_price(token);

Additionally, verify the power function syntax is correct for Cairo.


🏁 Script executed:

#!/bin/bash
# Description: Check if the power function syntax is correct in Cairo by searching for similar usage patterns

# Search for power function usage in Cairo files
rg -A 2 -B 2 '\.pow\(' --type cairo || rg -A 2 -B 2 '\.pow\(' -g '*.cairo'

# Also search for u256 power operations specifically
ast-grep --pattern 'u256.pow($_)'

Length of output: 800


To confirm whether to_usd needs a mutable self, let’s inspect the signature and implementation of get_price:


🏁 Script executed:

#!/bin/bash
# Find get_price definition in Cairo contracts
rg -R "fn get_price" -g "*.cairo" -A 5 -B 2

Length of output: 450


🏁 Script executed:

#!/bin/bash
# Display the top of OracleManager to inspect imports and type definitions
head -n 20 src/contracts/OracleManager.cairo

# Locate the get_price function signature and receiver type
rg "fn get_price" -g "*.cairo" -A5 -B2

Length of output: 2101


No mutable receiver needed for to_usd

  • The implementation of get_price only reads from storage (uses .read), so leaving to_usd(self: @ContractState, …) immutable is correct.
  • The u256.pow(u8) method is part of Cairo’s u256 utility and the syntax 10u256.pow(…) is valid.

No changes required.

Applicant, ApplicationStatus, ExperienceLevel, Job, JobCategory, JobDuration, Status,
};
use starkhive_contract::interfaces::IJobs::IJobs;
use starkhive_contract::interfaces::IOracleManager::IOracleManagerTrait;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add missing OracleStatus import

The code uses OracleStatus enum on lines 141, 444, and 470, but it's not imported. This will cause a compilation error.

-use starkhive_contract::interfaces::IOracleManager::IOracleManagerTrait;
+use starkhive_contract::interfaces::IOracleManager::{IOracleManagerTrait, OracleStatus};
📝 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
use starkhive_contract::interfaces::IOracleManager::IOracleManagerTrait;
use starkhive_contract::interfaces::IOracleManager::{IOracleManagerTrait, OracleStatus};
🤖 Prompt for AI Agents
In src/contracts/Jobs.cairo at line 10, the OracleStatus enum is used later in
the file but not imported, causing compilation errors. Add an import statement
for OracleStatus from its defining module alongside the existing imports to
ensure it is recognized and the code compiles successfully.

Comment on lines +115 to +118
fn set_oracle_manager(ref self: ContractState, oracle_manager: OracleManagerAddress) {
// TODO: add admin check
self.oracle_manager.write(oracle_manager);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Implement access control for set_oracle_manager

The function lacks access control, allowing anyone to change the oracle manager address. This is a critical security vulnerability that could lead to price manipulation.

Implement admin access control:

 fn set_oracle_manager(ref self: ContractState, oracle_manager: OracleManagerAddress) {
-    // TODO: add admin check
+    let caller = get_caller_address();
+    assert(caller == self.admin.read(), 'Unauthorized: admin only');
     self.oracle_manager.write(oracle_manager);
 }

Note: You'll need to add an admin storage variable and initialization logic if not already present.

📝 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
fn set_oracle_manager(ref self: ContractState, oracle_manager: OracleManagerAddress) {
// TODO: add admin check
self.oracle_manager.write(oracle_manager);
}
fn set_oracle_manager(ref self: ContractState, oracle_manager: OracleManagerAddress) {
let caller = get_caller_address();
assert(caller == self.admin.read(), 'Unauthorized: admin only');
self.oracle_manager.write(oracle_manager);
}
🤖 Prompt for AI Agents
In src/contracts/Jobs.cairo around lines 115 to 118, the set_oracle_manager
function lacks access control, allowing any caller to change the oracle manager
address. To fix this, add an admin storage variable to the contract state and
initialize it appropriately. Then, implement a check in set_oracle_manager to
verify that only the admin can call this function before updating the
oracle_manager. This will enforce proper access control and prevent unauthorized
changes.

location: felt252,
) -> u256 {
// Normalize token address to symbol for OracleManager (assume token symbol is passed as felt252 for now)
let token_symbol: felt252 = token.into(); // TODO: Replace with actual mapping/token registry if needed
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect token symbol conversion

Converting a contract address to felt252 doesn't produce a valid token symbol. This will cause oracle lookups to fail.

Consider implementing a token registry mapping or passing the token symbol as a parameter:

 fn create_job(
     ref self: ContractState,
     token: ContractAddress,
+    token_symbol: felt252,
     title: felt252,
     // ... other parameters
 ) -> u256 {
-    let token_symbol: felt252 = token.into(); // TODO: Replace with actual mapping/token registry if needed
     let oracle_manager_addr = self.oracle_manager.read();

Alternatively, implement a token registry that maps addresses to symbols.

📝 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
let token_symbol: felt252 = token.into(); // TODO: Replace with actual mapping/token registry if needed
fn create_job(
ref self: ContractState,
token: ContractAddress,
token_symbol: felt252,
title: felt252,
// ... other parameters
) -> u256 {
let oracle_manager_addr = self.oracle_manager.read();
// ...
}
🤖 Prompt for AI Agents
In src/contracts/Jobs.cairo at line 136, the current code incorrectly converts a
contract address to a felt252 token symbol, which is invalid and causes oracle
lookup failures. To fix this, replace the direct conversion with a proper token
registry mapping that maps token contract addresses to their corresponding
symbols, or modify the function to accept the token symbol as a parameter
instead of deriving it from the address.

Comment on lines +130 to +134
// --- Read-only: Expose Current Exchange Rate ---
#[view]
fn get_exchange_rate(self: @ContractState, token: felt252) -> (u256, u8, OracleStatus) {
self.get_price(token)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Architectural issue: View function calls state-modifying function.

The get_exchange_rate view function calls get_price which modifies storage. This violates the view function contract.

Consider separating the price retrieval logic:

  1. Create an internal _get_price_readonly function that doesn't update storage
  2. Use it in view functions like get_exchange_rate
  3. Keep get_price for external calls that need to update the last price/timestamp

Would you like me to provide a refactored implementation that properly separates read-only and state-modifying operations?

🤖 Prompt for AI Agents
In src/contracts/OracleManager.cairo around lines 130 to 134, the view function
get_exchange_rate calls get_price, which modifies storage, violating the view
function contract. To fix this, create a new internal function
_get_price_readonly that retrieves the price without modifying storage, then
update get_exchange_rate to call _get_price_readonly instead. Keep get_price
unchanged for external calls that update state. This separation ensures view
functions remain read-only.

Comment on lines +5 to +10
use starknet::ContractAddress;
use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess};
use starknet::{get_block_timestamp};
use core::option::OptionTrait;
use core::array::ArrayTrait;

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add missing import for get_caller_address.

The contract uses get_caller_address() in the admin functions but doesn't import it.

Add this import:

 use starknet::ContractAddress;
 use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess};
-use starknet::{get_block_timestamp};
+use starknet::{get_block_timestamp, get_caller_address};
 use core::option::OptionTrait;
 use core::array::ArrayTrait;
📝 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
use starknet::ContractAddress;
use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess};
use starknet::{get_block_timestamp};
use core::option::OptionTrait;
use core::array::ArrayTrait;
use starknet::ContractAddress;
use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess};
use starknet::{get_block_timestamp, get_caller_address};
use core::option::OptionTrait;
use core::array::ArrayTrait;
🤖 Prompt for AI Agents
In src/contracts/OracleManager.cairo around lines 5 to 10, the function
get_caller_address() is used but not imported. Add an import statement for
get_caller_address from the starknet module alongside the existing imports to
ensure the function is available in the contract.

Comment on lines +45 to +49
#[constructor]
fn constructor(ref self: ContractState, admin: ContractAddress, stale_threshold: u64) {
self.admin.write(admin);
self.stale_threshold.write(stale_threshold);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Validate admin address in constructor.

Add validation to ensure the admin address is not zero to prevent contract lockout.

 #[constructor]
 fn constructor(ref self: ContractState, admin: ContractAddress, stale_threshold: u64) {
+    assert(admin != 0.try_into().unwrap(), 'invalid_admin_address');
     self.admin.write(admin);
     self.stale_threshold.write(stale_threshold);
 }

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

🤖 Prompt for AI Agents
In src/contracts/OracleManager.cairo around lines 45 to 49, the constructor does
not validate the admin address, which could lead to contract lockout if a zero
address is provided. Add a check at the start of the constructor to ensure the
admin address is not zero, and revert or raise an error if it is, before writing
the admin value to storage.

Comment on lines +77 to +109
// --- External: Get Price (with Fallback) ---
#[external]
fn get_price(self: @ContractState, token: felt252) -> (u256, u8, OracleStatus) {
let feed = self.chainlink_feeds.read(token);
let (price, decimals, valid) = get_chainlink_price(feed);
let now = get_block_timestamp();
let mut status = OracleStatus::Missing;
let mut final_price = price;
let mut final_decimals = decimals;

if valid && price > 0 {
let last = self.last_update.read(token);
let threshold = self.stale_threshold.read();
if now - last <= threshold {
status = OracleStatus::Ok;
self.last_price.write(token, price);
self.last_update.write(token, now);
emit PriceFeedUpdated(token, price, decimals, 'chainlink');
} else {
status = OracleStatus::Stale;
}
} else {
// Fallback to manual price
let fallback = self.fallback_prices.read(token);
if fallback > 0 {
final_price = fallback;
final_decimals = 18;
status = OracleStatus::Fallback;
emit PriceFeedUpdated(token, fallback, 18, 'fallback');
}
}
(final_price, final_decimals, status)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix function signature and staleness check logic.

The function has two critical issues:

  1. It takes an immutable reference but attempts to write to storage
  2. The staleness check logic is inverted

Apply this fix:

 #[external]
-fn get_price(self: @ContractState, token: felt252) -> (u256, u8, OracleStatus) {
+fn get_price(ref self: ContractState, token: felt252) -> (u256, u8, OracleStatus) {
     let feed = self.chainlink_feeds.read(token);
     let (price, decimals, valid) = get_chainlink_price(feed);
     let now = get_block_timestamp();
     let mut status = OracleStatus::Missing;
     let mut final_price = price;
     let mut final_decimals = decimals;

     if valid && price > 0 {
         let last = self.last_update.read(token);
         let threshold = self.stale_threshold.read();
-        if now - last <= threshold {
+        if now - last <= threshold {
             status = OracleStatus::Ok;
             self.last_price.write(token, price);
             self.last_update.write(token, now);
             emit PriceFeedUpdated(token, price, decimals, 'chainlink');
         } else {
             status = OracleStatus::Stale;
         }
📝 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
// --- External: Get Price (with Fallback) ---
#[external]
fn get_price(self: @ContractState, token: felt252) -> (u256, u8, OracleStatus) {
let feed = self.chainlink_feeds.read(token);
let (price, decimals, valid) = get_chainlink_price(feed);
let now = get_block_timestamp();
let mut status = OracleStatus::Missing;
let mut final_price = price;
let mut final_decimals = decimals;
if valid && price > 0 {
let last = self.last_update.read(token);
let threshold = self.stale_threshold.read();
if now - last <= threshold {
status = OracleStatus::Ok;
self.last_price.write(token, price);
self.last_update.write(token, now);
emit PriceFeedUpdated(token, price, decimals, 'chainlink');
} else {
status = OracleStatus::Stale;
}
} else {
// Fallback to manual price
let fallback = self.fallback_prices.read(token);
if fallback > 0 {
final_price = fallback;
final_decimals = 18;
status = OracleStatus::Fallback;
emit PriceFeedUpdated(token, fallback, 18, 'fallback');
}
}
(final_price, final_decimals, status)
}
// --- External: Get Price (with Fallback) ---
#[external]
fn get_price(ref self: ContractState, token: felt252) -> (u256, u8, OracleStatus) {
let feed = self.chainlink_feeds.read(token);
let (price, decimals, valid) = get_chainlink_price(feed);
let now = get_block_timestamp();
let mut status = OracleStatus::Missing;
let mut final_price = price;
let mut final_decimals = decimals;
if valid && price > 0 {
let last = self.last_update.read(token);
let threshold = self.stale_threshold.read();
if now - last <= threshold {
status = OracleStatus::Ok;
self.last_price.write(token, price);
self.last_update.write(token, now);
emit PriceFeedUpdated(token, price, decimals, 'chainlink');
} else {
status = OracleStatus::Stale;
}
} else {
// Fallback to manual price
let fallback = self.fallback_prices.read(token);
if fallback > 0 {
final_price = fallback;
final_decimals = 18;
status = OracleStatus::Fallback;
emit PriceFeedUpdated(token, fallback, 18, 'fallback');
}
}
(final_price, final_decimals, status)
}
🤖 Prompt for AI Agents
In src/contracts/OracleManager.cairo lines 77 to 109, the get_price function
incorrectly uses an immutable reference for self while writing to storage, and
the staleness check logic is inverted. Change the function signature to take a
mutable reference to self. Then, invert the staleness condition to check if now
- last is greater than the threshold to mark the price as stale. Adjust the
logic so that if the price is stale, status is set to Stale without updating
storage; otherwise, update last_price and last_update and set status to Ok.

@Cybermaxi7 Cybermaxi7 merged commit 5f1177c into StarkHive:main Jun 5, 2025
1 of 3 checks passed
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.

Chainlink Price Feed Integration

1 participant