contract GoaldProxy is ERC20 {
    /** 
     * @dev The minimum amount of tokens necessary to be eligible for a reward. This is "one token", considering decimal places. We
     * are choosing two decimal places because we are initially targeting WBTC, which has 8. This way we can do a minimum reward ratio
     * of 1 / 1,000,000 of a WBTC, relative to our token. So at $25,000 (2020 value), the minimum reward would be $250 (assuming we
     * have issued all 10,000 tokens).
     */
    uint256 private constant REWARD_THRESHOLD = 10*2;

    /** @dev The current owner of the proxy. */
    address public  _owner = msg.sender;

    /** @dev Which Uniswap router we're currently using for trades. */
    address private _uniswapRouterAddress;

    /** @dev The latest proxy address. This is private since Goald contracts use `getProxyAddress()` to determine the address. */
    address private _proxyAddress = address(this);

    /** @dev The address of the token that will be used as a liquidity intermediary within Uniswap for indirect swaps (e.g., WETH). */
    address private _intermediaryToken;

    /** @dev The base token URI for the Goald metadata. */
    string  private _baseTokenURI;


    /** @dev Which deployers are allowed to create new Goalds. We use a mapping for O(1) lookups and an array for complete list. */
    mapping (address => bool) private _allowedDeployersMap;
    address[] private _allowedDeployersList;

    /** @dev The addresses of all deployed goalds. */
    address[] private _deployedGoalds;

    /** @dev The owner of each deployed goald. */
    address[] private _goaldOwners;


    /** @dev Which ERC20 contract will be used for rewards (e.g., WBTC). */
    address   private _rewardToken;

    /** @dev How many holders are eligible for rewards. This is used to determine how much should be reserved. */
    uint256   private _rewardHolders;

    /** @dev How much of the current balance is reserved for rewards. */
    uint256   private _reservedRewardBalance;

    /** @dev How many holders have yet to withdraw a given reward. */
    uint256[] private _rewardHolderCounts;

    /** @dev The multipliers for each reward. */
    uint256[] private _rewardMultipliers;

    /** @dev The remaining reserves for a given reward. */
    uint256[] private _rewardReserves;

    /** @dev The minimum reward index to check eligibility against for a given address. */
    mapping (address => uint256) private _minimumRewardIndex;
    
    /** @dev The available reward balance for a given address. */
    mapping (address => uint256) private _rewardBalance;

    /**
     * @dev The stage of the governance token. Tokens can be issued based on deployments regardless of what stage we are in.
     *      0: Created, with no governance protocol initiated. The initial governance issuance can be claimed.
     *      1: Initial governance issuance has been claimed.
     *      2: The governance protocal has been initiated.
     *      3: All governance tokens have been issued.
     */
    uint256 private constant STAGE_INITIAL               = 0;
    uint256 private constant STAGE_ISSUANCE_CLAIMED      = 1;
    uint256 private constant STAGE_DAO_INITIATED         = 2;
    uint256 private constant STAGE_ALL_GOVERNANCE_ISSUED = 3;
    uint256 private _governanceStage;

    // Reentrancy reversions are the only calls to revert (in this contract) that do not have reasons. We add a third state, 'frozen'
    // to allow for locking non-admin functions. The contract may be permanently frozen if it has been upgraded.
    uint256 private constant RE_NOT_ENTERED = 1;
    uint256 private constant RE_ENTERED     = 2;
    uint256 private constant RE_FROZEN      = 3;
    uint256 private _status;

    // Override decimal places to 2. See `REWARD_THRESHOLD`.
    constructor() ERC20("Goald", "GOALD") public {
        _setupDecimals(2);
        _status = RE_NOT_ENTERED;
        _proxyAddress = address(this);
    }

    /// Events ///

    event OwnerChanged(uint256 id, address owner);

    event RewardCreated(uint256 multiplier, string reason);

    /// Admin Functions ///

    /** Adds an allowed deployer. */
    function addAllowedDeployer(address newDeployer) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED || _status == RE_FROZEN);
        require(msg.sender == _owner,               "Not owner");
        require(!_allowedDeployersMap[newDeployer], "Already allowed");

        // Add the deployer.
        _allowedDeployersMap[newDeployer] = true;
        _allowedDeployersList.push(newDeployer);
    }

    /** Freezes the proxy contract. Only admin functions can be called. */
    function freeze() external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED);
        require(msg.sender == _owner, "Not owner");

        _status = RE_FROZEN;
    }

    /** Removes an allowed deployer by index. We require the index for no-traversal removal against a known address. */
    function removeAllowedDeployer(address deployerAddress, uint256 index) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED || _status == RE_FROZEN);
        require(msg.sender == _owner,                 "Not owner");
        require(index < _allowedDeployersList.length, "Out of bounds");

        // Check the address.
        address indexAddress = _allowedDeployersList[index];
        require(indexAddress == deployerAddress,       "Address mismatch");
        require(_allowedDeployersMap[deployerAddress], "Already restricted");

        // Remove the deployer.
        _allowedDeployersMap[deployerAddress] = false;
        _allowedDeployersList[index] = _allowedDeployersList[index - 1];
        _allowedDeployersList.pop();
    }

    /** Sets the base url for Goald metadata. */
    function setBaseTokenURI(string calldata baseTokenURI) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED || _status == RE_FROZEN);
        require(msg.sender == _owner, "Not owner");

        _baseTokenURI = baseTokenURI;
    }

    function setOwner(address newOwner) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED || _status == RE_FROZEN);
        require(msg.sender == _owner,   "Not owner");
        require(newOwner != address(0), "Can't be zero address");

        _owner = newOwner;
    }

    /** The proxy address is what the Goald deployers send their fees to. */
    function setProxyAddress(address newAddress) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED || _status == RE_FROZEN);
        require(msg.sender == _owner,     "Not owner");
        require(newAddress != address(0), "Can't be zero address");
        require(IGoaldProxy(newAddress).getProxyAddress() == newAddress);

        _proxyAddress = newAddress;
    }

    /** The uniswap router for converting tokens within this proxys. */
    function setUniswapRouterAddress(address newAddress) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED || _status == RE_FROZEN);
        require(msg.sender == _owner,     "Not owner");
        require(newAddress != address(0), "Can't be zero address");

        _uniswapRouterAddress = newAddress;
    }

    /** Unfreezes the proxy contract. Non-admin functions can again be called. */
    function unfreeze() external {
        // Reentrancy guard.
        require(_status == RE_FROZEN);
        require(msg.sender == _owner, "Not owner");

        _status = RE_NOT_ENTERED;
    }

    /// Goald Deployers ///

    /** Returns the address of the deployer at the specified index. */
    function getDeployerAt(uint256 index) external view returns (address) {
        return _allowedDeployersList[index];
    }

    /** Returns the address and owner of the Goald at the specified index. */
    function getGoaldAt(uint256 index) external view returns (address[2] memory) {
        return [_deployedGoalds[index], _goaldOwners[index]];
    }

    /** Gets the token that is used as an intermediary in Uniswap swaps for token pairs that have insufficient liquidity. */
    function getIntermediaryToken() external view returns (address) {
        return _intermediaryToken;
    }

    /** Returns the next Goald id so that we have a unique ID for each NFT, regardless of which deployer was used. */
    function getNextGoaldId() external view returns (uint256) {
        return _deployedGoalds.length + 1;
    }

    /** Returns the current address that fees will be sent to. */
    function getProxyAddress() external view returns (address) {
        return _proxyAddress;
    }

    /** Return the metadata for a specific Goald. */
    function getTokenURI(uint256 tokenId) external view returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT licence
        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        uint256 temp = tokenId;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        uint256 index = digits - 1;
        temp = tokenId;
        while (temp != 0) {
            buffer[index--] = byte(uint8(48 + temp % 10));
            temp /= 10;
        }

        return string(abi.encodePacked(_baseTokenURI, string(buffer)));
    }

    /** Returns the address of the uniswap router. */
    function getUniswapRouterAddress() external view returns (address) {
        return _uniswapRouterAddress;
    }

    /** Returns if the address is an allowed deployer. */
    function isAllowedDeployer(address deployer) external view returns (bool) {
        return _allowedDeployersMap[deployer];
    }

    /**
     * Called when a deployer deploys a new Goald. Currently we use this to distribute the governance token according to the following
     * schedule. An additional 12,000 tokens will be claimable by the deployer of this proxy. This will create a total supply of
     * 21,000 tokens. Once the governance protocal is set up, 11,000 tokens will be burned to initiate that mechanism. That will leave
     * 10% ownership for the deployer of the contract, with the remaining 90% disbused on Goald creations. No rewards can be paid out
     * before the governance protocal has been initiated.
     *
     *      # Goalds    # Tokens
     *       0 -  9       100
     *      10 - 19        90
     *      20 - 29        80
     *      30 - 39        70
     *      40 - 49        60
     *      50 - 59        50
     *      60 - 69        40
     *      70 - 79        30
     *      80 - 89        20
     *      90 - 99        10
     *       < 3600         1
     */
    function notifyGoaldCreated(address creator, address goaldAddress) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED);
        require(_allowedDeployersMap[msg.sender], "Not allowed deployer");
        require(_proxyAddress == address(this),   "Not latest proxy");

        // All governance tokens have been issued.
        if (_governanceStage == STAGE_ALL_GOVERNANCE_ISSUED) {
            _deployedGoalds.push(goaldAddress);
            _goaldOwners.push(creator);
            return;
        }

        // Calculate the amount of tokens issued based on the schedule.
        uint256 goaldsDeployed = _deployedGoalds.length;
        uint256 amount;
        if        (goaldsDeployed <   10) {
            amount = 100;
        } else if (goaldsDeployed <   20) {
            amount =  90;
        } else if (goaldsDeployed <   30) {
            amount =  80;
        } else if (goaldsDeployed <   40) {
            amount =  70;
        } else if (goaldsDeployed <   50) {
            amount =  60;
        } else if (goaldsDeployed <   60) {
            amount =  50;
        } else if (goaldsDeployed <   70) {
            amount =  40;
        } else if (goaldsDeployed <   80) {
            amount =  30;
        } else if (goaldsDeployed <   90) {
            amount =  20;
        } else if (goaldsDeployed <  100) {
            amount =  10;
        } else if (goaldsDeployed < 3600) {
            amount =   1;
        }

        // It's possible we have issued all governance tokens without the DAO initiated.
        if (amount > 0) {
            // Update their reward balance.
            _checkRewardBalance(creator);

            // We are creating a new holder.
            if (balanceOf(creator) < REWARD_THRESHOLD) {
                _rewardHolders ++;
            }

            // Give them the tokens.
            _mint(creator, amount * REWARD_THRESHOLD);
        }

        // We have issued all tokens, so move to the last stage of governance. This will short circuit this function on future calls.
        if (goaldsDeployed >= 3600 && _governanceStage == STAGE_DAO_INITIATED) {
            _governanceStage = STAGE_ALL_GOVERNANCE_ISSUED;
        }

        // Track the goald and its owner.
        _deployedGoalds.push(goaldAddress);
        _goaldOwners.push(creator);
    }

    /** Updates the owner of a deployed Goald. */
    function setGoaldOwner(uint256 id) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED);
        _status = RE_ENTERED;

        // Id is offset by one of the index
        id --;
        require(id < _deployedGoalds.length, "Invalid id");

        // We don't have the address as a parameter so we are guaranteed to always have the correct value stored here.
        address owner = IERC721(_deployedGoalds[id]).ownerOf(id + 1);
        _goaldOwners[id] = owner;

        // Hello world!
        emit OwnerChanged(id + 1, owner);

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _status = RE_NOT_ENTERED;
    }

    /// Governance ///

    /** Claims the initial issuance of the governance token to enable bootstrapping the DAO. */
    function claimIssuance() external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED || _status == RE_FROZEN);
        require(msg.sender == _owner,              "Not owner");
        require(_governanceStage == STAGE_INITIAL, "Already claimed");

        _mint(_owner, 12000 * REWARD_THRESHOLD);

        _governanceStage = STAGE_ISSUANCE_CLAIMED;
    }

    /** Uses Uniswap to convert all held amount of a specific token into the reward token, using the provided path. */
    function convertToken(address[] calldata path, uint256 deadline) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED);
        _status = RE_ENTERED;
        require(msg.sender == _owner,                    "Not owner");
            
        // Make sure this contract actually has a balance.
        IERC20 tokenContract = IERC20(path[0]);
        uint256 amount = tokenContract.balanceOf(address(this));
        require(amount > 0, "No balance for token");

        // Make sure the reward token is the last address in the path. Since the array is calldata we don't want to spend the gas to
        // push this onto the end.
        require(path[path.length - 1] == _rewardToken, "Last must be reward token");

        // Swap the tokens.
        tokenContract.approve(_uniswapRouterAddress, amount);
        IUniswapV2Router02(_uniswapRouterAddress).swapExactTokensForTokens(amount, 1, path, address(this), deadline);

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _status = RE_NOT_ENTERED;
    }

    /**
     * Uses Uniswap to convert all held amount of specific tokens into the reward token. The tokens must have a direct path,
     * otherwise the intermediary is used for increased liquidity.
     */
    function convertTokens(address[] calldata tokenAddresses, bool isIndirect, uint256 deadline) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED);
        _status = RE_ENTERED;
        require(msg.sender == _owner, "Not owner");

        // The path between a given token and the reward token within Uniswap.
        address[] memory path;
        if (isIndirect) {
            path[1] = _intermediaryToken;
            path[2] = _rewardToken;
        } else {
            path[1] = _rewardToken;
        }
        IUniswapV2Router02 uniswap = IUniswapV2Router02(_uniswapRouterAddress);

        address tokenAddress;
        IERC20 tokenContract;
        
        uint256 amount;
        uint256 count = tokenAddresses.length;
        for (uint256 i; i < count; i ++) {
            // Validate the token.
            tokenAddress = tokenAddresses[i];
            require(tokenAddress != address(0),    "Can't be zero address");
            require(tokenAddress != address(this), "Can't be this address");
            require(tokenAddress != _rewardToken,  "Can't be target address");
            
            // Make sure this contract actually has a balance.
            tokenContract = IERC20(tokenAddress);
            amount = tokenContract.balanceOf(address(this));
            if (amount == 0) {
                continue;
            }

            // Swap the tokens.
            tokenContract.approve(_uniswapRouterAddress, amount);
            path[0] = tokenAddress;
            uniswap.swapExactTokensForTokens(amount, 1, path, address(this), deadline);
        }

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _status = RE_NOT_ENTERED;
    }

    /** Returns the current stage of the DAO's governance. */
    function getGovernanceStage() external view returns (uint256) {
        return _governanceStage;
    }

    /** Releases ownership of this proxy and all subsequent ones to the DAO. */
    function initializeDAO() external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED);
        require(msg.sender == _owner,                       "Not owner");
        require(_governanceStage == STAGE_ISSUANCE_CLAIMED, "Issuance unclaimed");

        _burn(_owner, 11000 * REWARD_THRESHOLD);

        _governanceStage = STAGE_DAO_INITIATED;
    }

    /**
     * Changes which token will be the reward token. This can only happen if there is no balance in reserve held for rewards. If a
     * change is desired despite there being excess rewards, call `withdrawReward()` on behalf of each holder to drain the reserve.
     */
    function setRewardToken(address newToken) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED || _status == RE_FROZEN);
        require(msg.sender == _owner,        "Not owner");
        require(newToken != address(0),      "Can't be zero address");
        require(newToken != address(this),   "Can't be this address");
        require(_reservedRewardBalance == 0, "Have reserved balance");

        _rewardToken = newToken;
    }

    /// Rewards ///

    /**
     * Check which rewards a given address is eligible for, and update their current reward balance to reflect that total. Since
     * balances are static until transferred (or minted in the case of a new Goald being created), this function is called before
     * any change to a given addresses' balance. Ths will bring them up to date with any past, unclaimed rewards. Any future rewards
     * will be dependant on their balance after the change.
     */
    function _checkRewardBalance(address holder) internal {
        // There is no need for reentrancy since this only updates the `_rewardBalance` for a given holder according to the amounts
        // they are already owed according to the current state. If this is an unexpected reentrant call, then that holder gets the
        // benefit of this math without having to pay the gas.

        // The total number of rewards issued.
        uint256 count = _rewardMultipliers.length;

        // The holder has already claimed all rewards.
        uint256 currentMinimumIndex = _minimumRewardIndex[holder];
        if (currentMinimumIndex == count) {
            return;
        }

        // The holder is not eligible for a reward according to their current balance.
        uint256 balance = balanceOf(holder);
        if (balance < REWARD_THRESHOLD) {
            // Mark that they have been checked for all rewards.
            if (currentMinimumIndex < count) {
                _minimumRewardIndex[holder] = count;
            }

            return;
        }

        // Calculate the balance increase according to which rewards the holder has yet to claim. Also calculate the amount of the
        // reserve should be released if a given reward has been fully collected by all holders.
        uint256 multiplier;
        uint256 reserveDecrease;
        for (; currentMinimumIndex < count; currentMinimumIndex ++) {
            // This can never overflow since a reward can't be created unless there is enough reserve balance to cover its
            // multiplier, which already checks for overflows, likewise `multiplier * balance` can never overflow.
            multiplier += _rewardMultipliers[currentMinimumIndex];

            // Reduce the holder count and reserve for this reward. If this is the last holder, we refund the remainder of the held
            // reserve back to the main pool. We don't need to worry about underflows here because these values never increase. They
            // are set once when the reward is created, based on the total supply of the governance token at that time.
            if (_rewardHolderCounts[currentMinimumIndex] == 1) {
                // This tracks the sum of any excess remainder across all rewards this address is the last holder of. We are
                // offsetting by their eligible balance because we must still hold in reserve their reward until such time that they
                // choose to claim it.
                reserveDecrease += _rewardReserves[currentMinimumIndex] - (multiplier * balance);
                _rewardHolderCounts[currentMinimumIndex] = 0;
                _rewardReserves[currentMinimumIndex] = 0;
                // We don't wipe `_rewardMultipliers` here despite this being the last holder, so we have a historical record.
            } else {
                _rewardHolderCounts[currentMinimumIndex]--;
                _rewardReserves[currentMinimumIndex] -= multiplier * balance;
            }
        }
        _minimumRewardIndex[holder] = count;

        // Update the balance.
        uint256 currentBalance = _rewardBalance[holder];
        require(currentBalance + (multiplier * balance) > currentBalance, "Balance overflow");
        _rewardBalance[holder] = currentBalance + (multiplier * balance);   

        // Update the reserve balance.
        if (reserveDecrease > 0) {
            _reservedRewardBalance -= reserveDecrease;
        }
    }

    /**
     * Creates a new reward. Rewards are only paid out to holders who have at least "one token" at time of creation. The reward
     * is a multiplier, representing how many reward tokens (e.g., WBTC) should be paid out for one governance token. reward
     * eligibility is only updated in state in two cases:
     *      1) When a reward is being withdrawn (in which it is set to zero).
     *      2) When the governance token is transferred (balances are checked before the transfer, on both sender and recipient).
     */
    function createReward(uint256 multiplier, string calldata reason) external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED);
        _status = RE_ENTERED;
        require(msg.sender == _owner,                    "Not owner");
        require(_governanceStage >= STAGE_DAO_INITIATED, "DAO not initiated");
        require(multiplier > 0,                          "Multiplier must be > 0");

        // Make sure we can actually create a reward with that amount. This balance of the reward token at this proxy address should
        // never decrease except when rewards are claimed by holders.
        uint256 reservedRewardBalance = _reservedRewardBalance;
        uint256 currentBalance = IERC20(_rewardToken).balanceOf(address(this));
        require(currentBalance >= reservedRewardBalance, "Current reserve insufficient");
        uint256 reserveIncrease = totalSupply() * multiplier;
        require(reserveIncrease <= currentBalance - reservedRewardBalance, "Multiplier too large");

        // Increase the reserve.
        require(reservedRewardBalance + reserveIncrease > reservedRewardBalance, "Reserved overflow error");
        reservedRewardBalance += reserveIncrease;

        // Keep track of the holders, reserve, and multiplier for this reward. These values will not increase after being set here.
        uint256 holders = _rewardHolders;
        require(holders > 0, "Must have a holder");
        _rewardHolderCounts.push(holders);
        _rewardMultipliers.push(multiplier);
        _rewardReserves.push(reserveIncrease);

        // Hello world!
        emit RewardCreated(multiplier, reason);

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _status = RE_NOT_ENTERED;
    }

    /** Returns the reward balance for a holder according to the true state, not the hard state. See: `_checkRewardBalance()`. */
    function getHolderRewardBalance(address holder) external view returns (uint256) {
        uint256 count = _rewardMultipliers.length;
        uint256 balance = balanceOf(holder);
        uint256 rewardBalance = _rewardBalance[holder];
        uint256 currentMinimumIndex = _minimumRewardIndex[holder];
        for (; currentMinimumIndex < count; currentMinimumIndex ++) {
            rewardBalance += _rewardMultipliers[currentMinimumIndex] * balance;
        }

        return rewardBalance;
    }

    /** Get the details of the reward at the specified index.*/
    function getHolderRewardDetailsAt(uint256 index) external view returns (uint256[3] memory) {
        return [
            _rewardMultipliers[index],
            _rewardHolderCounts[index],
            _rewardReserves[index]
        ];
    }

    /** Return the historical reward details. */
    function getRewardDetails() external view returns (uint256[3] memory) {
        return [
            uint256(_rewardToken),
            _rewardHolders,
            _reservedRewardBalance
        ];
    }

    /**
     * Withdraws the current reward balance. The sender doesn't need to have any current balance of the governance token to
     * withdraw, so long as they have a preexisting outstanding balance. This has a provided recipient so that we can drain the
     * reward pool as necessary (e.g., for changing the reward token).
     */
    function withdrawReward(address holder) external {
        // Reentrancy guard. Allow owner to drain the pool even if frozen.
        require(_status == RE_NOT_ENTERED || (_status == RE_FROZEN && msg.sender == _owner));
        _status = RE_ENTERED;

        // Update their balance.
        _checkRewardBalance(holder);

        // Revert so gas estimators will show a failure.
        uint256 balance = _rewardBalance[holder];
        require(balance > 0, "No reward balance");

        // Wipe the balance.
        _rewardBalance[holder] = 0;
        require(_reservedRewardBalance - balance > 0, "Reserved balance underflow");
        _reservedRewardBalance -= balance;

        // Give them their balance.
        IERC20(_rewardToken).transfer(holder, balance);

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _status = RE_NOT_ENTERED;
    }

    /// ERC20 Overrides ///

    /** This is overridden so we can update the reward balancees prior to the transfer completing. */
    function transfer(address recipient, uint256 amount) public override returns (bool) {
        return transferFrom(msg.sender, recipient, amount);
    }

    /** This is overridden so we can update the reward balancees prior to the transfer completing. */
    function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
        // Update the reward balances prior to the transfer for both sender and receiver.
        _checkRewardBalance(sender);
        _checkRewardBalance(recipient);

        // Preserve the original balances so we know if we need to change `_rewardHolders`.
        uint256 senderBefore = balanceOf(sender);
        uint256 recipientBefore = balanceOf(recipient);

        super.transferFrom(sender, recipient, amount);

        // See if we need to change `_rewardHolders`.
        uint256 senderAfter = balanceOf(sender);
        if        (senderBefore  < REWARD_THRESHOLD && senderAfter >= REWARD_THRESHOLD) {
            _rewardHolders ++;
        } else if (senderBefore >= REWARD_THRESHOLD && senderAfter  < REWARD_THRESHOLD) {
            _rewardHolders --;
        }
        uint256 recipientAfter = balanceOf(recipient);
        if        (recipientBefore  < REWARD_THRESHOLD && recipientAfter >= REWARD_THRESHOLD) {
            _rewardHolders ++;
        } else if (recipientBefore >= REWARD_THRESHOLD && recipientAfter  < REWARD_THRESHOLD) {
            _rewardHolders --;
        }

        // The sender has no balance, so clear their minimum index. This should save on total storage space for this contract. We do
        // not clear the reward balance even if their token balance is zero, since they still have a claim to that balance.
        if (senderAfter == 0) {
            _minimumRewardIndex[msg.sender] = 0;
        }

        return true;
    }
}
function createReward(uint256 multiplier, string calldata reason) external {
    // Reentrancy guard.
    require(_status == RE_NOT_ENTERED);
    _status = RE_ENTERED;
    require(msg.sender == _owner,                    "Not owner");
    require(_governanceStage >= STAGE_DAO_INITIATED, "DAO not initiated");
    require(multiplier > 0,                          "Multiplier must be > 0");

    // Make sure we can actually create a reward with that amount. This balance of the reward token at this proxy address should
    // never decrease except when rewards are claimed by holders.
    uint256 reservedRewardBalance = _reservedRewardBalance;
    uint256 currentBalance = IERC20(_rewardToken).balanceOf(address(this));
    require(currentBalance >= reservedRewardBalance, "Current reserve insufficient");
    uint256 reserveIncrease = totalSupply() * multiplier;
    require(reserveIncrease <= currentBalance - reservedRewardBalance, "Multiplier too large");

    // Increase the reserve.
    require(reservedRewardBalance + reserveIncrease > reservedRewardBalance, "Reserved overflow error");
    reservedRewardBalance += reserveIncrease;

    // Keep track of the holders, reserve, and multiplier for this reward. These values will not increase after being set here.
    uint256 holders = _rewardHolders;
    require(holders > 0, "Must have a holder");
    _rewardHolderCounts.push(holders);
    _rewardMultipliers.push(multiplier);
    _rewardReserves.push(reserveIncrease);

    // Hello world!
    emit RewardCreated(multiplier, reason);

    // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
    _status = RE_NOT_ENTERED;
}

/**
 * Creates a new reward. Rewards are only paid out to holders who have at least "one token" at time of creation. The reward
 * is a multiplier, representing how many reward tokens (e.g., WBTC) should be paid out for one governance token. reward
 * eligibility is only updated in state in two cases:
 *      1) When a reward is being withdrawn (in which it is set to zero).
 *      2) When the governance token is transferred (balances are checked before the transfer, on both sender and recipient).
 */