// Verified using https://dapp.tools

// hevm: flattened sources of /nix/store/n0zrh7hav4swn38ckv0y2panmrlaxy1s-geb-fsm/dapp/geb-fsm/src/FSMWrapper.sol
pragma solidity =0.6.7;

////// /nix/store/3d3msxain9q01swpn63dsh9wl2hsal24-geb-treasury-reimbursement/dapp/geb-treasury-reimbursement/src/math/GebMath.sol
/* pragma solidity 0.6.7; */

contract GebMath {
    uint256 public constant RAY = 10 ** 27;
    uint256 public constant WAD = 10 ** 18;

    function ray(uint x) public pure returns (uint z) {
        z = multiply(x, 10 ** 9);
    }
    function rad(uint x) public pure returns (uint z) {
        z = multiply(x, 10 ** 27);
    }
    function minimum(uint x, uint y) public pure returns (uint z) {
        z = (x <= y) ? x : y;
    }
    function addition(uint x, uint y) public pure returns (uint z) {
        z = x + y;
        require(z >= x, "uint-uint-add-overflow");
    }
    function subtract(uint x, uint y) public pure returns (uint z) {
        z = x - y;
        require(z <= x, "uint-uint-sub-underflow");
    }
    function multiply(uint x, uint y) public pure returns (uint z) {
        require(y == 0 || (z = x * y) / y == x, "uint-uint-mul-overflow");
    }
    function rmultiply(uint x, uint y) public pure returns (uint z) {
        z = multiply(x, y) / RAY;
    }
    function rdivide(uint x, uint y) public pure returns (uint z) {
        z = multiply(x, RAY) / y;
    }
    function wdivide(uint x, uint y) public pure returns (uint z) {
        z = multiply(x, WAD) / y;
    }
    function wmultiply(uint x, uint y) public pure returns (uint z) {
        z = multiply(x, y) / WAD;
    }
    function rpower(uint x, uint n, uint base) public pure returns (uint z) {
        assembly {
            switch x case 0 {switch n case 0 {z := base} default {z := 0}}
            default {
                switch mod(n, 2) case 0 { z := base } default { z := x }
                let half := div(base, 2)  // for rounding.
                for { n := div(n, 2) } n { n := div(n,2) } {
                    let xx := mul(x, x)
                    if iszero(eq(div(xx, x), x)) { revert(0,0) }
                    let xxRound := add(xx, half)
                    if lt(xxRound, xx) { revert(0,0) }
                    x := div(xxRound, base)
                    if mod(n,2) {
                        let zx := mul(z, x)
                        if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) }
                        let zxRound := add(zx, half)
                        if lt(zxRound, zx) { revert(0,0) }
                        z := div(zxRound, base)
                    }
                }
            }
        }
    }
}

////// /nix/store/3d3msxain9q01swpn63dsh9wl2hsal24-geb-treasury-reimbursement/dapp/geb-treasury-reimbursement/src/reimbursement/NoSetupIncreasingTreasuryReimbursement.sol
/* pragma solidity 0.6.7; */

/* import "../math/GebMath.sol"; */

abstract contract StabilityFeeTreasuryLike_1 {
    function getAllowance(address) virtual external view returns (uint, uint);
    function systemCoin() virtual external view returns (address);
    function pullFunds(address, address, uint) virtual external;
}

