Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 208 additions & 0 deletions src/DjedTefnut.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// SPDX-License-Identifier: AEL
pragma solidity ^0.8.0;

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {Coin} from "./Coin.sol";
import {IOracleShu} from "./IOracleShu.sol";

Comment on lines +1 to +8
Copy link

@coderabbitai coderabbitai bot Dec 13, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

SPDX identifier AEL may be invalid for tooling/verification

If AEL isn’t a recognized SPDX license identifier in your pipeline/tooling, you’ll get warnings or verification friction. Consider switching to a standard SPDX id (or UNLICENSED) consistent with the repo’s licensing.

🤖 Prompt for AI Agents
In src/DjedTefnut.sol around lines 1 to 8, the SPDX license identifier at the
top is set to "AEL", which may not be a recognized SPDX identifier and can
trigger tooling/verification warnings; update the SPDX header to a valid SPDX
identifier used by the repo (for example "MIT", "GPL-3.0-or-later", etc.) or use
"UNLICENSED" if the code is not intended to be published, and ensure it exactly
matches the SPDX identifier casing and syntax (SPDX-License-Identifier: <ID>).

Copy link
Author

Choose a reason for hiding this comment

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

Isnt this incorrect?

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🌐 Web query:

AEL SPDX license identifier

💡 Result:

Do you mean a specific license abbreviated “AEL”? If so, what does AEL stand for (full license name or a link)? I can then look up the correct SPDX identifier.


🌐 Web query:

official SPDX license list identifiers 2025

💡 Result:

The official SPDX license identifiers are published on the SPDX License List page and in the SPDX license-list-data repository (machine-readable JSON/XML). Use the licenses page for the human-readable table and the GitHub repo (licenses.json / exceptions.json) for authoritative identifiers you can consume programmatically. Current references:

  • SPDX License List (web page, version 3.27.0, 2025). [1]
  • SPDX license-list-data (GitHub) — machine-readable files (json/xml) containing all identifiers. [2]

If you want the full JSON file of identifiers, download licenses.json from the license-list-data repo. [2]

Sources
[1] SPDX: SPDX License List — https://spdx.org/licenses/
[2] spdx/license-list-data — https://github.com/spdx/license-list-data


🏁 Script executed:

#!/bin/bash
# Fetch the official SPDX license list and check if "AEL" is a valid identifier
curl -s https://raw.githubusercontent.com/spdx/license-list-data/main/json/licenses.json | jq -r '.licenses[] | .licenseId' | grep -i "^AEL$"

Length of output: 0


@negimox, I've checked the official SPDX license list (version 3.27.0), and "AEL" is not a recognized standard SPDX license identifier. So CodeRabbit's warning is technically accurate from a standards perspective.

However, if "AEL" is a custom or proprietary license identifier specific to your project (such as "All Emcompassing License" or similar), then using it may be intentional. Could you clarify:

  1. What does "AEL" stand for in your project's context?
  2. Is this a custom license identifier you're using intentionally?

If it's a custom identifier and intentional, you can safely ignore the warning. If you meant to use a standard SPDX identifier, I can help you find the appropriate one. Alternatively, if the code is proprietary/closed-source, you might want to use UNLICENSED instead.

