{
  "review_date": "2026-10-14",
  "scope": "Upstream source review; not deployed bytecode verification",
  "upstream_commit": "f9467b932c4cce460dc1267c6bc280327cf66a72",
  "source_url": "https://raw.githubusercontent.com/InverseFinance/dola-savings/f9467b932c4cce460dc1267c6bc280327cf66a72/src/sDola.sol",
  "source_sha256": "b909ea0ec9ef3f19ba57e4d76cd0d5075fda185f4ffb327a9a9076991cea79fb",
  "source": "// SPDX-License-Identifier: MIT License\npragma solidity 0.8.21;\n\nimport \"lib/solmate/src/mixins/ERC4626.sol\";\n\ninterface IDolaSavings {\n    function balanceOf(address user) external view returns (uint);\n    function stake(uint amount, address recipient) external;\n    function unstake(uint amount) external;\n    function claim(address to) external;\n    function claimable(address user) external view returns (uint);\n    function dbr() external view returns (address);\n}\n\ninterface IERC20 {\n    function transfer(address, uint) external returns (bool);\n    function transferFrom(address, address, uint) external returns (bool);\n    function balanceOf(address) external view returns (uint);\n}\n\n/**\n * @title sDola\n * @dev Auto-compounding ERC4626 wrapper for DolaSacings utilizing xy=k auctions.\n * WARNING: While this vault is safe to be used as collateral in lending markets, it should not be allowed as a borrowable asset.\n * Any protocol in which sudden, large and atomic increases in the value of an asset may be a securit risk should not integrate this vault.\n */\ncontract sDola is ERC4626 {\n    \n    uint constant MIN_BALANCE = 10**16; // 1 cent\n    uint public constant MIN_SHARES = 10**18;\n    uint public constant MAX_ASSETS = 10**32; // 100 trillion DOLA\n    IDolaSavings public immutable savings;\n    ERC20 public immutable dbr;\n    address public gov;\n    address public pendingGov;\n    address public operator;\n    uint public prevK;\n    uint public targetK;\n    uint public lastKUpdate;\n    mapping (uint => uint) public weeklyRevenue;\n\n    /**\n     * @dev Constructor for sDola contract.\n     * WARNING: MIN_SHARES will always be unwithdrawable from the vault. Deployer should deposit enough to mint MIN_SHARES to avoid causing user grief.\n     * @param _dola Address of the DOLA token.\n     * @param _savings Address of the DolaSavings contract.\n     * @param _gov Address of the governance.\n     * @param _K Initial value for the K variable used in calculations.\n     */\n    constructor(\n        address _dola,\n        address _savings,\n        address _gov,\n        address _operator,\n        uint _K\n    ) ERC4626(ERC20(_dola), \"Staked Dola\", \"sDOLA\") {\n        require(_K > 0, \"_K must be positive\");\n        savings = IDolaSavings(_savings);\n        dbr = ERC20(IDolaSavings(_savings).dbr());\n        gov = _gov;\n        operator = _operator;\n        targetK = _K;\n        asset.approve(_savings, type(uint).max);\n    }\n\n    modifier onlyGov() {\n        require(msg.sender == gov, \"ONLY GOV\");\n        _;\n    }\n\n    modifier onlyOperator() {\n        require(msg.sender == gov || msg.sender == operator, \"ONLY OPERATOR\");\n        _;\n    }\n\n    /**\n     * @dev Hook that is called after tokens are deposited into the contract.\n     * @param assets The amount of assets that were deposited.\n     */    \n    function afterDeposit(uint256 assets, uint256) internal override {\n        require(totalSupply >= MIN_SHARES, \"Shares below MIN_SHARES\");\n        savings.stake(assets, address(this));\n    }\n\n    /**\n     * @dev Hook that is called before tokens are withdrawn from the contract.\n     * @param assets The amount of assets to withdraw.\n     * @param shares The amount of shares to withdraw\n     */\n    function beforeWithdraw(uint256 assets, uint256 shares) internal override {\n        require(totalAssets() >= assets + MIN_BALANCE, \"Insufficient assets\");\n        require(totalSupply - shares >= MIN_SHARES, \"Shares below MIN_SHARES\");\n        savings.unstake(assets);\n    }\n\n    /**\n     * @dev Calculates the total assets controlled by the contract.\n     * Weekly revenue is distributed linearly over the following week.\n     * @return The total assets in the contract.\n     */\n    function totalAssets() public view override returns (uint) {\n        uint week = block.timestamp / 7 days;\n        uint timeElapsed = block.timestamp % 7 days;\n        uint remainingLastRevenue = weeklyRevenue[week - 1] * (7 days - timeElapsed) / 7 days;\n        uint actualAssets = savings.balanceOf(address(this)) - remainingLastRevenue - weeklyRevenue[week];\n        return actualAssets < MAX_ASSETS ? actualAssets : MAX_ASSETS;\n    }\n\n    /**\n     * @dev Returns the current value of K, which is a weighted average between prevK and targetK.\n     * @return The current value of K.\n     */\n    function getK() public view returns (uint) {\n        uint duration = 7 days;\n        uint timeElapsed = block.timestamp - lastKUpdate;\n        if(timeElapsed > duration) {\n            return targetK;\n        }\n        uint targetWeight = timeElapsed;\n        uint prevWeight = duration - timeElapsed;\n        return (prevK * prevWeight + targetK * targetWeight) / duration;\n    }\n\n    /**\n     * @dev Calculates the DOLA reserve based on the current DBR reserve.\n     * @return The calculated DOLA reserve.\n     */\n    function getDolaReserve() public view returns (uint) {\n        return getK() / getDbrReserve();\n    }\n\n    /**\n     * @dev Calculates the DOLA reserve for a given DBR reserve.\n     * @param dbrReserve The DBR reserve value.\n     * @return The calculated DOLA reserve.\n     */\n    function getDolaReserve(uint dbrReserve) public view returns (uint) {\n        return getK() / dbrReserve;\n    }\n\n    /**\n     * @dev Returns the current DBR reserve as the sum of dbr balance and claimable dbr\n     * @return The current DBR reserve.\n     */\n    function getDbrReserve() public view returns (uint) {\n        return dbr.balanceOf(address(this)) + savings.claimable(address(this));\n    }\n\n    /**\n     * @dev Sets a new target K value.\n     * @param _K The new target K value.\n     */\n    function setTargetK(uint _K) external onlyOperator {\n        require(_K > getDbrReserve(), \"K must be larger than dbr reserve\");\n        prevK = getK();\n        targetK = _K;\n        lastKUpdate = block.timestamp;\n        emit SetTargetK(_K);\n    }\n\n    /**\n     * @dev Allows users to buy DBR with DOLA.\n     * WARNING: Never expose this directly to a UI as it's likely to cause a loss unless a transaction is executed immediately.\n     * Instead use the sDolaHelper function or custom smart contract code.\n     * @param exactDolaIn The exact amount of DOLA to spend.\n     * @param exactDbrOut The exact amount of DBR to receive.\n     * @param to The address that will receive the DBR.\n     */\n    function buyDBR(uint exactDolaIn, uint exactDbrOut, address to) external {\n        require(to != address(0), \"Zero address\");\n        savings.claim(address(this));\n        uint k = getK();\n        uint dbrBalance = dbr.balanceOf(address(this));\n        uint dbrReserve = dbrBalance - exactDbrOut;\n        uint dolaReserve = k / dbrBalance + exactDolaIn;\n        require(dolaReserve * dbrReserve >= k, \"Invariant\");\n        asset.transferFrom(msg.sender, address(this), exactDolaIn);\n        savings.stake(exactDolaIn, address(this));\n        weeklyRevenue[block.timestamp / 7 days] += exactDolaIn;\n        dbr.transfer(to, exactDbrOut);\n        emit Buy(msg.sender, to, exactDolaIn, exactDbrOut);\n    }\n\n    /**\n     * @dev Sets a new pending governance address.\n     * @param _gov The address of the new pending governance.\n     */\n    function setPendingGov(address _gov) external onlyGov {\n        pendingGov = _gov;\n    }\n\n    /**\n     * @dev Sets a new operator address.\n     * @param _operator New operator address.\n     */\n    function setOperator(address _operator) external onlyGov {\n        operator = _operator;\n    }\n\n    /**\n     * @dev Allows the pending governance to accept its role.\n     */\n    function acceptGov() external {\n        require(msg.sender == pendingGov, \"ONLY PENDINGGOV\");\n        gov = pendingGov;\n        pendingGov = address(0);\n    }\n\n    /**\n     * @dev Re-approves the DOLA token to be spent by the DolaSavings contract.\n     */\n    function reapprove() external {\n        asset.approve(address(savings), type(uint).max);\n    }\n\n    /**\n     * @dev Allows governance to sweep any ERC20 token from the contract.\n     * @dev Excludes the ability to sweep DBR tokens.\n     * @param token The address of the ERC20 token to sweep.\n     * @param amount The amount of tokens to sweep.\n     * @param to The recipient address of the swept tokens.\n     */\n    function sweep(address token, uint amount, address to) public onlyGov {\n        require(address(dbr) != token, \"Not authorized\");\n        IERC20(token).transfer(to, amount);\n    }\n\n    event Buy(address indexed caller, address indexed to, uint exactDolaIn, uint exactDbrOut);\n    event SetTargetK(uint newTargetK);\n}\n",
  "onchain_evidence": "dola-admin-coverage-2026-10-14.json",
  "open_checks": [
    "deployed wrapper and DSA bytecode",
    "live admin topology",
    "redemption execution and combined USDC routing",
    "remote deployment inventory and exits"
  ]
}