contract NoSetupIncreasingTreasuryReimbursement is GebMath {
    // --- Auth ---
    mapping (address => uint) public authorizedAccounts;
    /**
     * @notice Add auth to an account
     * @param account Account to add auth to
     */
    function addAuthorization(address account) virtual external isAuthorized {
        authorizedAccounts[account] = 1;
        emit AddAuthorization(account);
    }
    /**
     * @notice Remove auth from an account
     * @param account Account to remove auth from
     */
    function removeAuthorization(address account) virtual external isAuthorized {
        authorizedAccounts[account] = 0;
        emit RemoveAuthorization(account);
    }
    /**
    * @notice Checks whether msg.sender can call an authed function
    **/
    modifier isAuthorized {
        require(authorizedAccounts[msg.sender] == 1, "NoSetupIncreasingTreasuryReimbursement/account-not-authorized");
        _;
    }

    // --- Variables ---
    // Starting reward for the fee receiver/keeper
    uint256 public baseUpdateCallerReward;          // [wad]
    // Max possible reward for the fee receiver/keeper
    uint256 public maxUpdateCallerReward;           // [wad]
    // Max delay taken into consideration when calculating the adjusted reward
    uint256 public maxRewardIncreaseDelay;          // [seconds]
    // Rate applied to baseUpdateCallerReward every extra second passed beyond a certain point (e.g next time when a specific function needs to be called)
    uint256 public perSecondCallerRewardIncrease;   // [ray]

    // SF treasury
    StabilityFeeTreasuryLike_1  public treasury;

    // --- Events ---
    event AddAuthorization(address account);
    event RemoveAuthorization(address account);
    event ModifyParameters(
      bytes32 parameter,
      address addr
    );
    event ModifyParameters(
      bytes32 parameter,
      uint256 val
    );
    event FailRewardCaller(bytes revertReason, address feeReceiver, uint256 amount);

    constructor() public {
        authorizedAccounts[msg.sender] = 1;
        maxRewardIncreaseDelay         = uint(-1);

        emit AddAuthorization(msg.sender);
    }

    // --- Boolean Logic ---
    function either(bool x, bool y) internal pure returns (bool z) {
        assembly{ z := or(x, y)}
    }
    function both(bool x, bool y) internal pure returns (bool z) {
        assembly{ z := and(x, y)}
    }

    // --- Treasury ---
    /**
    * @notice This returns the stability fee treasury allowance for this contract by taking the minimum between the per block and the total allowances
    **/
    function treasuryAllowance() public view returns (uint256) {
        (uint total, uint perBlock) = treasury.getAllowance(address(this));
        return minimum(total, perBlock);
    }
    /*
    * @notice Get the SF reward that can be sent to a function caller right now
    * @param timeOfLastUpdate The last time when the function that the treasury pays for has been updated
    * @param defaultDelayBetweenCalls Enforced delay between calls to the function for which the treasury reimburses callers
    */
    function getCallerReward(uint256 timeOfLastUpdate, uint256 defaultDelayBetweenCalls) public view returns (uint256) {
        // If the rewards are null or if the time of the last update is in the future or present, return 0
        bool nullRewards = (baseUpdateCallerReward == 0 && maxUpdateCallerReward == 0);
        if (either(timeOfLastUpdate >= now, nullRewards)) return 0;

        // If the time elapsed is smaller than defaultDelayBetweenCalls or if the base reward is zero, return 0
        uint256 timeElapsed = (timeOfLastUpdate == 0) ? defaultDelayBetweenCalls : subtract(now, timeOfLastUpdate);
        if (either(timeElapsed < defaultDelayBetweenCalls, baseUpdateCallerReward == 0)) {
            return 0;
        }

        // If too much time elapsed, return the max reward
        uint256 adjustedTime      = subtract(timeElapsed, defaultDelayBetweenCalls);
        uint256 maxPossibleReward = minimum(maxUpdateCallerReward, treasuryAllowance() / RAY);
        if (adjustedTime > maxRewardIncreaseDelay) {
            return maxPossibleReward;
        }

        // Calculate the reward
        uint256 calculatedReward = baseUpdateCallerReward;
        if (adjustedTime > 0) {
            calculatedReward = rmultiply(rpower(perSecondCallerRewardIncrease, adjustedTime, RAY), calculatedReward);
        }

        // If the reward is higher than max, set it to max
        if (calculatedReward > maxPossibleReward) {
            calculatedReward = maxPossibleReward;
        }
        return calculatedReward;
    }
    /**
    * @notice Send a stability fee reward to an address
    * @param proposedFeeReceiver The SF receiver
    * @param reward The system coin amount to send
    **/
    function rewardCaller(address proposedFeeReceiver, uint256 reward) internal {
        // If the receiver is the treasury itself or if the treasury is null or if the reward is zero, return
        if (address(treasury) == proposedFeeReceiver) return;
        if (either(address(treasury) == address(0), reward == 0)) return;

        // Determine the actual receiver and send funds
        address finalFeeReceiver = (proposedFeeReceiver == address(0)) ? msg.sender : proposedFeeReceiver;
        try treasury.pullFunds(finalFeeReceiver, treasury.systemCoin(), reward) {}
        catch(bytes memory revertReason) {
            emit FailRewardCaller(revertReason, finalFeeReceiver, reward);
        }
    }
}

////// /nix/store/n0zrh7hav4swn38ckv0y2panmrlaxy1s-geb-fsm/dapp/geb-fsm/src/FSMWrapper.sol
/* pragma solidity 0.6.7; */

/* import "geb-treasury-reimbursement/reimbursement/NoSetupIncreasingTreasuryReimbursement.sol"; */

abstract contract FSMLike {
    function stopped() virtual public view returns (uint256);
    function priceSource() virtual public view returns (address);
    function updateDelay() virtual public view returns (uint16);
    function lastUpdateTime() virtual public view returns (uint64);
    function newPriceDeviation() virtual public view returns (uint256);
    function passedDelay() virtual public view returns (bool);
    function getNextBoundedPrice() virtual public view returns (uint128);
    function getNextPriceLowerBound() virtual public view returns (uint128);
    function getNextPriceUpperBound() virtual public view returns (uint128);
    function getResultWithValidity() virtual external view returns (uint256, bool);
    function getNextResultWithValidity() virtual external view returns (uint256, bool);
    function read() virtual external view returns (uint256);
}