contract DjedTefnut is ReentrancyGuard {
IOracleShu public oracle;
Coin public stableCoin;
Coin public reserveCoin;

// Treasury Parameters:
address public immutable TREASURY; // address of the treasury
uint256 public immutable TREASURY_FEE; // fixed treasury fee (no decay)
uint256 public treasuryRevenue = 0; // holds how much has already been paid to the treasury // Mutable state variable
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for any functions that read or modify treasuryRevenue
rg -n 'treasuryRevenue' --type sol -C3

Repository: DjedAlliance/Djed-Solidity

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Search for treasuryRevenue without type filter
rg -n 'treasuryRevenue' -C3

Repository: DjedAlliance/Djed-Solidity

Length of output: 16335


🏁 Script executed:

#!/bin/bash
# Check if treasuryRevenue is read anywhere in DjedTefnut.sol
# (aside from the declaration and increment we already found)
cat -n src/DjedTefnut.sol | grep -E '(treasuryRevenue|treasuryFee)'

Repository: DjedAlliance/Djed-Solidity

Length of output: 855


Add clarifying comment explaining treasuryRevenue is for external monitoring only.

The treasuryRevenue variable accumulates treasury fees but is never used in contract logic (unlike Djed.sol and DjedShu.sol where it drives dynamic fee calculations). Since it has no functional purpose beyond tracking, the existing inline comment should clarify that this variable is public for off-chain monitoring only and will never be reset.

🤖 Prompt for AI Agents
In src/DjedTefnut.sol around line 17, the inline comment for uint256 public
treasuryRevenue currently notes it holds amounts paid to the treasury but lacks
clarity about its purpose; update the comment on that line to state explicitly
that treasuryRevenue is a read-only accumulator exposed publicly for off-chain
monitoring/analytics only, is not used in contract logic or fee calculations,
and will never be reset so consumers should treat it as a cumulative metric.


// Djed Parameters:
uint256 public immutable FEE;
uint256 public immutable THRESHOLD_SUPPLY_SC;
uint256 public immutable RC_MIN_PRICE;
uint256 public immutable RC_INITIAL_PRICE;
uint256 public immutable TX_LIMIT;

// Scaling factors:
uint256 public immutable SCALING_FACTOR; // used to represent a decimal number `d` as the uint number `d * SCALING_FACTOR`
uint256 public immutable SC_DECIMAL_SCALING_FACTOR;
uint256 public immutable RC_DECIMAL_SCALING_FACTOR;

event BoughtStableCoins(address indexed buyer, address indexed receiver, uint256 amountSc, uint256 amountBc);
event SoldStableCoins(address indexed seller, address indexed receiver, uint256 amountSc, uint256 amountBc);
event BoughtReserveCoins(address indexed buyer, address indexed receiver, uint256 amountRc, uint256 amountBc);
event SoldReserveCoins(address indexed seller, address indexed receiver, uint256 amountRc, uint256 amountBc);

constructor(
address oracleAddress,
uint256 scalingFactor,
address treasury,
uint256 treasuryFee,
uint256 fee,
uint256 thresholdSupplySc,
uint256 rcMinPrice,
uint256 rcInitialPrice,
uint256 txLimit
) payable {
stableCoin = new Coin("StableCoin", "SC");
reserveCoin = new Coin("ReserveCoin", "RC");
SC_DECIMAL_SCALING_FACTOR = 10 ** stableCoin.decimals();
RC_DECIMAL_SCALING_FACTOR = 10 ** reserveCoin.decimals();
SCALING_FACTOR = scalingFactor;

TREASURY = treasury;
TREASURY_FEE = treasuryFee;

FEE = fee;
THRESHOLD_SUPPLY_SC = thresholdSupplySc;
RC_MIN_PRICE = rcMinPrice;
RC_INITIAL_PRICE = rcInitialPrice;
TX_LIMIT = txLimit;

oracle = IOracleShu(oracleAddress);
oracle.acceptTermsOfService();
}

// Reserve, Liabilities, Equity (in weis) and Reserve Ratio
function R(uint256 currentPaymentAmount) public view returns (uint256) {
return address(this).balance - currentPaymentAmount;
}

function L(uint256 _scPrice) internal view returns (uint256) {
return (stableCoin.totalSupply() * _scPrice) / SC_DECIMAL_SCALING_FACTOR;
}

function L() external view returns (uint256) {
return L(scMaxPrice(0));
}

function E(uint256 _scPrice, uint256 currentPaymentAmount) internal view returns (uint256) {
return R(currentPaymentAmount) - L(_scPrice);
}

function E(uint256 currentPaymentAmount) external view returns (uint256) {
return E(scMaxPrice(currentPaymentAmount), currentPaymentAmount);
}

// Ratio functions kept for informational purposes only (no longer restrict transactions)
function ratio() external view returns (uint256) {
uint256 liabilities = L(scMaxPrice(0));
if (liabilities == 0) return type(uint256).max;
return SCALING_FACTOR * R(0) / liabilities;
}

// # Public Trading Functions:
// scMaxPrice
function buyStableCoins(address receiver, uint256 feeUi, address ui) external payable nonReentrant {
oracle.updateOracleValues();
uint256 scP = scMaxPrice(msg.value);
uint256 amountBc = deductFees(msg.value, feeUi, ui); // side-effect: increases `treasuryRevenue` and pays UI and treasury
uint256 amountSc = (amountBc * SC_DECIMAL_SCALING_FACTOR) / scP;
require(amountSc <= TX_LIMIT || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "buySC: tx limit exceeded");
require(amountSc > 0, "buySC: receiving zero SCs");
stableCoin.mint(receiver, amountSc);
// Reserve ratio check removed in Tefnut
emit BoughtStableCoins(msg.sender, receiver, amountSc, msg.value);
}
Comment on lines 104 to 114
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add slippage protection for user safety.

The buyStableCoins function lacks slippage protection—users cannot specify a minimum amount of stable coins they're willing to accept. If the oracle price changes between transaction submission and execution, users could receive significantly fewer coins than expected.

-function buyStableCoins(address receiver, uint256 feeUi, address ui) external payable nonReentrant {
+function buyStableCoins(address receiver, uint256 feeUi, address ui, uint256 minAmountSc) external payable nonReentrant {
     oracle.updateOracleValues();
     uint256 scP = scMaxPrice(msg.value);
     uint256 amountBc = deductFees(msg.value, feeUi, ui);
     uint256 amountSc = (amountBc * SC_DECIMAL_SCALING_FACTOR) / scP;
+    require(amountSc >= minAmountSc, "buySC: slippage exceeded");
     require(amountSc <= TX_LIMIT || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "buySC: tx limit exceeded");
📝 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
function buyStableCoins(address receiver, uint256 feeUi, address ui) external payable nonReentrant {
oracle.updateOracleValues();
uint256 scP = scMaxPrice(msg.value);
uint256 amountBc = deductFees(msg.value, feeUi, ui); // side-effect: increases `treasuryRevenue` and pays UI and treasury
uint256 amountSc = (amountBc * SC_DECIMAL_SCALING_FACTOR) / scP;
require(amountSc <= TX_LIMIT || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "buySC: tx limit exceeded");
require(amountSc > 0, "buySC: receiving zero SCs");
stableCoin.mint(receiver, amountSc);
// Reserve ratio check removed in Tefnut
emit BoughtStableCoins(msg.sender, receiver, amountSc, msg.value);
}
function buyStableCoins(address receiver, uint256 feeUi, address ui, uint256 minAmountSc) external payable nonReentrant {
oracle.updateOracleValues();
uint256 scP = scMaxPrice(msg.value);
uint256 amountBc = deductFees(msg.value, feeUi, ui); // side-effect: increases `treasuryRevenue` and pays UI and treasury
uint256 amountSc = (amountBc * SC_DECIMAL_SCALING_FACTOR) / scP;
require(amountSc >= minAmountSc, "buySC: slippage exceeded");
require(amountSc <= TX_LIMIT || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "buySC: tx limit exceeded");
require(amountSc > 0, "buySC: receiving zero SCs");
stableCoin.mint(receiver, amountSc);
// Reserve ratio check removed in Tefnut
emit BoughtStableCoins(msg.sender, receiver, amountSc, msg.value);
}
🤖 Prompt for AI Agents
In src/DjedTefnut.sol around lines 96 to 106, the buyStableCoins function lacks
slippage protection; add a new uint256 parameter minAmountSc to the function
signature, compute amountSc as before, then require(amountSc >= minAmountSc,
"buySC: slippage exceeded") before the TX_LIMIT and zero checks (or combine
logically), and only mint/emit after that check; update any internal calls/tests
to pass the new parameter and document the behavior so users can specify a
minimum acceptable SC amount to protect against oracle price movement.


function sellStableCoins(uint256 amountSc, address receiver, uint256 feeUi, address ui) external nonReentrant {
oracle.updateOracleValues();
require(stableCoin.balanceOf(msg.sender) >= amountSc, "sellSC: insufficient SC balance");
require(amountSc <= TX_LIMIT || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "sellSC: tx limit exceeded");
uint256 scP = scMinPrice(0);
uint256 value = (amountSc * scP) / SC_DECIMAL_SCALING_FACTOR;
uint256 amountBc = deductFees(value, feeUi, ui); // side-effect: increases `treasuryRevenue` and pays UI and treasury
require(amountBc > 0, "sellSC: receiving zero BCs");
stableCoin.burn(msg.sender, amountSc);
transferEth(receiver, amountBc);
emit SoldStableCoins(msg.sender, receiver, amountSc, amountBc);
}
Comment on lines 116 to 127
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add slippage protection and consider transfer failure handling.

Similar to buyStableCoins, this function lacks slippage protection. Additionally, the ETH transfer on line 117 could fail if receiver is a contract that reverts on ETH receipt, causing the entire transaction (including the burn) to revert and potentially locking user funds if they retry.

Add a minimum received amount parameter:

-function sellStableCoins(uint256 amountSc, address receiver, uint256 feeUi, address ui) external nonReentrant {
+function sellStableCoins(uint256 amountSc, address receiver, uint256 feeUi, address ui, uint256 minAmountBc) external nonReentrant {
     oracle.updateOracleValues();
     require(stableCoin.balanceOf(msg.sender) >= amountSc, "sellSC: insufficient SC balance");
     require(amountSc <= TX_LIMIT || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "sellSC: tx limit exceeded");
     uint256 scP = scMinPrice(0);
     uint256 value = (amountSc * scP) / SC_DECIMAL_SCALING_FACTOR;
     uint256 amountBc = deductFees(value, feeUi, ui);
+    require(amountBc >= minAmountBc, "sellSC: slippage exceeded");
     require(amountBc > 0, "sellSC: receiving zero BCs");
📝 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
function sellStableCoins(uint256 amountSc, address receiver, uint256 feeUi, address ui) external nonReentrant {
oracle.updateOracleValues();
require(stableCoin.balanceOf(msg.sender) >= amountSc, "sellSC: insufficient SC balance");
require(amountSc <= TX_LIMIT || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "sellSC: tx limit exceeded");
uint256 scP = scMinPrice(0);
uint256 value = (amountSc * scP) / SC_DECIMAL_SCALING_FACTOR;
uint256 amountBc = deductFees(value, feeUi, ui); // side-effect: increases `treasuryRevenue` and pays UI and treasury
require(amountBc > 0, "sellSC: receiving zero BCs");
stableCoin.burn(msg.sender, amountSc);
transferEth(receiver, amountBc);
emit SoldStableCoins(msg.sender, receiver, amountSc, amountBc);
}
function sellStableCoins(uint256 amountSc, address receiver, uint256 feeUi, address ui, uint256 minAmountBc) external nonReentrant {
oracle.updateOracleValues();
require(stableCoin.balanceOf(msg.sender) >= amountSc, "sellSC: insufficient SC balance");
require(amountSc <= TX_LIMIT || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "sellSC: tx limit exceeded");
uint256 scP = scMinPrice(0);
uint256 value = (amountSc * scP) / SC_DECIMAL_SCALING_FACTOR;
uint256 amountBc = deductFees(value, feeUi, ui); // side-effect: increases `treasuryRevenue` and pays UI and treasury
require(amountBc >= minAmountBc, "sellSC: slippage exceeded");
require(amountBc > 0, "sellSC: receiving zero BCs");
stableCoin.burn(msg.sender, amountSc);
transferEth(receiver, amountBc);
emit SoldStableCoins(msg.sender, receiver, amountSc, amountBc);
}


function buyReserveCoins(address receiver, uint256 feeUi, address ui) external payable nonReentrant {
oracle.updateOracleValues();
uint256 scP = scMinPrice(msg.value);
uint256 rcBp = rcBuyingPrice(scP, msg.value);
uint256 amountBc = deductFees(msg.value, feeUi, ui); // side-effect: increases `treasuryRevenue` and pays UI and treasury
require(amountBc <= (TX_LIMIT * scP) / SC_DECIMAL_SCALING_FACTOR || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "buyRC: tx limit exceeded");
uint256 amountRc = (amountBc * RC_DECIMAL_SCALING_FACTOR) / rcBp;
require(amountRc > 0, "buyRC: receiving zero RCs");
reserveCoin.mint(receiver, amountRc);
// Reserve ratio check removed in Tefnut
emit BoughtReserveCoins(msg.sender, receiver, amountRc, msg.value);
}

function sellReserveCoins(uint256 amountRc, address receiver, uint256 feeUi, address ui) external nonReentrant {
oracle.updateOracleValues();
require(reserveCoin.balanceOf(msg.sender) >= amountRc, "sellRC: insufficient RC balance");
uint256 scP = scMaxPrice(0);
uint256 value = (amountRc * rcTargetPrice(scP, 0)) / RC_DECIMAL_SCALING_FACTOR;
require(value <= (TX_LIMIT * scP) / SC_DECIMAL_SCALING_FACTOR || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "sellRC: tx limit exceeded");
uint256 amountBc = deductFees(value, feeUi, ui); // side-effect: increases `treasuryRevenue` and pays UI and treasury
require(amountBc > 0, "sellRC: receiving zero BCs");
reserveCoin.burn(msg.sender, amountRc);
transferEth(receiver, amountBc);
// Reserve ratio check removed in Tefnut
emit SoldReserveCoins(msg.sender, receiver, amountRc, amountBc);
}
Comment on lines 142 to 154
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add slippage protection and fix TX_LIMIT inconsistency.

The TX_LIMIT check on line 139 uses value (before fees), while buyReserveCoins line 126 checks amountBc (after fees). This inconsistency could allow bypass of limits or incorrect enforcement. Additionally, slippage protection is missing.

-function sellReserveCoins(uint256 amountRc, address receiver, uint256 feeUi, address ui) external nonReentrant {
+function sellReserveCoins(uint256 amountRc, address receiver, uint256 feeUi, address ui, uint256 minAmountBc) external nonReentrant {
     oracle.updateOracleValues();
     require(reserveCoin.balanceOf(msg.sender) >= amountRc, "sellRC: insufficient RC balance");
     uint256 scP = scMaxPrice(0);
     uint256 value = (amountRc * rcTargetPrice(scP, 0)) / RC_DECIMAL_SCALING_FACTOR;
-    require(value <= (TX_LIMIT * scP) / SC_DECIMAL_SCALING_FACTOR || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "sellRC: tx limit exceeded");
     uint256 amountBc = deductFees(value, feeUi, ui);
+    require(amountBc <= (TX_LIMIT * scP) / SC_DECIMAL_SCALING_FACTOR || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "sellRC: tx limit exceeded");
+    require(amountBc >= minAmountBc, "sellRC: slippage exceeded");
     require(amountBc > 0, "sellRC: receiving zero BCs");
🤖 Prompt for AI Agents
In src/DjedTefnut.sol around lines 134-146, the TX_LIMIT is checked against
pre-fee `value` and slippage protection is missing; change the flow to compute
`amountBc` after calling deductFees, then enforce TX_LIMIT against `amountBc`
(matching buyReserveCoins behavior) and add a slippage-protection input (e.g.,
minAmountBc) to the function signature, requiring amountBc >= minAmountBc before
burning and transferring; also move the reserveCoin.burn to after these checks
and keep the emitted event intact. Ensure the buyReserveCoins path uses the same
post-fee TX_LIMIT logic for consistency.


// sellBothCoins function removed in Tefnut

// # Auxiliary Functions

function deductFees(uint256 value, uint256 feeUi, address ui) internal returns (uint256) {
uint256 f = (value * FEE) / SCALING_FACTOR;
uint256 fUi = (value * feeUi) / SCALING_FACTOR;
uint256 fT = (value * TREASURY_FEE) / SCALING_FACTOR; // Fixed treasury fee (no decay)
treasuryRevenue += fT;
transferEth(TREASURY, fT);
transferEth(ui, fUi);
// transferEth(address(this), f); // this happens implicitly, and thus `f` is effectively transferred to the reserve.
return value - f - fUi - fT; // amountBc
}
Comment on lines 160 to 170
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate fees and protect against DoS via malicious UI address.

The function has several concerns:

  1. DoS Risk: If ui is a contract that reverts on ETH receipt, all trading would fail. Validate ui != address(0) and consider wrapping transfers in try/catch.

  2. Fee Validation: No runtime check that feeUi + FEE + TREASURY_FEE <= SCALING_FACTOR. If the sum exceeds 100%, line 160 would underflow and revert (in 0.8.x). Add validation in the constructor or here.

  3. Checks-Effects-Interactions: State change (line 156) occurs before external calls (lines 157-158). While protected by nonReentrant, follow CEI pattern for defense in depth.

 function deductFees(uint256 value, uint256 feeUi, address ui) internal returns (uint256) {
+    require(feeUi + FEE + TREASURY_FEE <= SCALING_FACTOR, "Total fees exceed 100%");
     uint256 f = (value * FEE) / SCALING_FACTOR;
     uint256 fUi = (value * feeUi) / SCALING_FACTOR;
     uint256 fT = (value * TREASURY_FEE) / SCALING_FACTOR;
     treasuryRevenue += fT;
-    transferEth(TREASURY, fT);
-    transferEth(ui, fUi);
+    if (fT > 0) {
+        transferEth(TREASURY, fT);
+    }
+    if (fUi > 0 && ui != address(0)) {
+        transferEth(ui, fUi);
+    }
     // transferEth(address(this), f); // this happens implicitly, and thus `f` is effectively transferred to the reserve.
     return value - f - fUi - fT; // amountBc
 }
📝 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
function deductFees(uint256 value, uint256 feeUi, address ui) internal returns (uint256) {
uint256 f = (value * FEE) / SCALING_FACTOR;
uint256 fUi = (value * feeUi) / SCALING_FACTOR;
uint256 fT = (value * TREASURY_FEE) / SCALING_FACTOR; // Fixed treasury fee (no decay)
treasuryRevenue += fT;
transferEth(TREASURY, fT);
transferEth(ui, fUi);
// transferEth(address(this), f); // this happens implicitly, and thus `f` is effectively transferred to the reserve.
return value - f - fUi - fT; // amountBc
}
function deductFees(uint256 value, uint256 feeUi, address ui) internal returns (uint256) {
require(feeUi + FEE + TREASURY_FEE <= SCALING_FACTOR, "Total fees exceed 100%");
uint256 f = (value * FEE) / SCALING_FACTOR;
uint256 fUi = (value * feeUi) / SCALING_FACTOR;
uint256 fT = (value * TREASURY_FEE) / SCALING_FACTOR; // Fixed treasury fee (no decay)
treasuryRevenue += fT;
if (fT > 0) {
transferEth(TREASURY, fT);
}
if (fUi > 0 && ui != address(0)) {
transferEth(ui, fUi);
}
// transferEth(address(this), f); // this happens implicitly, and thus `f` is effectively transferred to the reserve.
return value - f - fUi - fT; // amountBc
}
🤖 Prompt for AI Agents
In src/DjedTefnut.sol around lines 152–161, validate and harden fee handling by
(1) enforcing ui != address(0) at the start of deductFees (or at call sites) to
prevent trivial DoS, (2) ensuring feeUi + FEE + TREASURY_FEE <= SCALING_FACTOR
(preferably validated once in the constructor and re-checked here defensively),
(3) compute all fee amounts first, update state (treasuryRevenue) before
performing external transfers to follow CEI, and (4) avoid DoS from a reverting
ui by using a low-level call for the ui transfer and handling failure (e.g., on
failure route fUi to TREASURY or record it in treasuryRevenue) so a malicious UI
contract cannot block the function. Ensure any transfer failures are handled
deterministically (not left to revert) and log or record failures where
appropriate.


// isRatioAboveMin and isRatioBelowMax functions removed in Tefnut

// # Price Functions: return the price in weis for 1 whole coin.

function scPrice(uint256 currentPaymentAmount, uint256 scTargetPrice) private view returns (uint256) {
uint256 supplySc = stableCoin.totalSupply();
return supplySc == 0
? scTargetPrice
: Math.min(scTargetPrice, (R(currentPaymentAmount) * SC_DECIMAL_SCALING_FACTOR) / supplySc);
}

function scMaxPrice(uint256 currentPaymentAmount) public view returns (uint256) {
(uint256 scTargetPrice,) = oracle.readMaxPrice();
return scPrice(currentPaymentAmount, scTargetPrice);
}

function scMinPrice(uint256 currentPaymentAmount) public view returns (uint256) {
(uint256 scTargetPrice,) = oracle.readMinPrice();
return scPrice(currentPaymentAmount, scTargetPrice);
}

function rcTargetPrice(uint256 currentPaymentAmount) external view returns (uint256) {
return rcTargetPrice(scMaxPrice(currentPaymentAmount), currentPaymentAmount);
}

function rcTargetPrice(uint256 _scPrice, uint256 currentPaymentAmount) internal view returns (uint256) {
uint256 supplyRc = reserveCoin.totalSupply();
require(supplyRc != 0, "RC supply is zero");
return (E(_scPrice, currentPaymentAmount) * RC_DECIMAL_SCALING_FACTOR) / supplyRc;
}

function rcBuyingPrice(uint256 currentPaymentAmount) external view returns (uint256) {
return rcBuyingPrice(scMaxPrice(currentPaymentAmount), currentPaymentAmount);
}

function rcBuyingPrice(uint256 _scPrice, uint256 currentPaymentAmount) internal view returns (uint256) {
return reserveCoin.totalSupply() == 0
? RC_INITIAL_PRICE
: Math.max(rcTargetPrice(_scPrice, currentPaymentAmount), RC_MIN_PRICE);
}

function transferEth(address receiver, uint256 amount) internal {
(bool success,) = payable(receiver).call{value: amount}("");
require(success, "Transfer failed.");
}
}