// File: src/util/ALE.sol



//SPDX-License-Identifier: None
pragma solidity ^0.8.0;

import "src/interfaces/IMarket.sol";
import "src/interfaces/IERC20.sol";
import "src/interfaces/ITransformHelper.sol";
import "src/util/CurveDBRHelper.sol";
import {Ownable} from "lib/openzeppelin-contracts/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";

interface IDBR {
    function markets(address) external view returns (bool);
}

// Accelerated leverage engine
contract ALE is Ownable, ReentrancyGuard, CurveDBRHelper {
    error CollateralNotSet();
    error MarketNotSet(address market);
    error SwapFailed();
    error DOLAInvalidBorrow(uint256 expected, uint256 actual);
    error DOLAInvalidRepay(uint256 expected, uint256 actual);
    error InvalidProxyAddress();
    error InvalidHelperAddress();
    error NothingToDeposit();
    error DepositFailed(uint256 expected, uint256 actual);
    error WithdrawFailed(uint256 expected, uint256 actual);
    error TotalSupplyChanged(uint256 expected, uint256 actual);
    error CollateralIsZero();
    error NoMarket(address market);
    error WrongCollateral(address market, address buySellToken, address collateral, address helper);

    // 0x ExchangeProxy address.
    // See https://docs.0x.org/developer-resources/contract-addresses
    address payable public exchangeProxy;

    IDBR public constant DBR = IDBR(0xAD038Eb671c44b853887A7E32528FaB35dC5D710);

    struct Market {
        IERC20 buySellToken;
        IERC20 collateral;
        ITransformHelper helper;
    }

    struct Permit {
        uint256 deadline;
        uint8 v;
        bytes32 r;
        bytes32 s;
    }

    struct DBRHelper {
        uint256 amountIn; // DOLA or DBR
        uint256 minOut; // DOLA or DBR
        uint256 dola; // DOLA to extra borrow or extra repay
    }

    event LeverageUp(
        address indexed market,
        address indexed account,
        uint256 dolaFlashMinted, // DOLA flash minted for buying collateral only
        uint256 collateralDeposited, // amount of collateral deposited into the escrow    
        uint256 dolaBorrowed,  // amount of DOLA borrowed on behalf of the user
        uint256 dolaForDBR   // amount of DOLA used for buying DBR
    );

    event LeverageDown(
        address indexed market,
        address indexed account,
        uint256 dolaFlashMinted, // Flash minted DOLA for repaying leverage only
        uint256 collateralSold,  // amount of collateral/underlying sold
        uint256 dolaUserRepaid,  // amount of DOLA deposited by the user as part of the repay
        uint256 dbrSoldForDola   // amount of DBR sold for DOLA
    );

    event Deposit(address indexed market, address indexed account, uint256 depositAmount);

    event NewMarket(address indexed market, address indexed buySellToken, address collateral, address indexed helper);

    event NewHelper(address indexed market, address indexed helper);

    // Mapping of market to Market structs
    // NOTE: in normal cases sellToken/buyToken is the collateral token,
    // in other cases it could be different (eg. st-yCRV is collateral, yCRV is the token to be swapped from/to DOLA)
    mapping(address => Market) public markets;

    modifier dolaSupplyUnchanged() {
        uint256 totalSupply = dola.totalSupply();
        _;
        if (totalSupply != dola.totalSupply())
            revert TotalSupplyChanged(totalSupply, dola.totalSupply());
    }

    constructor(
        address _exchangeProxy,
        address _pool
    ) Ownable(msg.sender) CurveDBRHelper(_pool) {
        exchangeProxy = payable(address(_exchangeProxy));
    }

    function setExchangeProxy(address _exchangeProxy) external onlyOwner {
        if (_exchangeProxy == address(0)) revert InvalidProxyAddress();
        exchangeProxy = payable(_exchangeProxy);
    }

    /// @notice Set the market for a collateral token
    /// @param _buySellToken The token which will be bought/sold (usually the collateral token), probably underlying if there's a helper
    /// @param _market The market contract
    /// @param _collateral The collateral token
    /// @param _helper Optional helper contract to transform collateral to buySelltoken and viceversa
    function setMarket(
        address _market,
        address _buySellToken,
        address _collateral,
        address _helper
    ) external onlyOwner {
        if(!DBR.markets(_market)) revert NoMarket(_market);

        if(_helper == address(0)) {
            if(_buySellToken != IMarket(_market).collateral() || _collateral != IMarket(_market).collateral()) {
                revert WrongCollateral(_market, _buySellToken, _collateral, _helper);
            }
        } else if (_helper != address(0) && _collateral != IMarket(_market).collateral()) {
            revert WrongCollateral(_market, _buySellToken, _collateral, _helper);
        }

        markets[_market].buySellToken = IERC20(_buySellToken);
        markets[_market].collateral = IERC20(_collateral);
        IERC20(_buySellToken).approve(_market, type(uint256).max);

        if (_buySellToken != _collateral) {
            IERC20(_collateral).approve(_market, type(uint256).max);
        } 

        if (_helper != address(0)) {
            markets[_market].helper = ITransformHelper(_helper);
            IERC20(_buySellToken).approve(_helper, type(uint256).max);
            IERC20(_collateral).approve(_helper, type(uint256).max);
        }

        emit NewMarket(_market, _buySellToken, _collateral, _helper);
    }

    /// @notice Update the helper contract
    /// @param _market The market we want to update the helper contract for
    /// @param _helper The helper contract
    function updateMarketHelper(
        address _market,
        address _helper
    ) external onlyOwner {
        if (address(markets[_market].buySellToken) == address(0)) revert MarketNotSet(_market);

        address oldHelper = address(markets[_market].helper);
        if (oldHelper != address(0)) {
            markets[_market].buySellToken.approve(oldHelper, 0);
            markets[_market].collateral.approve(oldHelper, 0);
        }

        markets[_market].helper = ITransformHelper(_helper);
        
        if (_helper != address(0)) {
            markets[_market].buySellToken.approve(_helper, type(uint256).max);
            markets[_market].collateral.approve(_helper, type(uint256).max);
        }
     

        emit NewHelper(_market, _helper);
    }

    /// @notice Leverage user position by minting DOLA, buying collateral, deposting into the user escrow and borrow DOLA on behalf to repay the minted DOLA
    /// @dev Requires user to sign message to permit the contract to borrow DOLA on behalf
    /// @param _value Amount of DOLA to flash mint/burn
    /// @param _market The market contract
    /// @param _spender The `allowanceTarget` field from the API response.
    /// @param _swapCallData The `data` field from the API response.
    /// @param _permit Permit data
    /// @param _helperData Optional helper data in case the collateral needs to be transformed
    /// @param _dbrData Optional data in case the user wants to buy DBR and also withdraw some DOLA
    function leveragePosition(
        uint256 _value,
        address _market,
        address _spender,
        bytes calldata _swapCallData,
        Permit calldata _permit,
        bytes calldata _helperData,
        DBRHelper calldata _dbrData
    ) public payable nonReentrant dolaSupplyUnchanged {
        if (address(markets[_market].buySellToken) == address(0))
            revert MarketNotSet(_market);

        IMarket market = IMarket(_market);

        // Mint and approve
        _mintAndApproveDola(_spender, _value);

        // Call the encoded swap function call on the contract at `swapTarget`,
        // passing along any ETH attached to this function call to cover protocol fees.
        (bool success, ) = exchangeProxy.call{value: msg.value}(_swapCallData);
        if (!success) revert SwapFailed();

        // Actual collateral/buyToken bought
        uint256 collateralAmount = markets[_market].buySellToken.balanceOf(address(this));
        if(collateralAmount == 0) revert CollateralIsZero();

        // If there's a helper contract, the buyToken has to be transformed
        if (address(markets[_market].helper) != address(0)) {
            collateralAmount = _convertToCollateral(
                collateralAmount,
                _market,
                _helperData
            );
        }

        // Deposit and borrow on behalf
        market.deposit(msg.sender, collateralAmount);

        _borrowDola(_value, _permit, _dbrData, market);

        // Burn the dola minted previously
        dola.burn(_value);

        if (_dbrData.dola != 0) {
            dola.transfer(msg.sender, _dbrData.dola);
        }

        if (_dbrData.amountIn != 0) {
            _buyDbr(_dbrData.amountIn, _dbrData.minOut, msg.sender);
        }

        if (dola.balanceOf(address(this)) != 0) {
            dola.transfer(msg.sender, dola.balanceOf(address(this)));
        }

        // Refund any possible unspent 0x protocol fees to the sender.
        if (address(this).balance > 0)
            payable(msg.sender).transfer(address(this).balance);

        emit LeverageUp(_market, msg.sender, _value, collateralAmount, _dbrData.dola, _dbrData.amountIn);
    }

    /// @notice Deposit collateral and instantly leverage user position by minting DOLA, buying collateral, deposting into the user escrow and borrow DOLA on behalf to repay the minted DOLA
    /// @dev Requires user to sign message to permit the contract to borrow DOLA on behalf
    /// @param _initialDeposit Amount of collateral or underlying (in case of helper) to deposit
    /// @param _value Amount of DOLA to borrow
    /// @param _market The market address
    /// @param _spender The `allowanceTarget` field from the API response.
    /// @param _swapCallData The `data` field from the API response.
    /// @param _permit Permit data
    /// @param _helperData Optional helper data in case the collateral needs to be transformed
    function depositAndLeveragePosition(
        uint256 _initialDeposit,
        uint256 _value,
        address _market,
        address _spender,
        bytes calldata _swapCallData,
        Permit calldata _permit,
        bytes calldata _helperData,
        DBRHelper calldata _dbrData
    ) external payable {
        if (_initialDeposit == 0) revert NothingToDeposit();
        IERC20 buyToken = markets[_market].buySellToken;
        buyToken.transferFrom(msg.sender, address(this), _initialDeposit);

        emit Deposit(_market, msg.sender, _initialDeposit);

        leveragePosition(
            _value,
            _market,
            _spender,
            _swapCallData,
            _permit,
            _helperData,
            _dbrData
        );
    }

    /// @notice Repay a DOLA loan and withdraw collateral from the escrow
    /// @dev Requires user to sign message to permit the contract to withdraw collateral from the escrow
    /// @param _value Amount of DOLA to repay
    /// @param _market The market contract
    /// @param _collateralAmount Collateral amount to withdraw from the escrow
    /// @param _spender The `allowanceTarget` field from the API response.
    /// @param _swapCallData The `data` field from the API response.
    /// @param _permit Permit data
    /// @param _helperData Optional helper data in case collateral needs to be transformed
    /// @param _dbrData Optional data in case the user wants to sell DBR
    function deleveragePosition(
        uint256 _value,
        address _market,
        uint256 _collateralAmount,
        address _spender,
        bytes calldata _swapCallData,
        Permit calldata _permit,
        bytes calldata _helperData,
        DBRHelper calldata _dbrData
    ) external payable nonReentrant dolaSupplyUnchanged {
        if (address(markets[_market].buySellToken) == address(0))
            revert MarketNotSet(_market);

        IERC20 sellToken = markets[_market].buySellToken;

        _repayAndWithdraw(_value, _collateralAmount, _permit, _dbrData, IMarket(_market));

        // If there's a helper contract, the collateral has to be transformed
        if (address(markets[_market].helper) != address(0)) {
            _collateralAmount = _convertToAsset(
                _collateralAmount,
                _market,
                sellToken,
                _helperData
            );
        }

        // Approve sellToken for spender
        sellToken.approve(_spender, 0);
        sellToken.approve(_spender, _collateralAmount);

        // Call the encoded swap function call on the contract at `swapTarget`,
        // passing along any ETH attached to this function call to cover protocol fees.
        // NOTE: This will swap the collateral or helperCollateral for DOLA
        (bool success, ) = exchangeProxy.call{value: msg.value}(_swapCallData);
        if (!success) revert SwapFailed();

        if (address(markets[_market].helper) == address(0)) {
            uint256 collateralAvailable = markets[_market].collateral.balanceOf(
                address(this)
            );

            if (collateralAvailable != 0) {
                markets[_market].collateral.transfer(
                    msg.sender,
                    collateralAvailable
                );
            }
        } else {
            uint256 sellTokenBal = sellToken.balanceOf(address(this));
            // Send any leftover sellToken to the sender
            if (sellTokenBal != 0) {
                sellToken.transfer(msg.sender, sellTokenBal);
            }
        }

        if (dola.balanceOf(address(this)) < _value)
            revert DOLAInvalidRepay(_value, dola.balanceOf(address(this)));

        dola.burn(_value);

        // Send any DOLA leftover to the sender after burning (in case the collateral withdrawn and swapped exceeds the value to burn)
        dola.transfer(msg.sender, dola.balanceOf(address(this)));

        if (_dbrData.amountIn != 0) {
            dbr.transferFrom(msg.sender, address(this), _dbrData.amountIn);
            _sellDbr(_dbrData.amountIn, _dbrData.minOut, msg.sender);
        }

        // Refund any unspent protocol fees to the sender.
        if (address(this).balance > 0)
            payable(msg.sender).transfer(address(this).balance);

       emit LeverageDown(_market, msg.sender, _value, _collateralAmount, _dbrData.dola, _dbrData.amountIn);
    }

    /// @notice Mint DOLA to this contract and approve the spender
    /// @param spender The spender address
    /// @param _value Amount of DOLA to mint and approve
    function _mintAndApproveDola(address spender, uint256 _value) internal {
        dola.mint(address(this), _value);
        dola.approve(spender, _value);
    }

    /// @notice Borrow DOLA on behalf of the user
    /// @param _value Amount of DOLA to borrow
    /// @param _permit Permit data
    /// @param _dbrData DBR data
    /// @param market The market contract
    function _borrowDola(
        uint256 _value,
        Permit calldata _permit,
        DBRHelper calldata _dbrData,
        IMarket market
    ) internal {
        uint256 dolaToBorrow = _value;

        if (_dbrData.dola != 0) {
            dolaToBorrow += _dbrData.dola;
        }

        if (_dbrData.amountIn != 0) {
            dolaToBorrow += _dbrData.amountIn;
        }
        // We borrow the amount of DOLA we minted before plus the amount for buying DBR if any
        market.borrowOnBehalf(
            msg.sender,
            dolaToBorrow,
            _permit.deadline,
            _permit.v,
            _permit.r,
            _permit.s
        );

        if (dola.balanceOf(address(this)) < dolaToBorrow)
            revert DOLAInvalidBorrow(dolaToBorrow,dola.balanceOf(address(this)));
    }

    /// @notice Repay DOLA loan and withdraw collateral from the escrow
    /// @param _value Amount of DOLA to repay
    /// @param _collateralAmount Collateral amount to withdraw from the escrow
    /// @param _permit Permit data
    /// @param _dbrData DBR data
    /// @param market The market contract
    function _repayAndWithdraw(
        uint256 _value,
        uint256 _collateralAmount,
        Permit calldata _permit,
        DBRHelper calldata _dbrData,
        IMarket market
    ) internal {
        if (_dbrData.dola != 0) {
            dola.transferFrom(msg.sender, address(this), _dbrData.dola);

            dola.mint(address(this), _value);
            dola.approve(address(market), _value + _dbrData.dola);

            market.repay(msg.sender, _value + _dbrData.dola);
        } else {
            _mintAndApproveDola(address(market), _value);
            market.repay(msg.sender, _value);
        }

        // withdraw amount from ZERO EX quote
        market.withdrawOnBehalf(
            msg.sender,
            _collateralAmount,
            _permit.deadline,
            _permit.v,
            _permit.r,
            _permit.s
        );
    }

    /// @notice convert a collateral amount into the underlying asset
    /// @param _collateralAmount Collateral amount to convert
    /// @param _market The market contract
    /// @param sellToken The sell token (the underlying asset)
    /// @param _helperData Optional helper data
    /// @return assetAmount The amount of sellToken/underlying after the conversion
    function _convertToAsset(
        uint256 _collateralAmount,
        address _market,
        IERC20 sellToken,
        bytes calldata _helperData
    ) internal returns (uint256) {
        // Collateral amount is now transformed into sellToken
        uint256 assetAmount = markets[_market].helper.transformFromCollateral(
            _collateralAmount,
            _helperData
        );

        if (sellToken.balanceOf(address(this)) < assetAmount)
            revert WithdrawFailed(assetAmount,sellToken.balanceOf(address(this)));

        return assetAmount;
    }

    /// @notice convert the underlying asset amount into the collateral
    /// @param _assetAmount The amount of sellToken/underlying to convert
    /// @param _market The market contract
    /// @param _helperData Optional helper data
    /// @return collateralAmount The amount of collateral after the conversion
    function _convertToCollateral(
        uint256 _assetAmount,
        address _market,
        bytes calldata _helperData
    ) internal returns (uint256) {
        // Collateral amount is now transformed
        uint256 collateralAmount = markets[_market]
            .helper
            .transformToCollateral(_assetAmount, _helperData);

        if (
            markets[_market].collateral.balanceOf(address(this)) <
            collateralAmount
        )
            revert DepositFailed(
                collateralAmount,
                markets[_market].collateral.balanceOf(address(this))
            );

        return collateralAmount;
    }

    // solhint-disable-next-line no-empty-blocks
    receive() external payable {}
}

