-
Notifications
You must be signed in to change notification settings - Fork 16
feat: Implement Djed Tefnut protocol #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.19; | ||
|
|
||
| import "forge-std/Script.sol"; | ||
| import "./DeploymentParameters.sol"; | ||
| import {DjedTefnut} from "../src/DjedTefnut.sol"; | ||
|
|
||
| contract DeployDjedTefnut is Script, DeploymentParameters { | ||
| function run(SupportedNetworks network, SupportedVersion version) external { | ||
| uint256 senderPrivateKey = vm.envUint("PRIVATE_KEY"); | ||
| vm.startBroadcast(senderPrivateKey); | ||
|
|
||
| ( | ||
| address oracleAddress, | ||
| address treasuryAddress, | ||
| uint256 scalingFactor, | ||
| uint256 treasuryFee, | ||
| , // treasuryRevenueTarget - unused | ||
| , // reserveRatioMin - unused | ||
| , // reserveRatioMax - unused | ||
| uint256 fee, | ||
| uint256 thresholdSupplySc, | ||
| uint256 rcMinPrice, | ||
| uint256 rcInitialPrice, | ||
| uint256 txLimit | ||
| ) = getConfigFromNetwork(network, version); | ||
|
|
||
| DjedTefnut djedTefnut = new DjedTefnut( | ||
| oracleAddress, | ||
| scalingFactor, | ||
| treasuryAddress, | ||
| treasuryFee, | ||
| fee, | ||
| thresholdSupplySc, | ||
| rcMinPrice, | ||
| rcInitialPrice, | ||
| txLimit | ||
| ); | ||
|
|
||
| console.log("DjedTefnut deployed at:", address(djedTefnut)); | ||
| vm.stopBroadcast(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.19; | ||
|
|
||
| import "forge-std/Script.sol"; | ||
| import {DjedTefnut} from "../src/DjedTefnut.sol"; | ||
| import {MockShuOracle} from "../src/mock/MockShuOracle.sol"; | ||
|
|
||
| contract DeployDjedTefnutWithMock is Script { | ||
| function run() external { | ||
| uint256 senderPrivateKey = vm.envUint("PRIVATE_KEY"); | ||
| vm.startBroadcast(senderPrivateKey); | ||
|
|
||
| // Deploy MockShuOracle with initial price of $1 (1e18) | ||
| MockShuOracle oracle = new MockShuOracle(1e18); | ||
| console.log("MockShuOracle deployed at:", address(oracle)); | ||
|
|
||
| // Deploy DjedTefnut with mock oracle | ||
| DjedTefnut djedTefnut = new DjedTefnut( | ||
| address(oracle), // oracle address | ||
| 1e18, // scalingFactor (1.0) | ||
| msg.sender, // treasury address (deployer) | ||
| 100, // treasuryFee (1%) | ||
| 200, // fee (2%) | ||
| 1000e18, // thresholdSupplySc (1000 stable coins) | ||
| 1e15, // rcMinPrice (0.001) | ||
| 1e17, // rcInitialPrice (0.1) | ||
| 100e18 // txLimit (100 ETH) | ||
| ); | ||
|
|
||
| console.log("DjedTefnut deployed at:", address(djedTefnut)); | ||
| vm.stopBroadcast(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,176 @@ | ||
| // 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"; | ||
|
|
||
| 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 fee to fund the treasury | ||
|
|
||
| // 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 * scalingFactor` | ||
| 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) | ||
| function R(uint256 _currentPaymentAmount) public view returns (uint256) { | ||
| return address(this).balance - _currentPaymentAmount; | ||
| } | ||
|
Comment on lines
+60
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Suggested fix: compute prices first, then transfer fees, then mint using the post-fee function buyStableCoins(address receiver, uint256 feeUi, address ui) external payable nonReentrant {
oracle.updateOracleValues();
- uint256 amountBc = deductFees(msg.value, feeUi, ui);
- uint256 amountSc = (amountBc * SC_DECIMAL_SCALING_FACTOR) / scMaxPrice(msg.value);
+ uint256 price = scMaxPrice(msg.value);
+ uint256 amountBc = deductFees(msg.value, feeUi, ui);
+ uint256 amountSc = (amountBc * SC_DECIMAL_SCALING_FACTOR) / price;
require(amountSc <= TX_LIMIT || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "buySC: tx limit exceeded");
require(amountSc > 0, "buySC: receiving zero SCs");
stableCoin.mint(receiver, amountSc);
- emit BoughtStableCoins(msg.sender, receiver, amountSc, msg.value);
+ emit BoughtStableCoins(msg.sender, receiver, amountSc, amountBc);
}
function buyReserveCoins(address receiver, uint256 feeUi, address ui) external payable nonReentrant {
oracle.updateOracleValues();
- uint256 amountBc = deductFees(msg.value, feeUi, ui);
- uint256 minPrice = scMinPrice(msg.value);
+ uint256 minPrice = scMinPrice(msg.value);
+ uint256 rcPrice = rcBuyingPrice(minPrice, msg.value);
+ uint256 amountBc = deductFees(msg.value, feeUi, ui);
require(amountBc <= (TX_LIMIT * minPrice) / SC_DECIMAL_SCALING_FACTOR || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "buyRC: tx limit exceeded");
- uint256 amountRc = (amountBc * RC_DECIMAL_SCALING_FACTOR) / rcBuyingPrice(minPrice, msg.value);
+ uint256 amountRc = (amountBc * RC_DECIMAL_SCALING_FACTOR) / rcPrice;
require(amountRc > 0, "buyRC: receiving zero RCs");
reserveCoin.mint(receiver, amountRc);
- emit BoughtReserveCoins(msg.sender, receiver, amountRc, msg.value);
+ emit BoughtReserveCoins(msg.sender, receiver, amountRc, amountBc);
}
function deductFees(uint256 value, uint256 feeUi, address ui) internal returns (uint256) {
+ if (feeUi > 0) require(ui != address(0), "ui required when feeUi > 0");
uint256 totalFees = ((value * FEE) + (value * feeUi) + (value * TREASURY_FEE)) / SCALING_FACTOR;
transfer(TREASURY, (value * TREASURY_FEE) / SCALING_FACTOR);
transfer(ui, (value * feeUi) / SCALING_FACTOR);
return value - totalFees;
}Also applies to: 81-89, 102-111, 127-132 |
||
|
|
||
| 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); | ||
| } | ||
|
|
||
| // # Public Trading Functions: | ||
| function buyStableCoins(address receiver, uint256 feeUi, address ui) external payable nonReentrant { | ||
| oracle.updateOracleValues(); | ||
| uint256 amountBc = deductFees(msg.value, feeUi, ui); | ||
| uint256 amountSc = (amountBc * SC_DECIMAL_SCALING_FACTOR) / scMaxPrice(msg.value); | ||
| require(amountSc <= TX_LIMIT || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "buySC: tx limit exceeded"); | ||
| require(amountSc > 0, "buySC: receiving zero SCs"); | ||
| stableCoin.mint(receiver, amountSc); | ||
| emit BoughtStableCoins(msg.sender, receiver, amountSc, msg.value); | ||
| } | ||
|
|
||
| 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"); | ||
| stableCoin.burn(msg.sender, amountSc); | ||
| uint256 amountBc = deductFees((amountSc * scMinPrice(0)) / SC_DECIMAL_SCALING_FACTOR, feeUi, ui); | ||
| require(amountBc > 0, "sellSC: receiving zero BCs"); | ||
| transfer(receiver, amountBc); | ||
| emit SoldStableCoins(msg.sender, receiver, amountSc, amountBc); | ||
| } | ||
|
Comment on lines
+91
to
+100
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical:
Suggested fix: compute price (and gross payout) first, then burn, then deduct fees and transfer. 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");
- stableCoin.burn(msg.sender, amountSc);
- uint256 amountBc = deductFees((amountSc * scMinPrice(0)) / SC_DECIMAL_SCALING_FACTOR, feeUi, ui);
+ uint256 price = scMinPrice(0);
+ uint256 grossBc = (amountSc * price) / SC_DECIMAL_SCALING_FACTOR;
+ stableCoin.burn(msg.sender, amountSc);
+ uint256 amountBc = deductFees(grossBc, feeUi, ui);
require(amountBc > 0, "sellSC: receiving zero BCs");
transfer(receiver, amountBc);
emit SoldStableCoins(msg.sender, receiver, amountSc, amountBc);
}
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 maxPrice = scMaxPrice(0);
- require((amountRc * rcTargetPrice(maxPrice, 0)) / RC_DECIMAL_SCALING_FACTOR <= (TX_LIMIT * maxPrice) / SC_DECIMAL_SCALING_FACTOR || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "sellRC: tx limit exceeded");
- reserveCoin.burn(msg.sender, amountRc);
- uint256 amountBc = deductFees((amountRc * rcTargetPrice(maxPrice, 0)) / RC_DECIMAL_SCALING_FACTOR, feeUi, ui);
+ uint256 rcPrice = rcTargetPrice(maxPrice, 0);
+ uint256 grossBc = (amountRc * rcPrice) / RC_DECIMAL_SCALING_FACTOR;
+ require(grossBc <= (TX_LIMIT * maxPrice) / SC_DECIMAL_SCALING_FACTOR || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "sellRC: tx limit exceeded");
+ reserveCoin.burn(msg.sender, amountRc);
+ uint256 amountBc = deductFees(grossBc, feeUi, ui);
require(amountBc > 0, "sellRC: receiving zero BCs");
transfer(receiver, amountBc);
emit SoldReserveCoins(msg.sender, receiver, amountRc, amountBc);
}Also applies to: 113-123, 156-170 |
||
|
|
||
| function buyReserveCoins(address receiver, uint256 feeUi, address ui) external payable nonReentrant { | ||
| oracle.updateOracleValues(); | ||
| uint256 amountBc = deductFees(msg.value, feeUi, ui); | ||
| uint256 minPrice = scMinPrice(msg.value); | ||
| require(amountBc <= (TX_LIMIT * minPrice) / SC_DECIMAL_SCALING_FACTOR || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "buyRC: tx limit exceeded"); | ||
| uint256 amountRc = (amountBc * RC_DECIMAL_SCALING_FACTOR) / rcBuyingPrice(minPrice, msg.value); | ||
| require(amountRc > 0, "buyRC: receiving zero RCs"); | ||
| reserveCoin.mint(receiver, amountRc); | ||
| 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 maxPrice = scMaxPrice(0); | ||
| require((amountRc * rcTargetPrice(maxPrice, 0)) / RC_DECIMAL_SCALING_FACTOR <= (TX_LIMIT * maxPrice) / SC_DECIMAL_SCALING_FACTOR || stableCoin.totalSupply() < THRESHOLD_SUPPLY_SC, "sellRC: tx limit exceeded"); | ||
| reserveCoin.burn(msg.sender, amountRc); | ||
| uint256 amountBc = deductFees((amountRc * rcTargetPrice(maxPrice, 0)) / RC_DECIMAL_SCALING_FACTOR, feeUi, ui); | ||
| require(amountBc > 0, "sellRC: receiving zero BCs"); | ||
| transfer(receiver, amountBc); | ||
| emit SoldReserveCoins(msg.sender, receiver, amountRc, amountBc); | ||
| } | ||
|
|
||
| // # Auxiliary Functions | ||
|
|
||
| function deductFees(uint256 value, uint256 feeUi, address ui) internal returns (uint256) { | ||
| uint256 totalFees = ((value * FEE) + (value * feeUi) + (value * TREASURY_FEE)) / SCALING_FACTOR; | ||
| transfer(TREASURY, (value * TREASURY_FEE) / SCALING_FACTOR); | ||
| transfer(ui, (value * feeUi) / SCALING_FACTOR); | ||
| return value - totalFees; | ||
| } | ||
|
|
||
| // # Price Functions: return the price in weis for 1 whole coin. | ||
|
|
||
| function scPrice(uint256 _currentPaymentAmount, uint256 scTargetPrice) private view returns (uint256) { | ||
| return stableCoin.totalSupply() == 0 | ||
| ? scTargetPrice | ||
| : Math.min(scTargetPrice, (R(_currentPaymentAmount) * SC_DECIMAL_SCALING_FACTOR) / stableCoin.totalSupply()); | ||
| } | ||
|
|
||
| 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) | ||
| { | ||
| require(reserveCoin.totalSupply() != 0, "RC supply is zero"); | ||
| return (E(_scPrice, _currentPaymentAmount) * RC_DECIMAL_SCALING_FACTOR) / reserveCoin.totalSupply(); | ||
| } | ||
|
|
||
| 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 transfer(address receiver, uint256 amount) internal { | ||
| (bool success, ) = payable(receiver).call{value: amount}(""); | ||
| require(success, "Transfer failed."); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix treasury address + align fee units with
scalingFactor(deployment would misconfigure protocol).msg.sender(Line 21) in a Forge script is commonly the script contract address, not the broadcaster EOA; you likely want the broadcast signer address.scalingFactor = 1e18(Line 20),treasuryFee = 100andfee = 200(Line 22-23) don’t represent 1%/2% under a “scaled decimal” convention; they’ll be effectively ~0.Proposed fix:
function run() external { uint256 senderPrivateKey = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(senderPrivateKey); vm.startBroadcast(senderPrivateKey); // Deploy DjedTefnut with mock oracle DjedTefnut djedTefnut = new DjedTefnut( address(oracle), // oracle address 1e18, // scalingFactor (1.0) - msg.sender, // treasury address (deployer) - 100, // treasuryFee (1%) - 200, // fee (2%) + deployer, // treasury address (deployer) + 1e16, // treasuryFee (1%) if scalingFactor=1e18 + 2e16, // fee (2%) if scalingFactor=1e18 1000e18, // thresholdSupplySc (1000 stable coins) 1e15, // rcMinPrice (0.001) 1e17, // rcInitialPrice (0.1) 100e18 // txLimit (100 ETH) ); }Also: confirm
console.logcompiles in your setup (some setups requireconsole2.logor an explicit console import).🤖 Prompt for AI Agents