{
  "name": "StableEngine",
  "path": "krishnala-perps/src/stable/StableEngine.sol",
  "source": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"openzeppelin-contracts/contracts/token/ERC20/IERC20.sol\";\nimport {SafeERC20} from \"openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport {ReentrancyGuard} from \"openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol\";\nimport {AccessControl} from \"openzeppelin-contracts/contracts/access/AccessControl.sol\";\nimport {Pausable} from \"openzeppelin-contracts/contracts/utils/Pausable.sol\";\nimport {OracleMark} from \"../perps/OracleMark.sol\";\nimport {EngineToken} from \"./EngineToken.sol\";\n\n/// @title StableEngine — DJED-style minimal over-collateralized stablecoin\n/// @notice One KRIS(WKRIS) reserve backs DJED ($1 peg) and SHEN (reservecoin). DJED\n///         holders are protected; SHEN holders take the volatility. The reserve ratio\n///         (reserveUSD / djedLiabilityUSD) is kept inside [MIN_RATIO, MAX_RATIO].\ncontract StableEngine is ReentrancyGuard, AccessControl, Pausable {\n    using SafeERC20 for IERC20;\n\n    bytes32 public constant PAUSER_ROLE = keccak256(\"PAUSER_ROLE\");\n\n    uint256 public constant WAD = 1e18;\n    uint256 public constant MIN_RATIO = 4e18; // 400%\n    uint256 public constant MAX_RATIO = 8e18; // 800%\n\n    IERC20 public immutable base;   // WKRIS reserve asset\n    OracleMark public immutable oracle;\n    bytes32 public immutable market; // KRIS price feed key\n    EngineToken public immutable djed;\n    EngineToken public immutable shen;\n    uint256 public feeBps = 150;    // 1.5%, accrues to the reserve (grows SHEN)\n    uint256 public reserveBase;     // WKRIS held\n\n    event MintStable(address indexed who, uint256 baseIn, uint256 djedOut);\n    event BurnStable(address indexed who, uint256 djedIn, uint256 baseOut);\n    event MintReserve(address indexed who, uint256 baseIn, uint256 shenOut);\n    event BurnReserve(address indexed who, uint256 shenIn, uint256 baseOut);\n\n    constructor(address admin, address base_, address oracle_, bytes32 market_) {\n        _grantRole(DEFAULT_ADMIN_ROLE, admin);\n        _grantRole(PAUSER_ROLE, admin);\n        base = IERC20(base_);\n        oracle = OracleMark(oracle_);\n        market = market_;\n        djed = new EngineToken(\"Krishnala DJED\", \"DJED\");\n        shen = new EngineToken(\"Krishnala SHEN\", \"SHEN\");\n    }\n\n    // --- admin ---\n\n    function pause() external onlyRole(PAUSER_ROLE) { _pause(); }\n    function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) { _unpause(); }\n\n    // --- views ---\n\n    /// @notice kUSD per 1 base (KRIS), scaled 1e18. Reverts if oracle is stale.\n    function price() public view returns (uint256) {\n        return oracle.getFreshPrice(market);\n    }\n\n    function reserveValueUSD() public view returns (uint256) {\n        return (reserveBase * price()) / WAD;\n    }\n\n    function djedLiabilityUSD() public view returns (uint256) {\n        return djed.totalSupply(); // 1 DJED == $1 == 1e18\n    }\n\n    function equityUSD() public view returns (uint256) {\n        uint256 r = reserveValueUSD();\n        uint256 l = djedLiabilityUSD();\n        return r > l ? r - l : 0;\n    }\n\n    /// @notice reserveUSD / djedLiabilityUSD, scaled 1e18. Infinite when no DJED outstanding.\n    function ratio() public view returns (uint256) {\n        uint256 l = djedLiabilityUSD();\n        if (l == 0) return type(uint256).max;\n        return (reserveValueUSD() * WAD) / l;\n    }\n\n    /// @notice USD price of one SHEN (equity / supply); WAD at bootstrap.\n    function shenPrice() public view returns (uint256) {\n        uint256 s = shen.totalSupply();\n        if (s == 0) return WAD;\n        return (equityUSD() * WAD) / s;\n    }\n\n    // --- stablecoin (DJED) ---\n\n    function mintStable(uint256 baseIn, uint256 minDjed) external nonReentrant whenNotPaused returns (uint256 djedOut) {\n        uint256 p = price();\n        base.safeTransferFrom(msg.sender, address(this), baseIn);\n        reserveBase += baseIn;\n        uint256 usdIn = (baseIn * p) / WAD;\n        djedOut = (usdIn * (10000 - feeBps)) / 10000;\n        require(djedOut >= minDjed && djedOut > 0, \"slippage\");\n        djed.mint(msg.sender, djedOut);\n        require(ratio() >= MIN_RATIO, \"below min ratio\"); // must stay over-collateralized\n        emit MintStable(msg.sender, baseIn, djedOut);\n    }\n\n    function burnStable(uint256 djedIn, uint256 minBase) external nonReentrant returns (uint256 baseOut) {\n        uint256 p = price();\n        uint256 usdOut = (djedIn * (10000 - feeBps)) / 10000;\n        baseOut = (usdOut * WAD) / p;\n        require(baseOut >= minBase && baseOut <= reserveBase, \"amount\");\n        djed.burn(msg.sender, djedIn);\n        reserveBase -= baseOut;\n        base.safeTransfer(msg.sender, baseOut);\n        emit BurnStable(msg.sender, djedIn, baseOut);\n    }\n\n    // --- reservecoin (SHEN) ---\n\n    function mintReserve(uint256 baseIn, uint256 minShen) external nonReentrant whenNotPaused returns (uint256 shenOut) {\n        uint256 p = price();\n        uint256 sp = shenPrice(); // pre-state price\n        base.safeTransferFrom(msg.sender, address(this), baseIn);\n        reserveBase += baseIn;\n        uint256 usdIn = (baseIn * p) / WAD;\n        uint256 net = (usdIn * (10000 - feeBps)) / 10000;\n        shenOut = (net * WAD) / sp;\n        require(shenOut >= minShen && shenOut > 0, \"slippage\");\n        shen.mint(msg.sender, shenOut);\n        // MAX guard prevents over-reserving; skipped at bootstrap (no DJED liability yet)\n        require(djedLiabilityUSD() == 0 || ratio() <= MAX_RATIO, \"above max ratio\");\n        emit MintReserve(msg.sender, baseIn, shenOut);\n    }\n\n    function burnReserve(uint256 shenIn, uint256 minBase) external nonReentrant returns (uint256 baseOut) {\n        uint256 p = price();\n        uint256 sp = shenPrice();\n        uint256 usdOut = (shenIn * sp) / WAD;\n        uint256 net = (usdOut * (10000 - feeBps)) / 10000;\n        baseOut = (net * WAD) / p;\n        require(baseOut >= minBase && baseOut <= reserveBase, \"amount\");\n        shen.burn(msg.sender, shenIn);\n        reserveBase -= baseOut;\n        base.safeTransfer(msg.sender, baseOut);\n        require(ratio() >= MIN_RATIO, \"below min ratio\"); // can't drain backing below peg\n        emit BurnReserve(msg.sender, shenIn, baseOut);\n    }\n}\n"
}