// File: src/interfaces/IMarket.sol



pragma solidity ^0.8.13;

interface IMarket {
    function borrowOnBehalf(address msgSender, uint dolaAmount, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
    function withdrawOnBehalf(address msgSender, uint amount, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
    function deposit(address msgSender, uint collateralAmount) external;
    function repay(address msgSender, uint amount) external;
    function collateral() external returns(address);
    function debts(address user) external returns(uint);
    function recall(uint amount) external;
    function totalDebt() external view returns (uint);
    function borrowPaused() external view returns (bool);
    function oracle() external view returns (address);
}


// File: src/interfaces/IERC20.sol



pragma solidity ^0.8.13;

interface IERC20 {
    function approve(address,uint) external;
    function transfer(address,uint) external returns (bool);
    function transferFrom(address,address,uint) external returns (bool);
    function balanceOf(address) external view returns (uint);
    function allowance(address from, address to) external view returns (uint);
}

interface IDelegateableERC20 is IERC20 {
    function delegate(address delegatee) external;
    function delegates(address delegator) external view returns (address delegatee);
}


// File: src/interfaces/ITransformHelper.sol



//SPDX-License-Identifier: None
pragma solidity ^0.8.0;

interface ITransformHelper {
    
    struct Permit {
        uint256 deadline;
        uint8 v;
        bytes32 r;
        bytes32 s;
    }

    function transformToCollateral(
        uint amount,
        bytes calldata data
    ) external returns (uint);

    function transformToCollateralAndDeposit(
        uint amount,
        bytes calldata data
    ) external returns (uint256 collateralAmount);

    function transformFromCollateral(
        uint amount,
        bytes calldata data
    ) external returns (uint);

    function withdrawAndTransformFromCollateral(
        uint amount,
        Permit calldata permit,
        bytes calldata data
    ) external returns (uint256 underlyingAmount);

    function assetToCollateralRatio(
        uint assetAmount
    ) external view returns (uint collateralAmount);

    function assetToCollateral(
        uint assetAmount
    ) external view returns (uint collateralAmount);
    
    function collateralToAsset(
        uint collateralAmount
    ) external view returns (uint assetAmount);
}

// File: src/util/CurveDBRHelper.sol



pragma solidity ^0.8.13;
//import "src/util/OffchainAbstractHelper.sol";
import "src/interfaces/IERC20.sol";
import "src/interfaces/IDola.sol";

interface ICurvePool {
    function coins(uint index) external view returns (address);

    function get_dy(uint i, uint j, uint dx) external view returns (uint);

    function exchange(
        uint i,
        uint j,
        uint dx,
        uint min_dy,
        bool use_eth
    ) external payable returns (uint);

    function exchange(
        uint i,
        uint j,
        uint dx,
        uint min_dy,
        bool use_eth,
        address receiver
    ) external payable returns (uint);
}

contract CurveDBRHelper {
    ICurvePool public immutable curvePool;
    IDola constant dola = IDola(0x865377367054516e17014CcdED1e7d814EDC9ce4);
    IERC20 constant dbr = IERC20(0xAD038Eb671c44b853887A7E32528FaB35dC5D710);

    uint dbrIndex;
    uint dolaIndex;

    constructor(address _pool) {
        curvePool = ICurvePool(_pool);
        dola.approve(_pool, type(uint).max);
        dbr.approve(_pool, type(uint).max);
        if (ICurvePool(_pool).coins(0) == address(dola)) {
            dolaIndex = 0;
            dbrIndex = 1;
        } else {
            dolaIndex = 1;
            dbrIndex = 0;
        }
    }

    /**
    @notice Sells an exact amount of DBR for DOLA in a curve pool
    @param amount Amount of DBR to sell
    @param minOut minimum amount of DOLA to receive
    */
    function _sellDbr(uint amount, uint minOut, address receiver) internal {
        if (amount > 0) {
            curvePool.exchange(
                dbrIndex,
                dolaIndex,
                amount,
                minOut,
                false,
                receiver
            );
        }
    }

    /**
    @notice Buys an exact amount of DBR for DOLA in a curve pool
    @param amount Amount of DOLA to sell
    @param minOut minimum amount of DBR out
    */
    function _buyDbr(uint amount, uint minOut, address receiver) internal {
        if (amount > 0) {
            curvePool.exchange(
                dolaIndex,
                dbrIndex,
                amount,
                minOut,
                false,
                receiver
            );
        }
    }

    /**
    @notice Approximates the total amount of dola and dbr needed to borrow a dolaBorrowAmount while also borrowing enough to buy the DBR needed to cover for the borrowing period
    @dev Uses a binary search to approximate the amounts needed. Should only be called as part of generating transaction parameters.
    @param dolaBorrowAmount Amount of dola the user wishes to end up with
    @param period Amount of time in seconds the loan will last
    @param iterations Number of approximation iterations. The higher the more precise the result
    */
    function approximateDolaAndDbrNeeded(
        uint dolaBorrowAmount,
        uint period,
        uint iterations
    ) public view returns (uint dolaForDbr, uint dbrNeeded) {
        uint amountIn = dolaBorrowAmount;
        uint stepSize = amountIn / 2;
        uint dbrReceived = curvePool.get_dy(dolaIndex, dbrIndex, amountIn);
        uint dbrToBuy = ((amountIn + dolaBorrowAmount) * period) / 365 days;
        uint dist = dbrReceived > dbrToBuy
            ? dbrReceived - dbrToBuy
            : dbrToBuy - dbrReceived;
        for (uint i; i < iterations; ++i) {
            uint newAmountIn = amountIn;
            if (dbrReceived > dbrToBuy) {
                newAmountIn -= stepSize;
            } else {
                newAmountIn += stepSize;
            }
            uint newDbrReceived = curvePool.get_dy(
                dolaIndex,
                dbrIndex,
                newAmountIn
            );
            uint newDbrToBuy = ((newAmountIn + dolaBorrowAmount) * period) /
                365 days;
            uint newDist = newDbrReceived > newDbrToBuy
                ? newDbrReceived - newDbrToBuy
                : newDbrToBuy - newDbrReceived;
            if (newDist < dist) {
                dbrReceived = newDbrReceived;
                dbrToBuy = newDbrToBuy;
                dist = newDist;
                amountIn = newAmountIn;
            }
            stepSize /= 2;
        }
        return (amountIn, ((dolaBorrowAmount + amountIn) * period) / 365 days);
    }
}

// File: lib/openzeppelin-contracts/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        _status = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}

// File: src/interfaces/IDola.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "src/interfaces/IERC20.sol";

interface IDola  is IERC20{
    function mint(address recipient, uint256 amount) external;
    function burn(uint256 amount) external;
    function addMinter(address minter) external;
    function totalSupply() external view returns (uint256);
}
// File: lib/openzeppelin-contracts/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}