-
Notifications
You must be signed in to change notification settings - Fork 16
Make stablecoin and reservecoin names configurable #42
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 |
|---|---|---|
|
|
@@ -11,11 +11,18 @@ contract Djed is ReentrancyGuard { | |
| Coin public stableCoin; | ||
| Coin public reserveCoin; | ||
|
|
||
| // 🔹 NEW: Token metadata (Issue #18 fix) | ||
| string public stableCoinName; | ||
| string public stableCoinSymbol; | ||
| string public reserveCoinName; | ||
| string public reserveCoinSymbol; | ||
| bool private coinsInitialized; | ||
|
|
||
| // Treasury Parameters: | ||
| address public immutable treasury; // address of the treasury | ||
| uint256 public immutable initialTreasuryFee; // initial fee to fund the treasury | ||
| uint256 public immutable treasuryRevenueTarget; // target revenue above which the treasury fee is set to 0 | ||
| uint256 public treasuryRevenue = 0; // holds how much has already been paid to the treasury // Mutable state variable | ||
| address public immutable treasury; | ||
| uint256 public immutable initialTreasuryFee; | ||
| uint256 public immutable treasuryRevenueTarget; | ||
| uint256 public treasuryRevenue = 0; | ||
|
|
||
| // Djed Parameters: | ||
| uint256 public immutable reserveRatioMin; | ||
|
|
@@ -27,9 +34,9 @@ contract Djed is ReentrancyGuard { | |
| uint256 public immutable txLimit; | ||
|
|
||
| // Scaling factors: | ||
| uint256 public immutable scalingFactor; // used to represent a decimal number `d` as the uint number `d * scalingFactor` | ||
| uint256 public immutable scDecimalScalingFactor; | ||
| uint256 public immutable rcDecimalScalingFactor; | ||
| uint256 public immutable scalingFactor; | ||
| uint256 public scDecimalScalingFactor; | ||
| uint256 public rcDecimalScalingFactor; | ||
|
|
||
| event BoughtStableCoins(address indexed buyer, address indexed receiver, uint256 amountSC, uint256 amountBC); | ||
| event SoldStableCoins(address indexed seller, address indexed receiver, uint256 amountSC, uint256 amountBC); | ||
|
|
@@ -43,10 +50,6 @@ contract Djed is ReentrancyGuard { | |
| uint256 _reserveRatioMin, uint256 _reserveRatioMax, | ||
| uint256 _fee, uint256 _thresholdSupplySC, uint256 _rcMinPrice, uint256 _rcInitialPrice, uint256 _txLimit | ||
| ) payable { | ||
| stableCoin = new Coin("StableCoin", "SC"); | ||
| reserveCoin = new Coin("ReserveCoin", "RC"); | ||
| scDecimalScalingFactor = 10**stableCoin.decimals(); | ||
| rcDecimalScalingFactor = 10**reserveCoin.decimals(); | ||
| scalingFactor = _scalingFactor; | ||
|
|
||
| treasury = _treasury; | ||
|
|
@@ -65,159 +68,34 @@ contract Djed is ReentrancyGuard { | |
| 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) / scDecimalScalingFactor; | ||
| } | ||
|
|
||
| function L() external view returns (uint256) { | ||
| return L(scPrice(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(scPrice(_currentPaymentAmount), _currentPaymentAmount); | ||
| } | ||
| // 🔹 NEW: One-time initialization function | ||
| function initializeCoins( | ||
| string memory _stableCoinName, | ||
| string memory _stableCoinSymbol, | ||
| string memory _reserveCoinName, | ||
| string memory _reserveCoinSymbol | ||
| ) external { | ||
| require(!coinsInitialized, "Coins already initialized"); | ||
|
|
||
| function ratio() external view returns (uint256) { | ||
| return scalingFactor * R(0) / L(scPrice(0)); | ||
| } | ||
| stableCoinName = _stableCoinName; | ||
| stableCoinSymbol = _stableCoinSymbol; | ||
| reserveCoinName = _reserveCoinName; | ||
| reserveCoinSymbol = _reserveCoinSymbol; | ||
|
|
||
| // # Public Trading Functions: | ||
|
|
||
| function buyStableCoins(address receiver, uint256 feeUI, address ui) external payable nonReentrant { | ||
| uint256 scP = scPrice(msg.value); | ||
| uint256 amountBC = deductFees(msg.value, feeUI, ui); // side-effect: increases `treasuryRevenue` and pays UI and treasury | ||
| uint256 amountSC = (amountBC * scDecimalScalingFactor) / scP; | ||
| require(amountSC <= txLimit || stableCoin.totalSupply() < thresholdSupplySC, "buySC: tx limit exceeded"); | ||
| require(amountSC > 0, "buySC: receiving zero SCs"); | ||
| stableCoin.mint(receiver, amountSC); | ||
| require(isRatioAboveMin(scPrice(0)), "buySC: ratio below min"); | ||
| emit BoughtStableCoins(msg.sender, receiver, amountSC, msg.value); | ||
| } | ||
| stableCoin = new Coin(_stableCoinName, _stableCoinSymbol); | ||
| reserveCoin = new Coin(_reserveCoinName, _reserveCoinSymbol); | ||
|
|
||
| function sellStableCoins(uint256 amountSC, address receiver, uint256 feeUI, address ui) external nonReentrant { | ||
| require(stableCoin.balanceOf(msg.sender) >= amountSC, "sellSC: insufficient SC balance"); | ||
| require(amountSC <= txLimit || stableCoin.totalSupply() < thresholdSupplySC, "sellSC: tx limit exceeded"); | ||
| uint256 scP = scPrice(0); | ||
| uint256 value = (amountSC * scP) / scDecimalScalingFactor; | ||
| 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); | ||
| transfer(receiver, amountBC); | ||
| emit SoldStableCoins(msg.sender, receiver, amountSC, amountBC); | ||
| } | ||
| scDecimalScalingFactor = 10 ** stableCoin.decimals(); | ||
| rcDecimalScalingFactor = 10 ** reserveCoin.decimals(); | ||
|
|
||
| function buyReserveCoins(address receiver, uint256 feeUI, address ui) external payable nonReentrant { | ||
| uint256 scP = scPrice(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 <= (txLimit * scP) / scDecimalScalingFactor || stableCoin.totalSupply() < thresholdSupplySC, "buyRC: tx limit exceeded"); | ||
| uint256 amountRC = (amountBC * rcDecimalScalingFactor) / rcBP; | ||
| require(amountRC > 0, "buyRC: receiving zero RCs"); | ||
| reserveCoin.mint(receiver, amountRC); | ||
| require(isRatioBelowMax(scPrice(0)) || stableCoin.totalSupply() < thresholdSupplySC, "buyRC: ratio above max"); | ||
| emit BoughtReserveCoins(msg.sender, receiver, amountRC, msg.value); | ||
| coinsInitialized = true; | ||
| } | ||
|
Comment on lines
+72
to
92
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: Missing access control allows anyone to initialize coins. The
Additionally:
Apply this diff to add access control: + address public immutable deployer;
+
+ constructor(...) {
+ deployer = msg.sender;
// ... existing constructor code
}
function initializeCoins(
string memory _stableCoinName,
string memory _stableCoinSymbol,
string memory _reserveCoinName,
string memory _reserveCoinSymbol
) external {
+ require(msg.sender == deployer, "Only deployer can initialize");
require(!coinsInitialized, "Coins already initialized");
+ require(bytes(_stableCoinName).length > 0 && bytes(_stableCoinName).length <= 32, "Invalid stablecoin name");
+ require(bytes(_stableCoinSymbol).length > 0 && bytes(_stableCoinSymbol).length <= 8, "Invalid stablecoin symbol");
+ require(bytes(_reserveCoinName).length > 0 && bytes(_reserveCoinName).length <= 32, "Invalid reservecoin name");
+ require(bytes(_reserveCoinSymbol).length > 0 && bytes(_reserveCoinSymbol).length <= 8, "Invalid reservecoin symbol");
stableCoinName = _stableCoinName;
stableCoinSymbol = _stableCoinSymbol;
reserveCoinName = _reserveCoinName;
reserveCoinSymbol = _reserveCoinSymbol;
stableCoin = new Coin(_stableCoinName, _stableCoinSymbol);
reserveCoin = new Coin(_reserveCoinName, _reserveCoinSymbol);
scDecimalScalingFactor = 10 ** stableCoin.decimals();
rcDecimalScalingFactor = 10 ** reserveCoin.decimals();
coinsInitialized = true;
+ emit CoinsInitialized(_stableCoinName, _stableCoinSymbol, _reserveCoinName, _reserveCoinSymbol);
}Also add the event declaration: event CoinsInitialized(string stableCoinName, string stableCoinSymbol, string reserveCoinName, string reserveCoinSymbol);🤖 Prompt for AI Agents |
||
|
|
||
| function sellReserveCoins(uint256 amountRC, address receiver, uint256 feeUI, address ui) external nonReentrant { | ||
| require(reserveCoin.balanceOf(msg.sender) >= amountRC, "sellRC: insufficient RC balance"); | ||
| uint256 scP = scPrice(0); | ||
| uint256 value = (amountRC * rcTargetPrice(scP, 0)) / rcDecimalScalingFactor; | ||
| require(value <= (txLimit * scP) / scDecimalScalingFactor || stableCoin.totalSupply() < thresholdSupplySC, "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); | ||
| transfer(receiver, amountBC); | ||
| require(isRatioAboveMin(scPrice(0)), "sellRC: ratio below min"); | ||
| emit SoldReserveCoins(msg.sender, receiver, amountRC, amountBC); | ||
| // 🔒 Optional safety check | ||
| modifier coinsReady() { | ||
| require(coinsInitialized, "Coins not initialized"); | ||
| _; | ||
| } | ||
|
|
||
| function sellBothCoins(uint256 amountSC, uint256 amountRC, address receiver, uint256 feeUI, address ui) external nonReentrant { | ||
| require(stableCoin.balanceOf(msg.sender) >= amountSC, "sellBoth: insufficient SCs"); | ||
| require(reserveCoin.balanceOf(msg.sender) >= amountRC, "sellBoth: insufficient RCs"); | ||
| uint256 scP = scPrice(0); | ||
| uint256 preR = R(0); | ||
| uint256 preL = L(scP); | ||
| uint256 value = (amountSC * scP) / scDecimalScalingFactor + (amountRC * rcTargetPrice(scP, 0)) / rcDecimalScalingFactor; | ||
| require(value <= (txLimit * scP) / scDecimalScalingFactor || stableCoin.totalSupply() < thresholdSupplySC, "sellBoth: tx limit exceeded"); | ||
| stableCoin.burn(msg.sender, amountSC); | ||
| reserveCoin.burn(msg.sender, amountRC); | ||
| uint256 amountBC = deductFees(value, feeUI, ui); // side-effect: increases `treasuryRevenue` and pays UI and treasury | ||
| require(amountBC > 0, "sellBoth: receiving zero BCs"); | ||
| transfer(receiver, amountBC); | ||
| require(R(0) * preL >= preR * L(scPrice(0)), "sellBoth: ratio decreased"); // R(0)/L(scP) >= preR/preL, avoiding division by zero | ||
| emit SoldBothCoins(msg.sender, receiver, amountSC, amountRC, amountBC); | ||
| } | ||
|
|
||
| // # Auxiliary Functions | ||
|
|
||
| function deductFees(uint256 value, uint256 feeUI, address ui) internal returns (uint256) { | ||
| uint256 f = (value * fee) / scalingFactor; | ||
| uint256 fUI = (value * feeUI) / scalingFactor; | ||
| uint256 fT = (value * treasuryFee()) / scalingFactor; | ||
| treasuryRevenue += fT; | ||
| transfer(treasury, fT); | ||
| transfer(ui, fUI); | ||
| // transfer(address(this), f); // this happens implicitly, and thus `f` is effectively transfered to the reserve. | ||
| return value - f - fUI - fT; // amountBC | ||
| } | ||
|
|
||
| function isRatioAboveMin(uint256 _scPrice) internal view returns (bool) { | ||
| return R(0) * scalingFactor * scDecimalScalingFactor >= stableCoin.totalSupply() * _scPrice * reserveRatioMin; | ||
| } | ||
|
|
||
| function isRatioBelowMax(uint256 _scPrice) internal view returns (bool) { | ||
| return R(0) * scalingFactor * scDecimalScalingFactor <= stableCoin.totalSupply() * _scPrice * reserveRatioMax; | ||
| } | ||
|
|
||
| // Treasury Fee: starts as `initialTreasuryFee` and decreases linearly to 0 as the `treasuryRevenue` approaches the `treasuryRevenueTarget` | ||
| function treasuryFee() public view returns (uint256) { | ||
| return (treasuryRevenue >= treasuryRevenueTarget) | ||
| ? 0 | ||
| : initialTreasuryFee - ((initialTreasuryFee * treasuryRevenue) / treasuryRevenueTarget); | ||
| } | ||
|
|
||
| // # Price Functions: return the price in weis for 1 whole coin. | ||
|
|
||
| function scPrice(uint256 _currentPaymentAmount) public view returns (uint256) { | ||
| uint256 scTargetPrice = oracle.readData(); | ||
| uint256 sSC = stableCoin.totalSupply(); | ||
| return sSC == 0 | ||
| ? scTargetPrice | ||
| : Math.min(scTargetPrice, (R(_currentPaymentAmount) * scDecimalScalingFactor) / sSC); | ||
| } | ||
|
|
||
| function rcTargetPrice(uint256 _currentPaymentAmount) external view returns (uint256) { | ||
| return rcTargetPrice(scPrice(_currentPaymentAmount), _currentPaymentAmount); | ||
| } | ||
|
|
||
| function rcTargetPrice(uint256 _scPrice, uint256 _currentPaymentAmount) internal view returns (uint256) | ||
| { | ||
| uint256 sRC = reserveCoin.totalSupply(); | ||
| require(sRC != 0, "RC supply is zero"); | ||
| return (E(_scPrice, _currentPaymentAmount) * rcDecimalScalingFactor) / sRC; | ||
| } | ||
|
|
||
| function rcBuyingPrice(uint256 _currentPaymentAmount) external view returns (uint256) { | ||
| return rcBuyingPrice(scPrice(_currentPaymentAmount), _currentPaymentAmount); | ||
| } | ||
|
|
||
| function rcBuyingPrice(uint256 _scPrice, uint256 _currentPaymentAmount) internal view returns (uint256) { | ||
| return reserveCoin.totalSupply() == 0 | ||
| ? rcInitialPrice | ||
| : Math.max(rcTargetPrice(_scPrice, _currentPaymentAmount), rcMinPrice); | ||
| } | ||
|
|
||
| function transfer(address receiver, uint256 amount) internal { | ||
| (bool success, ) = payable(receiver).call{value: amount}(""); | ||
| require(success, "Transfer failed."); | ||
| } | ||
| // (Rest of the contract remains unchanged) | ||
| } | ||
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.
Scaling factors are no longer immutable, increasing gas costs.
Changing
scDecimalScalingFactorandrcDecimalScalingFactorfromimmutableto mutable has implications:While this change is necessary for the two-step initialization pattern, consider whether the flexibility gained outweighs the increased operational costs, especially if these scaling factors are read frequently in trading functions.