contract FSMWrapper is NoSetupIncreasingTreasuryReimbursement {
    // --- Vars ---
    // When the rate has last been relayed
    uint256 public lastReimburseTime;       // [timestamp]
    // Enforced gap between reimbursements
    uint256 public reimburseDelay;          // [seconds]

    FSMLike public fsm;

    constructor(address fsm_, uint256 reimburseDelay_) public NoSetupIncreasingTreasuryReimbursement() {
        require(fsm_ != address(0), "FSMWrapper/null-fsm");

        fsm            = FSMLike(fsm_);
        reimburseDelay = reimburseDelay_;

        emit ModifyParameters("reimburseDelay", reimburseDelay);
    }

    // --- Administration ---
    /*
    * @notice Change the addresses of contracts that this wrapper is connected to
    * @param parameter The contract whose address is changed
    * @param addr The new contract address
    */
    function modifyParameters(bytes32 parameter, address addr) external isAuthorized {
        require(addr != address(0), "FSMWrapper/null-addr");
        if (parameter == "fsm") {
          fsm = FSMLike(addr);
        }
        else if (parameter == "treasury") {
          require(StabilityFeeTreasuryLike_1(addr).systemCoin() != address(0), "FSMWrapper/treasury-coin-not-set");
          treasury = StabilityFeeTreasuryLike_1(addr);
        }
        else revert("FSMWrapper/modify-unrecognized-param");
        emit ModifyParameters(
          parameter,
          addr
        );
    }
    /*
    * @notify Modify a uint256 parameter
    * @param parameter The parameter name
    * @param val The new parameter value
    */
    function modifyParameters(bytes32 parameter, uint256 val) external isAuthorized {
        if (parameter == "baseUpdateCallerReward") {
          require(val <= maxUpdateCallerReward, "FSMWrapper/invalid-base-caller-reward");
          baseUpdateCallerReward = val;
        }
        else if (parameter == "maxUpdateCallerReward") {
          require(val >= baseUpdateCallerReward, "FSMWrapper/invalid-max-caller-reward");
          maxUpdateCallerReward = val;
        }
        else if (parameter == "perSecondCallerRewardIncrease") {
          require(val >= RAY, "FSMWrapper/invalid-caller-reward-increase");
          perSecondCallerRewardIncrease = val;
        }
        else if (parameter == "maxRewardIncreaseDelay") {
          require(val > 0, "FSMWrapper/invalid-max-increase-delay");
          maxRewardIncreaseDelay = val;
        }
        else if (parameter == "reimburseDelay") {
          reimburseDelay = val;
        }
        else revert("FSMWrapper/modify-unrecognized-param");
        emit ModifyParameters(
          parameter,
          val
        );
    }

    // --- Renumeration Logic ---
    /*
    * @notice Renumerate the caller that updates the connected FSM
    * @param feeReceiver The address that will receive the reward for the update
    */
    function renumerateCaller(address feeReceiver) external {
        // Perform checks
        require(address(fsm) == msg.sender, "FSMWrapper/invalid-caller");
        require(feeReceiver != address(0), "FSMWrapper/null-fee-receiver");
        // Check delay between calls
        require(either(subtract(now, lastReimburseTime) >= reimburseDelay, lastReimburseTime == 0), "FSMWrapper/wait-more");
        // Get the caller's reward
        uint256 callerReward = getCallerReward(lastReimburseTime, reimburseDelay);
        // Store the timestamp of the update
        lastReimburseTime = now;
        // Pay the caller for updating the FSM
        rewardCaller(feeReceiver, callerReward);
    }

    // --- Wrapped Functionality ---
    /*
    * @notify Return whether the FSM is stopped
    */
    function stopped() public view returns (uint256) {
        return fsm.stopped();
    }
    /*
    * @notify Return the FSM price source
    */
    function priceSource() public view returns (address) {
        return fsm.priceSource();
    }
    /*
    * @notify Return the FSM update delay
    */
    function updateDelay() public view returns (uint16) {
        return fsm.updateDelay();
    }
    /*
    * @notify Return the FSM last update time
    */
    function lastUpdateTime() public view returns (uint64) {
        return fsm.lastUpdateTime();
    }
    /*
    * @notify Return the FSM's next price deviation
    */
    function newPriceDeviation() public view returns (uint256) {
        return fsm.newPriceDeviation();
    }
    /*
    * @notify Return whether the update delay has been passed in the FSM
    */
    function passedDelay() public view returns (bool) {
        return fsm.passedDelay();
    }
    /*
    * @notify Return the next bounded price from the FSM
    */
    function getNextBoundedPrice() public view returns (uint128) {
        return fsm.getNextBoundedPrice();
    }
    /*
    * @notify Return the next lower bound price from the FSM
    */
    function getNextPriceLowerBound() public view returns (uint128) {
        return fsm.getNextPriceLowerBound();
    }
    /*
    * @notify Return the next upper bound price from the FSM
    */
    function getNextPriceUpperBound() public view returns (uint128) {
        return fsm.getNextPriceUpperBound();
    }
    /*
    * @notify Return the result with its validity from the FSM
    */
    function getResultWithValidity() external view returns (uint256, bool) {
        (uint256 price, bool valid) = fsm.getResultWithValidity();
        return (price, valid);
    }
    /*
    * @notify Return the next result with its validity from the FSM
    */
    function getNextResultWithValidity() external view returns (uint256, bool) {
        (uint256 price, bool valid) = fsm.getNextResultWithValidity();
        return (price, valid);
    }
    /*
    * @notify Return the result from the FSM if it's valid
    */
    function read() external view returns (uint256) {
        return fsm.read();
    }
}