Repository: tellor-io/telliot-feeds (language: python)

Issue:
Check for Disputed Value Tip availability
When a reporter submits a value that qualifies for a tip, that tip is made available again if the submitted value is disputed. Currently the telliot tip listener may not be hearing these tips. This issue wants to make sure that the data keeps flowing in these situations. 


Discussion / hints:

Fix patch:
diff --git a/contracts/SampleReporterContract/SampleFlexReporter.sol b/contracts/SampleReporterContract/SampleFlexReporter.sol
index e5d1817d..ece3e4b1 100644
--- a/contracts/SampleReporterContract/SampleFlexReporter.sol
+++ b/contracts/SampleReporterContract/SampleFlexReporter.sol
@@ -1,5 +1,5 @@
 // SPDX-License-Identifier: MIT
-pragma solidity 0.8.15;
+pragma solidity ^0.8.15;
 
 interface IFlex {
     function balanceOf(address account) external view returns (uint256);
diff --git a/contracts/TellorFlex/Autopay.sol b/contracts/Tellor/Autopay.sol
similarity index 87%
rename from contracts/TellorFlex/Autopay.sol
rename to contracts/Tellor/Autopay.sol
index fd25ed7a..2007e694 100644
--- a/contracts/TellorFlex/Autopay.sol
+++ b/contracts/Tellor/Autopay.sol
@@ -1,9 +1,9 @@
 // SPDX-License-Identifier: MIT
-pragma solidity 0.8.3;
+pragma solidity ^0.8.0;
 
-import "contracts/TellorFlex/UsingTellor.sol";
-import "../../interfaces/IERC20.sol";
-import "contracts/TellorFlex/QueryDataStorage.sol";
+import { UsingTellor } from "./UsingTellor.sol";
+import { IERC20 } from "../../interfaces/IERC20.sol";
+import "../../interfaces/IQueryDataStorage.sol";
 
 /**
  @author Tellor Inc.
@@ -14,7 +14,7 @@ import "contracts/TellorFlex/QueryDataStorage.sol";
 contract Autopay is UsingTellor {
     // Storage
     IERC20 public token; // TRB token address
-    QueryDataStorage public queryDataStorage; // Query data storage contract
+    IQueryDataStorage public queryDataStorage; // Query data storage contract
     uint256 public fee; // 1000 is 100%, 50 is 5%, etc.
 
     mapping(bytes32 => bytes32[]) currentFeeds; // mapping queryId to dataFeedIds array
@@ -101,12 +101,11 @@ contract Autopay is UsingTellor {
      */
     constructor(
         address payable _tellor,
-        address _token,
         address _queryDataStorage,
         uint256 _fee
     ) UsingTellor(_tellor) {
-        token = IERC20(_token);
-        queryDataStorage = QueryDataStorage(_queryDataStorage);
+        token = IERC20(tellor.token());
+        queryDataStorage = IQueryDataStorage(_queryDataStorage);
         fee = _fee;
     }
 
@@ -125,7 +124,8 @@ contract Autopay is UsingTellor {
         uint256 _cumulativeReward;
         for (uint256 _i = 0; _i < _timestamps.length; _i++) {
             _cumulativeReward += _getOneTimeTipAmount(
-                _queryId, _timestamps[_i]
+                _queryId,
+                _timestamps[_i]
             );
         }
         require(
@@ -135,6 +135,7 @@ contract Autopay is UsingTellor {
             )
         );
         token.approve(address(tellor), (_cumulativeReward * fee) / 1000);
+        tellor.addStakingRewards((_cumulativeReward * fee) / 1000);
         if (getCurrentTip(_queryId) == 0) {
             if (queryIdsWithFundingIndex[_queryId] != 0) {
                 uint256 _idx = queryIdsWithFundingIndex[_queryId] - 1;
@@ -155,83 +156,68 @@ contract Autopay is UsingTellor {
      * @dev Allows Tellor reporters to claim their tips in batches
      * @param _feedId unique feed identifier
      * @param _queryId ID of reported data
-     * @param _timestamps[] batch of timestamps array of reported data eligible for reward
+     * @param _timestamps batch of timestamps array of reported data eligible for reward
      */
     function claimTip(
         bytes32 _feedId,
         bytes32 _queryId,
         uint256[] calldata _timestamps
     ) external {
-        uint256 _reward;
+        Feed storage _feed = dataFeed[_queryId][_feedId];
+        uint256 _balance = _feed.details.balance;
+        require(_balance > 0, "no funds available for this feed");
         uint256 _cumulativeReward;
         for (uint256 _i = 0; _i < _timestamps.length; _i++) {
-            _reward = _claimTip(_feedId, _queryId, _timestamps[_i]);
-            _cumulativeReward += _reward;
-        }
-        emit TipClaimed(_feedId, _queryId, _cumulativeReward, msg.sender);
-    }
-    /**
-     * @dev Internal function which allows Tellor reporters to claim their autopay tips
-     * @param _feedId of dataFeed
-     * @param _queryId id of reported data
-     * @param _timestamp timestamp of reported data eligible for reward
-     * @return uint256 reward amount
-     */
-    function _claimTip(
-        bytes32 _feedId,
-        bytes32 _queryId,
-        uint256 _timestamp
-    ) internal returns (uint256) {
-        Feed storage _feed = dataFeed[_queryId][_feedId];
-        bytes memory _valueRetrieved = retrieveData(_queryId, _timestamp);
-        uint256 _n = (_timestamp - _feed.details.startTime) /
-            _feed.details.interval; // finds closest interval _n to timestamp
-        uint256 _c = _feed.details.startTime + _feed.details.interval * _n; // finds timestamp _c of interval _n
-        (
-            bytes memory _valueRetrievedBefore,
-            uint256 _timestampBefore
-        ) = getDataBefore(_queryId, _timestamp);
-        uint256 _priceChange = 0; //price change from last value to current value
-        if (_feed.details.priceThreshold != 0) {
-            uint256 _v1 = _bytesToUint(_valueRetrieved);
-            uint256 _v2 = _bytesToUint(_valueRetrievedBefore);
-            if (_v2 == 0) {
-                _priceChange = 10000;
-            } else if (_v1 >= _v2) {
-                _priceChange = (10000 * (_v1 - _v2)) / _v2;
-            } else {
-                _priceChange = (10000 * (_v2 - _v1)) / _v2;
-            }
-        }
-        if (_priceChange <= _feed.details.priceThreshold) {
-        }
-        uint256 _rewardAmount;
-        if (_feed.details.balance > _feed.details.reward) {
-            _rewardAmount = _feed.details.reward;
-            _feed.details.balance -= _feed.details.reward;
-        } else {
-            _rewardAmount = _feed.details.balance;
-            _feed.details.balance = 0;
-            // Adjust currently funded feeds
-            if (feedsWithFunding.length > 1) {
-                uint256 _idx = _feed.details.feedsWithFundingIndex - 1;
-                // Replace unfunded feed in array with last element
-                feedsWithFunding[_idx] = feedsWithFunding[
-                    feedsWithFunding.length - 1
-                ];
-                bytes32 _feedIdLastFunded = feedsWithFunding[_idx];
-                bytes32 _queryIdLastFunded = queryIdFromDataFeedId[
-                    _feedIdLastFunded
-                ];
-                dataFeed[_queryIdLastFunded][_feedIdLastFunded]
-                    .details
-                    .feedsWithFundingIndex = _idx + 1;
+            require(
+                block.timestamp - _timestamps[_i] > 12 hours,
+                "buffer time has not passed"
+            );
+            require(
+                getReporterByTimestamp(_queryId, _timestamps[_i]) == msg.sender,
+                "message sender not reporter for given queryId and timestamp"
+            );
+            _cumulativeReward += _getRewardAmount(
+                _feedId,
+                _queryId,
+                _timestamps[_i]
+            );
+            if (_cumulativeReward >= _balance) {
+                // Balance runs out
+                require(
+                    _i == _timestamps.length - 1,
+                    "insufficient balance for all submitted timestamps"
+                );
+                _cumulativeReward = _balance;
+                // Adjust currently funded feeds
+                if (feedsWithFunding.length > 1) {
+                    uint256 _idx = _feed.details.feedsWithFundingIndex - 1;
+                    // Replace unfunded feed in array with last element
+                    feedsWithFunding[_idx] = feedsWithFunding[
+                        feedsWithFunding.length - 1
+                    ];
+                    bytes32 _feedIdLastFunded = feedsWithFunding[_idx];
+                    bytes32 _queryIdLastFunded = queryIdFromDataFeedId[
+                        _feedIdLastFunded
+                    ];
+                    dataFeed[_queryIdLastFunded][_feedIdLastFunded]
+                        .details
+                        .feedsWithFundingIndex = _idx + 1;
+                }
+                feedsWithFunding.pop();
+                _feed.details.feedsWithFundingIndex = 0;
             }
-            feedsWithFunding.pop();
-            _feed.details.feedsWithFundingIndex = 0;
+            _feed.rewardClaimed[_timestamps[_i]] = true;
         }
-        _feed.rewardClaimed[_timestamp] = true;
-        return _rewardAmount;
+        _feed.details.balance -= _cumulativeReward;
+        require(
+            token.transfer(
+                msg.sender,
+                _cumulativeReward - ((_cumulativeReward * fee) / 1000)
+            )
+        );
+        token.approve(address(tellor), (_cumulativeReward * fee) / 1000);
+        tellor.addStakingRewards((_cumulativeReward * fee) / 1000);
+        emit TipClaimed(_feedId, _queryId, _cumulativeReward, msg.sender);
     }
 
     /**
@@ -286,7 +272,7 @@ contract Autopay is UsingTellor {
         uint256 _amount
     ) external returns (bytes32 _feedId) {
         require(
-            _queryId == keccak256(_queryData) || uint256(_queryId) <= 100,
+            _queryId == keccak256(_queryData),
             "id must be hash of bytes data"
         );
         _feedId = keccak256(
@@ -336,7 +322,7 @@ contract Autopay is UsingTellor {
         bytes calldata _queryData
     ) external {
         require(
-            _queryId == keccak256(_queryData) || uint256(_queryId) <= 100,
+            _queryId == keccak256(_queryData),
             "id must be hash of bytes data"
         );
         require(_amount > 0, "tip must be greater than zero");
@@ -420,6 +406,7 @@ contract Autopay is UsingTellor {
     {
         return (dataFeed[queryIdFromDataFeedId[_feedId]][_feedId].details);
     }
+
     /**
      * @dev Getter function for currently funded feed details
      * @return FeedDetailsWithQueryData[] array of details for funded feeds
@@ -455,6 +442,7 @@ contract Autopay is UsingTellor {
     function getFundedQueryIds() external view returns (bytes32[] memory) {
         return queryIdsWithFunding;
     }
+
     /**
      * @dev Getter function for currently funded single tips with queryData
      * @return SingleTipsWithQueryData[] array of current tips
@@ -478,6 +466,7 @@ contract Autopay is UsingTellor {
         }
         return _query;
     }
+
     /**
      * @dev Getter function to get number of past tips
      * @param _queryId id of reported data
@@ -568,6 +557,7 @@ contract Autopay is UsingTellor {
     ) external view returns (bool) {
         return dataFeed[_queryId][_feedId].rewardClaimed[_timestamp];
     }
+
     /**
      * @dev Getter function for reading whether a reward has been claimed
      * @param _feedId feedId of dataFeed
@@ -588,6 +578,7 @@ contract Autopay is UsingTellor {
         }
         return _status;
     }
+
     /**
      * @dev Getter function for retrieving the total amount of tips paid by a given address
      * @param _user address of user to query
@@ -627,10 +618,9 @@ contract Autopay is UsingTellor {
             block.timestamp - _timestamp > 12 hours,
             "buffer time has not passed"
         );
-
-        // require(!isInDispute(_queryId, _timestamp), "value disputed");
+        require(!isInDispute(_queryId, _timestamp), "value disputed");
         require(
-            msg.sender == tellor.getReporterByTimestamp(_queryId, _timestamp),
+            msg.sender == getReporterByTimestamp(_queryId, _timestamp),
             "msg sender must be reporter address"
         );
         Tip[] storage _tips = tips[_queryId];
@@ -770,6 +760,7 @@ contract Autopay is UsingTellor {
                 "price threshold not met"
             );
         }
+
         if (_feed.details.balance < _rewardAmount) {
             _rewardAmount = _feed.details.balance;
         }
diff --git a/contracts/Tellor/Governance.sol b/contracts/Tellor/Governance.sol
new file mode 100644
index 00000000..89975cf3
--- /dev/null
+++ b/contracts/Tellor/Governance.sol
@@ -0,0 +1,586 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.3;
+
+import "../../interfaces/IOracle.sol";
+import "../../interfaces/IERC20.sol";
+import "./UsingTellor.sol";
+
+/**
+ @author Tellor Inc.
+ @title Governance
+ @dev This is a governance contract to be used with TellorFlex. It handles disputing
+ * Tellor oracle data and voting on those disputes
+*/
+contract Governance is UsingTellor {
+    // Storage
+    IOracle public oracle; // Tellor oracle contract
+    IERC20 public token; // token used for dispute fees, same as reporter staking token
+    address public oracleAddress; //tellorFlex address
+    address public teamMultisig; // address of team multisig wallet, one of four stakeholder groups
+    uint256 public voteCount; // total number of votes initiated
+    bytes32 public autopayAddrsQueryId =
+        keccak256(abi.encode("AutopayAddresses", abi.encode(bytes("")))); // query id for autopay addresses array
+    mapping(uint256 => Dispute) private disputeInfo; // mapping of dispute IDs to the details of the dispute
+    mapping(bytes32 => uint256) private openDisputesOnId; // mapping of a query ID to the number of disputes on that query ID
+    mapping(uint256 => Vote) private voteInfo; // mapping of dispute IDs to the details of the vote
+    mapping(bytes32 => uint256[]) private voteRounds; // mapping of vote identifier hashes to an array of dispute IDs
+    mapping(address => uint256) private voteTallyByAddress; // mapping of addresses to the number of votes they have cast
+    mapping(address => uint256[]) private disputeIdsByReporter; // mapping of reporter addresses to an array of dispute IDs
+
+    enum VoteResult {
+        FAILED,
+        PASSED,
+        INVALID
+    } // status of a potential vote
+
+    // Structs
+    struct Dispute {
+        bytes32 queryId; // query ID of disputed value
+        uint256 timestamp; // timestamp of disputed value
+        bytes value; // disputed value
+        address disputedReporter; // reporter who submitted the disputed value
+        uint256 slashedAmount; // amount of tokens slashed from reporter
+    }
+
+    struct Tally {
+        uint256 doesSupport; // number of votes in favor
+        uint256 against; // number of votes against
+        uint256 invalidQuery; // number of votes for invalid
+    }
+
+    struct Vote {
+        bytes32 identifierHash; // identifier hash of the vote
+        uint256 voteRound; // the round of voting on a given dispute or proposal
+        uint256 startDate; // timestamp of when vote was initiated
+        uint256 blockNumber; // block number of when vote was initiated
+        uint256 fee; // fee paid to initiate the vote round
+        uint256 tallyDate; // timestamp of when the votes were tallied
+        Tally tokenholders; // vote tally of tokenholders
+        Tally users; // vote tally of users
+        Tally reporters; // vote tally of reporters
+        Tally teamMultisig; // vote tally of teamMultisig
+        bool executed; // boolean of whether the vote was executed
+        VoteResult result; // VoteResult after votes were tallied
+        address initiator; // address which initiated dispute/proposal
+        mapping(address => bool) voted; // mapping of address to whether or not they voted
+    }
+
+    // Events
+    event NewDispute(
+        uint256 _disputeId,
+        bytes32 _queryId,
+        uint256 _timestamp,
+        address _reporter
+    ); // Emitted when a new dispute is opened
+
+    event Voted(
+        uint256 _disputeId,
+        bool _supports,
+        address _voter,
+        bool _invalidQuery
+    ); // Emitted when an address casts their vote
+    event VoteExecuted(uint256 _disputeId, VoteResult _result); // Emitted when a vote is executed
+    event VoteTallied(
+        uint256 _disputeId,
+        VoteResult _result,
+        address _initiator,
+        address _reporter
+    ); // Emitted when all casting for a vote is tallied
+
+    /**
+     * @dev Initializes contract parameters
+     * @param _tellor address of tellor oracle contract to be governed
+     * @param _teamMultisig address of tellor team multisig, one of four voting
+     * stakeholder groups
+     */
+    constructor(address payable _tellor, address _teamMultisig)
+        UsingTellor(_tellor)
+    {
+        oracle = IOracle(_tellor);
+        token = IERC20(oracle.getTokenAddress());
+        oracleAddress = _tellor;
+        teamMultisig = _teamMultisig;
+    }
+
+    /**
+     * @dev Initializes a dispute/vote in the system
+     * @param _queryId being disputed
+     * @param _timestamp being disputed
+     */
+    function beginDispute(bytes32 _queryId, uint256 _timestamp) external {
+        // Ensure value actually exists
+        require(
+            oracle.getBlockNumberByTimestamp(_queryId, _timestamp) != 0,
+            "no value exists at given timestamp"
+        );
+        bytes32 _hash = keccak256(abi.encodePacked(_queryId, _timestamp));
+        // Push new vote round
+        uint256 _disputeId = voteCount + 1;
+        uint256[] storage _voteRounds = voteRounds[_hash];
+        _voteRounds.push(_disputeId);
+
+        // Create new vote and dispute
+        Vote storage _thisVote = voteInfo[_disputeId];
+        Dispute storage _thisDispute = disputeInfo[_disputeId];
+
+        // Initialize dispute information - query ID, timestamp, value, etc.
+        _thisDispute.queryId = _queryId;
+        _thisDispute.timestamp = _timestamp;
+        _thisDispute.disputedReporter = oracle.getReporterByTimestamp(
+            _queryId,
+            _timestamp
+        );
+        // Initialize vote information - hash, initiator, block number, etc.
+        _thisVote.identifierHash = _hash;
+        _thisVote.initiator = msg.sender;
+        _thisVote.blockNumber = block.number;
+        _thisVote.startDate = block.timestamp;
+        _thisVote.voteRound = _voteRounds.length;
+        disputeIdsByReporter[_thisDispute.disputedReporter].push(_disputeId);
+        uint256 _disputeFee = getDisputeFee();
+        if (_voteRounds.length == 1) {
+            require(
+                block.timestamp - _timestamp < 12 hours,
+                "Dispute must be started within reporting lock time"
+            );
+            openDisputesOnId[_queryId]++;
+            // calculate dispute fee based on number of open disputes on query ID
+            _disputeFee = _disputeFee * 2**(openDisputesOnId[_queryId] - 1);
+            // slash a single stakeAmount from reporter
+            _thisDispute.slashedAmount = oracle.slashReporter(_thisDispute.disputedReporter, address(this));
+            _thisDispute.value = oracle.retrieveData(_queryId, _timestamp);
+            oracle.removeValue(_queryId, _timestamp);
+        } else {
+            uint256 _prevId = _voteRounds[_voteRounds.length - 2];
+            require(
+                block.timestamp - voteInfo[_prevId].tallyDate < 1 days,
+                "New dispute round must be started within a day"
+            );
+            _disputeFee = _disputeFee * 2**(_voteRounds.length - 1);
+            _thisDispute.slashedAmount = disputeInfo[_voteRounds[0]].slashedAmount;
+            _thisDispute.value = disputeInfo[_voteRounds[0]].value;
+        }
+        if (_disputeFee > oracle.getStakeAmount()) {
+          _disputeFee = oracle.getStakeAmount();
+        }
+        _thisVote.fee = _disputeFee;
+        voteCount++;
+        require(
+            token.transferFrom(msg.sender, address(this), _disputeFee),
+            "Fee must be paid"
+        ); // This is the dispute fee. Returned if dispute passes
+        emit NewDispute(
+            _disputeId,
+            _queryId,
+            _timestamp,
+            _thisDispute.disputedReporter
+        );
+    }
+
+    /**
+     * @dev Executes vote and transfers corresponding balances to initiator/reporter
+     * @param _disputeId is the ID of the vote being executed
+     */
+    function executeVote(uint256 _disputeId) external {
+        // Ensure validity of vote ID, vote has been executed, and vote must be tallied
+        Vote storage _thisVote = voteInfo[_disputeId];
+        require(_disputeId <= voteCount && _disputeId > 0, "Dispute ID must be valid");
+        require(!_thisVote.executed, "Vote has already been executed");
+        require(_thisVote.tallyDate > 0, "Vote must be tallied");
+        // Ensure vote must be final vote and that time has to be pass (86400 = 24 * 60 * 60 for seconds in a day)
+        require(
+            voteRounds[_thisVote.identifierHash].length == _thisVote.voteRound,
+            "Must be the final vote"
+        );
+        //The time  has to pass after the vote is tallied
+        require(
+            block.timestamp - _thisVote.tallyDate >= 1 days,
+            "1 day has to pass after tally to allow for disputes"
+        );
+        _thisVote.executed = true;
+        Dispute storage _thisDispute = disputeInfo[_disputeId];
+        openDisputesOnId[_thisDispute.queryId]--;
+        uint256 _i;
+        uint256 _voteID;
+        if (_thisVote.result == VoteResult.PASSED) {
+            // If vote is in dispute and passed, iterate through each vote round and transfer the dispute to initiator
+            for (
+                _i = voteRounds[_thisVote.identifierHash].length;
+                _i > 0;
+                _i--
+            ) {
+                _voteID = voteRounds[_thisVote.identifierHash][_i - 1];
+                _thisVote = voteInfo[_voteID];
+                // If the first vote round, also make sure to transfer the reporter's slashed stake to the initiator
+                if (_i == 1) {
+                    token.transfer(
+                        _thisVote.initiator,
+                        _thisDispute.slashedAmount
+                    );
+                }
+                token.transfer(_thisVote.initiator, _thisVote.fee);
+            }
+        } else if (_thisVote.result == VoteResult.INVALID) {
+            // If vote is in dispute and is invalid, iterate through each vote round and transfer the dispute fee to initiator
+            for (
+                _i = voteRounds[_thisVote.identifierHash].length;
+                _i > 0;
+                _i--
+            ) {
+                _voteID = voteRounds[_thisVote.identifierHash][_i - 1];
+                _thisVote = voteInfo[_voteID];
+                token.transfer(_thisVote.initiator, _thisVote.fee);
+            }
+            // Transfer slashed tokens back to disputed reporter
+            token.transfer(
+                _thisDispute.disputedReporter,
+                _thisDispute.slashedAmount
+            );
+        } else if (_thisVote.result == VoteResult.FAILED) {
+            // If vote is in dispute and fails, iterate through each vote round and transfer the dispute fee to disputed reporter
+            uint256 _reporterReward = 0;
+            for (
+                _i = voteRounds[_thisVote.identifierHash].length;
+                _i > 0;
+                _i--
+            ) {
+                _voteID = voteRounds[_thisVote.identifierHash][_i - 1];
+                _thisVote = voteInfo[_voteID];
+                _reporterReward += _thisVote.fee;
+            }
+            _reporterReward += _thisDispute.slashedAmount;
+            token.transfer(_thisDispute.disputedReporter, _reporterReward);
+        }
+        emit VoteExecuted(_disputeId, voteInfo[_disputeId].result);
+    }
+
+    /**
+     * @dev Tallies the votes and begins the 1 day challenge period
+     * @param _disputeId is the dispute id
+     */
+    function tallyVotes(uint256 _disputeId) external {
+        // Ensure vote has not been executed and that vote has not been tallied
+        Vote storage _thisVote = voteInfo[_disputeId];
+        require(_thisVote.tallyDate == 0, "Vote has already been tallied");
+        require(_disputeId <= voteCount && _disputeId > 0, "Vote does not exist");
+        // Determine appropriate vote duration dispute round
+        // Vote time increases as rounds increase but only up to 6 days (withdrawal period)
+        require(
+            block.timestamp - _thisVote.startDate >=
+                86400 * _thisVote.voteRound ||
+                block.timestamp - _thisVote.startDate >= 86400 * 6,
+            "Time for voting has not elapsed"
+        );
+        // Get total votes from each separate stakeholder group.  This will allow
+        // normalization so each group's votes can be combined and compared to
+        // determine the vote outcome.
+        uint256 _tokenVoteSum = _thisVote.tokenholders.doesSupport +
+            _thisVote.tokenholders.against +
+            _thisVote.tokenholders.invalidQuery;
+        uint256 _reportersVoteSum = _thisVote.reporters.doesSupport +
+            _thisVote.reporters.against +
+            _thisVote.reporters.invalidQuery;
+        uint256 _multisigVoteSum = _thisVote.teamMultisig.doesSupport +
+            _thisVote.teamMultisig.against +
+            _thisVote.teamMultisig.invalidQuery;
+        uint256 _usersVoteSum = _thisVote.users.doesSupport +
+            _thisVote.users.against +
+            _thisVote.users.invalidQuery;
+        // Cannot divide by zero
+        if (_tokenVoteSum == 0) {
+            _tokenVoteSum++;
+        }
+        if (_reportersVoteSum == 0) {
+            _reportersVoteSum++;
+        }
+        if (_multisigVoteSum == 0) {
+            _multisigVoteSum++;
+        }
+        if (_usersVoteSum == 0) {
+            _usersVoteSum++;
+        }
+        // Normalize and combine each stakeholder group votes
+        uint256 _scaledDoesSupport = ((_thisVote.tokenholders.doesSupport *
+            1e18) / _tokenVoteSum) +
+            ((_thisVote.reporters.doesSupport * 1e18) / _reportersVoteSum) +
+            ((_thisVote.teamMultisig.doesSupport * 1e18) / _multisigVoteSum) +
+            ((_thisVote.users.doesSupport * 1e18) / _usersVoteSum);
+        uint256 _scaledAgainst = ((_thisVote.tokenholders.against * 1e18) /
+            _tokenVoteSum) +
+            ((_thisVote.reporters.against * 1e18) / _reportersVoteSum) +
+            ((_thisVote.teamMultisig.against * 1e18) / _multisigVoteSum) +
+            ((_thisVote.users.against * 1e18) / _usersVoteSum);
+        uint256 _scaledInvalid = ((_thisVote.tokenholders.invalidQuery * 1e18) /
+            _tokenVoteSum) +
+            ((_thisVote.reporters.invalidQuery * 1e18) / _reportersVoteSum) +
+            ((_thisVote.teamMultisig.invalidQuery * 1e18) / _multisigVoteSum) +
+            ((_thisVote.users.invalidQuery * 1e18) / _usersVoteSum);
+
+        // If votes in support outweight the sum of against and invalid, result is passed
+        if (_scaledDoesSupport > _scaledAgainst + _scaledInvalid) {
+            _thisVote.result = VoteResult.PASSED;
+        // If votes in against outweight the sum of support and invalid, result is failed
+        } else if (_scaledAgainst > _scaledDoesSupport + _scaledInvalid) {
+            _thisVote.result = VoteResult.FAILED;
+        // Otherwise, result is invalid
+        } else {
+            _thisVote.result = VoteResult.INVALID;
+        }
+
+        _thisVote.tallyDate = block.timestamp; // Update time vote was tallied
+        emit VoteTallied(
+            _disputeId,
+            _thisVote.result,
+            _thisVote.initiator,
+            disputeInfo[_disputeId].disputedReporter
+        );
+    }
+
+    /**
+     * @dev Enables the sender address to cast a vote
+     * @param _disputeId is the ID of the vote
+     * @param _supports is the address's vote: whether or not they support or are against
+     * @param _invalidQuery is whether or not the dispute is valid
+     */
+    function vote(
+        uint256 _disputeId,
+        bool _supports,
+        bool _invalidQuery
+    ) public {
+        // Ensure that dispute has not been executed and that vote does not exist and is not tallied
+        require(_disputeId <= voteCount && _disputeId > 0, "Vote does not exist");
+        Vote storage _thisVote = voteInfo[_disputeId];
+        require(_thisVote.tallyDate == 0, "Vote has already been tallied");
+        require(!_thisVote.voted[msg.sender], "Sender has already voted");
+        // Update voting status and increment total queries for support, invalid, or against based on vote
+        _thisVote.voted[msg.sender] = true;
+        uint256 _tokenBalance = token.balanceOf(msg.sender);
+        (, uint256 _stakedBalance, uint256 _lockedBalance, , , , , ) = oracle.getStakerInfo(msg.sender);
+        _tokenBalance += _stakedBalance + _lockedBalance;
+        if (_invalidQuery) {
+            _thisVote.tokenholders.invalidQuery += _tokenBalance;
+            _thisVote.reporters.invalidQuery += oracle
+                .getReportsSubmittedByAddress(msg.sender);
+            _thisVote.users.invalidQuery += _getUserTips(msg.sender);
+            if (msg.sender == teamMultisig) {
+                _thisVote.teamMultisig.invalidQuery += 1;
+            }
+        } else if (_supports) {
+            _thisVote.tokenholders.doesSupport += _tokenBalance;
+            _thisVote.reporters.doesSupport += oracle.getReportsSubmittedByAddress(msg.sender);
+            _thisVote.users.doesSupport += _getUserTips(msg.sender);
+            if (msg.sender == teamMultisig) {
+                _thisVote.teamMultisig.doesSupport += 1;
+            }
+        } else {
+            _thisVote.tokenholders.against += _tokenBalance;
+            _thisVote.reporters.against += oracle.getReportsSubmittedByAddress(
+                msg.sender
+            );
+            _thisVote.users.against += _getUserTips(msg.sender);
+            if (msg.sender == teamMultisig) {
+                _thisVote.teamMultisig.against += 1;
+            }
+        }
+        voteTallyByAddress[msg.sender]++;
+        emit Voted(_disputeId, _supports, msg.sender, _invalidQuery);
+    }
+
+    /**
+     * @dev Enables the sender address to cast votes for multiple disputes
+     * @param _disputeIds is an array of vote IDs
+     * @param _supports is an array of the address's votes: whether or not they support or are against
+     * @param _invalidQuery is array of whether or not the dispute is valid
+     */
+    function voteOnMultipleDisputes(
+        uint256[] memory _disputeIds,
+        bool[] memory _supports,
+        bool[] memory _invalidQuery
+    ) external {
+        for (uint256 _i = 0; _i < _disputeIds.length; _i++) {
+            vote(_disputeIds[_i], _supports[_i], _invalidQuery[_i]);
+        }
+    }
+
+    // *****************************************************************************
+    // *                                                                           *
+    // *                               Getters                                     *
+    // *                                                                           *
+    // *****************************************************************************
+
+    /**
+     * @dev Determines if an address voted for a specific vote
+     * @param _disputeId is the ID of the vote
+     * @param _voter is the address of the voter to check for
+     * @return bool of whether or note the address voted for the specific vote
+     */
+    function didVote(uint256 _disputeId, address _voter)
+        external
+        view
+        returns (bool)
+    {
+        return voteInfo[_disputeId].voted[_voter];
+    }
+
+    /**
+     * @dev Get the latest dispute fee
+     */
+    function getDisputeFee() public view returns (uint256) {
+        return (oracle.getStakeAmount() / 10);
+    }
+
+
+    function getDisputesByReporter(address _reporter) external view returns (uint256[] memory) {
+        return disputeIdsByReporter[_reporter];
+    }
+
+    /**
+     * @dev Returns info on a dispute for a given ID
+     * @param _disputeId is the ID of a specific dispute
+     * @return bytes32 of the data ID of the dispute
+     * @return uint256 of the timestamp of the dispute
+     * @return bytes memory of the value being disputed
+     * @return address of the reporter being disputed
+     */
+    function getDisputeInfo(uint256 _disputeId)
+        external
+        view
+        returns (
+            bytes32,
+            uint256,
+            bytes memory,
+            address
+        )
+    {
+        Dispute storage _d = disputeInfo[_disputeId];
+        return (_d.queryId, _d.timestamp, _d.value, _d.disputedReporter);
+    }
+
+    /**
+     * @dev Returns the number of open disputes for a specific query ID
+     * @param _queryId is the ID of a specific data feed
+     * @return uint256 of the number of open disputes for the query ID
+     */
+    function getOpenDisputesOnId(bytes32 _queryId)
+        external
+        view
+        returns (uint256)
+    {
+        return openDisputesOnId[_queryId];
+    }
+
+    /**
+     * @dev Returns the total number of votes
+     * @return uint256 of the total number of votes
+     */
+    function getVoteCount() external view returns (uint256) {
+        return voteCount;
+    }
+
+    /**
+     * @dev Returns info on a vote for a given vote ID
+     * @param _disputeId is the ID of a specific vote
+     * @return bytes32 identifier hash of the vote
+     * @return uint256[17] memory of the pertinent round info (vote rounds, start date, fee, etc.)
+     * @return bool memory of both whether or not the vote was executed
+     * @return VoteResult result of the vote
+     * @return address memory of the vote initiator
+     */
+    function getVoteInfo(uint256 _disputeId)
+        external
+        view
+        returns (
+            bytes32,
+            uint256[17] memory,
+            bool,
+            VoteResult,
+            address
+        )
+    {
+        Vote storage _v = voteInfo[_disputeId];
+        return (
+            _v.identifierHash,
+            [
+                _v.voteRound,
+                _v.startDate,
+                _v.blockNumber,
+                _v.fee,
+                _v.tallyDate,
+                _v.tokenholders.doesSupport,
+                _v.tokenholders.against,
+                _v.tokenholders.invalidQuery,
+                _v.users.doesSupport,
+                _v.users.against,
+                _v.users.invalidQuery,
+                _v.reporters.doesSupport,
+                _v.reporters.against,
+                _v.reporters.invalidQuery,
+                _v.teamMultisig.doesSupport,
+                _v.teamMultisig.against,
+                _v.teamMultisig.invalidQuery
+            ],
+            _v.executed,
+            _v.result,
+            _v.initiator
+        );
+    }
+
+    /**
+     * @dev Returns an array of voting rounds for a given vote
+     * @param _hash is the identifier hash for a vote
+     * @return uint256[] memory dispute IDs of the vote rounds
+     */
+    function getVoteRounds(bytes32 _hash)
+        external
+        view
+        returns (uint256[] memory)
+    {
+        return voteRounds[_hash];
+    }
+
+    /**
+     * @dev Returns the total number of votes cast by an address
+     * @param _voter is the address of the voter to check for
+     * @return uint256 of the total number of votes cast by the voter
+     */
+    function getVoteTallyByAddress(address _voter)
+        external
+        view
+        returns (uint256)
+    {
+        return voteTallyByAddress[_voter];
+    }
+
+    // Internal
+    /**
+     * @dev Retrieves total tips contributed to autopay by a given address
+     * @param _user address of the user to check the tip count for
+     * @return _userTipTally uint256 of total tips contributed to autopay by the address
+     */
+    function _getUserTips(address _user) internal returns (uint256 _userTipTally) {
+        // get autopay addresses array from oracle
+        (bytes memory _autopayAddrsBytes, uint256 _timestamp) = getDataBefore(
+            autopayAddrsQueryId,
+            block.timestamp - 12 hours
+        );
+        if (_timestamp > 0) {
+            address[] memory _autopayAddrs = abi.decode(
+                _autopayAddrsBytes,
+                (address[])
+            );
+            // iterate through autopay addresses retrieve tips by user address
+            for (uint256 _i = 0; _i < _autopayAddrs.length; _i++) {
+                (bool _success, bytes memory _returnData) = _autopayAddrs[_i]
+                    .call(
+                        abi.encodeWithSignature(
+                            "getTipsByAddress(address)",
+                            _user
+                        )
+                    );
+                if (_success) {
+                    _userTipTally += abi.decode(_returnData, (uint256));
+                }
+            }
+        }
+    }
+}
diff --git a/contracts/TellorFlex/QueryDataStorage.sol b/contracts/Tellor/QueryDataStorage.sol
similarity index 97%
rename from contracts/TellorFlex/QueryDataStorage.sol
rename to contracts/Tellor/QueryDataStorage.sol
index b6efe295..7ef68506 100644
--- a/contracts/TellorFlex/QueryDataStorage.sol
+++ b/contracts/Tellor/QueryDataStorage.sol
@@ -1,5 +1,5 @@
 // SPDX-License-Identifier: MIT
-pragma solidity 0.8.3;
+pragma solidity ^0.8.3;
 
 /**
  @author Tellor Inc.
diff --git a/contracts/TellorFlex/TellorFlex360.sol b/contracts/Tellor/TellorFlex.sol
similarity index 65%
rename from contracts/TellorFlex/TellorFlex360.sol
rename to contracts/Tellor/TellorFlex.sol
index 43ceee64..3537419e 100644
--- a/contracts/TellorFlex/TellorFlex360.sol
+++ b/contracts/Tellor/TellorFlex.sol
@@ -1,5 +1,5 @@
 // SPDX-License-Identifier: MIT
-pragma solidity 0.8.3;
+pragma solidity ^0.8.3;
 
 import "../../interfaces/IERC20.sol";
 
@@ -11,12 +11,13 @@ import "../../interfaces/IERC20.sol";
  * by a single address known as 'governance', which could be an externally owned
  * account or a contract, allowing for a flexible, modular design.
 */
-contract TellorFlex360 {
+contract TellorFlex {
     // Storage
     IERC20 public token; // token used for staking and rewards
     address public governance; // address with ability to remove values and slash reporters
     address public owner; // contract deployer, can call init function once
     uint256 public accumulatedRewardPerShare; // accumulated staking reward per staked token
+    uint256 public minimumStakeAmount; // minimum amount of tokens required to stake
     uint256 public reportingLock; // base amount of time before a reporter is able to submit a value again
     uint256 public rewardRate; // total staking rewards released per second
     uint256 public stakeAmount; // minimum amount required to be a staker
@@ -29,6 +30,7 @@ contract TellorFlex360 {
     uint256 public totalRewardDebt; // staking reward debt, used to calculate real staking rewards balance
     uint256 public totalStakeAmount; // total amount of tokens locked in contract (via stake)
     uint256 public totalStakers; // total number of stakers with at least stakeAmount staked, not exact
+    uint256 public toWithdraw; //amountLockedForWithdrawal
 
     mapping(bytes32 => Report) private reports; // mapping of query IDs to a report
     mapping(address => StakeInfo) private stakerDetails; // mapping from a persons address to their staking info
@@ -56,6 +58,26 @@ contract TellorFlex360 {
         mapping(bytes32 => uint256) reportsSubmittedByQueryId; // mapping of queryId to number of reports submitted by reporter
     }
 
+    // Events
+    event NewReport(
+        bytes32 indexed _queryId,
+        uint256 indexed _time,
+        bytes _value,
+        uint256 _nonce,
+        bytes _queryData,
+        address indexed _reporter
+    );
+    event NewStakeAmount(uint256 _newStakeAmount);
+    event NewStaker(address indexed _staker, uint256 indexed _amount);
+    event ReporterSlashed(
+        address indexed _reporter,
+        address _recipient,
+        uint256 _slashAmount
+    );
+    event StakeWithdrawn(address _staker);
+    event StakeWithdrawRequested(address _staker, uint256 _amount);
+    event ValueRemoved(bytes32 _queryId, uint256 _timestamp);
+
     // Functions
     /**
      * @dev Initializes system parameters
@@ -70,6 +92,7 @@ contract TellorFlex360 {
         uint256 _reportingLock,
         uint256 _stakeAmountDollarTarget,
         uint256 _stakingTokenPrice,
+        uint256 _minimumStakeAmount,
         bytes32 _stakingTokenPriceQueryId
     ) {
         require(_token != address(0), "must set token address");
@@ -80,7 +103,13 @@ contract TellorFlex360 {
         owner = msg.sender;
         reportingLock = _reportingLock;
         stakeAmountDollarTarget = _stakeAmountDollarTarget;
-        stakeAmount = (_stakeAmountDollarTarget * 1e18) / _stakingTokenPrice;
+        minimumStakeAmount = _minimumStakeAmount;
+        uint256 _potentialStakeAmount = (_stakeAmountDollarTarget * 1e18) / _stakingTokenPrice;
+        if(_potentialStakeAmount < _minimumStakeAmount) {
+            stakeAmount = _minimumStakeAmount;
+        } else {
+            stakeAmount = _potentialStakeAmount;
+        }
         stakingTokenPriceQueryId = _stakingTokenPriceQueryId;
     }
 
@@ -89,7 +118,7 @@ contract TellorFlex360 {
      * @param _governanceAddress address of governance contract (github.com/tellor-io/governance)
      */
     function init(address _governanceAddress) external {
-        // require(msg.sender == owner, "only owner can set governance address");
+        require(msg.sender == owner, "only owner can set governance address");
         require(governance == address(0), "governance address already set");
         require(
             _governanceAddress != address(0),
@@ -98,6 +127,23 @@ contract TellorFlex360 {
         governance = _governanceAddress;
     }
 
+    /**
+     * @dev Funds the Flex contract with staking rewards (paid by autopay and minting)
+     * @param _amount amount of tokens to fund contract with
+     */
+    function addStakingRewards(uint256 _amount) external {
+        require(token.transferFrom(msg.sender, address(this), _amount));
+        _updateRewards();
+        stakingRewardsBalance += _amount;
+        // update reward rate = real staking rewards balance / 30 days
+        rewardRate =
+            (stakingRewardsBalance -
+                ((accumulatedRewardPerShare * totalStakeAmount) /
+                    1e18 -
+                    totalRewardDebt)) /
+            30 days;
+    }
+
     /**
      * @dev Allows a reporter to submit stake
      * @param _amount amount of tokens to stake
@@ -111,6 +157,7 @@ contract TellorFlex360 {
             if (_lockedBalance >= _amount) {
                 // if staker's locked balance covers full _amount, use that
                 _staker.lockedBalance -= _amount;
+                toWithdraw -= _amount;
             } else {
                 // otherwise, stake the whole locked balance and transfer the
                 // remaining amount from the staker's address
@@ -121,12 +168,108 @@ contract TellorFlex360 {
                         _amount - _lockedBalance
                     )
                 );
+                toWithdraw -= _staker.lockedBalance;
                 _staker.lockedBalance = 0;
             }
+        } else {
+            if (_stakedBalance == 0) {
+                // if staked balance and locked balance equal 0, save current vote tally.
+                // voting participation used for calculating rewards
+                (bool _success, bytes memory _returnData) = governance.call(
+                    abi.encodeWithSignature("getVoteCount()")
+                );
+                if (_success) {
+                    _staker.startVoteCount = uint256(abi.decode(_returnData, (uint256)));
+                }
+                (_success,_returnData) = governance.call(
+                    abi.encodeWithSignature("getVoteTallyByAddress(address)",msg.sender)
+                );
+                if(_success){
+                    _staker.startVoteTally =  abi.decode(_returnData,(uint256));
+                }
+            }
             require(token.transferFrom(msg.sender, address(this), _amount));
         }
         _updateStakeAndPayRewards(msg.sender, _stakedBalance + _amount);
         _staker.startDate = block.timestamp; // This resets the staker start date to now
+        emit NewStaker(msg.sender, _amount);
+    }
+
+    /**
+     * @dev Removes a value from the oracle.
+     * Note: this function is only callable by the Governance contract.
+     * @param _queryId is ID of the specific data feed
+     * @param _timestamp is the timestamp of the data value to remove
+     */
+    function removeValue(bytes32 _queryId, uint256 _timestamp) external {
+        require(msg.sender == governance, "caller must be governance address");
+        Report storage _report = reports[_queryId];
+        require(!_report.isDisputed[_timestamp], "value already disputed");
+        uint256 _index = _report.timestampIndex[_timestamp];
+        require(_timestamp == _report.timestamps[_index], "invalid timestamp");
+        _report.valueByTimestamp[_timestamp] = "";
+        _report.isDisputed[_timestamp] = true;
+        emit ValueRemoved(_queryId, _timestamp);
+    }
+
+    /**
+     * @dev Allows a reporter to request to withdraw their stake
+     * @param _amount amount of staked tokens requesting to withdraw
+     */
+    function requestStakingWithdraw(uint256 _amount) external {
+        StakeInfo storage _staker = stakerDetails[msg.sender];
+        require(
+            _staker.stakedBalance >= _amount,
+            "insufficient staked balance"
+        );
+        _updateStakeAndPayRewards(msg.sender, _staker.stakedBalance - _amount);
+        _staker.startDate = block.timestamp;
+        _staker.lockedBalance += _amount;
+        toWithdraw += _amount;
+        emit StakeWithdrawRequested(msg.sender, _amount);
+    }
+
+    /**
+     * @dev Slashes a reporter and transfers their stake amount to the given recipient
+     * Note: this function is only callable by the governance address.
+     * @param _reporter is the address of the reporter being slashed
+     * @param _recipient is the address receiving the reporter's stake
+     * @return _slashAmount uint256 amount of token slashed and sent to recipient address
+     */
+    function slashReporter(address _reporter, address _recipient)
+        external
+        returns (uint256 _slashAmount)
+    {
+        require(msg.sender == governance, "only governance can slash reporter");
+        StakeInfo storage _staker = stakerDetails[_reporter];
+        uint256 _stakedBalance = _staker.stakedBalance;
+        uint256 _lockedBalance = _staker.lockedBalance;
+        require(_stakedBalance + _lockedBalance > 0, "zero staker balance");
+        if (_lockedBalance >= stakeAmount) {
+            // if locked balance is at least stakeAmount, slash from locked balance
+            _slashAmount = stakeAmount;
+            _staker.lockedBalance -= stakeAmount;
+            toWithdraw -= stakeAmount;
+        } else if (_lockedBalance + _stakedBalance >= stakeAmount) {
+            // if locked balance + staked balance is at least stakeAmount,
+            // slash from locked balance and slash remainder from staked balance
+            _slashAmount = stakeAmount;
+            _updateStakeAndPayRewards(
+                _reporter,
+                _stakedBalance - (stakeAmount - _lockedBalance)
+            );
+            toWithdraw -= _lockedBalance;
+            _staker.lockedBalance = 0;
+        } else {
+            // if sum(locked balance + staked balance) is less than stakeAmount,
+            // slash sum
+            _slashAmount = _stakedBalance + _lockedBalance;
+            toWithdraw -= _lockedBalance;
+            _updateStakeAndPayRewards(_reporter, 0);
+            _staker.lockedBalance = 0;
+        }
+        require(token.transfer(_recipient, _slashAmount));
+        emit ReporterSlashed(_reporter, _recipient, _slashAmount);
     }
 
     /**
@@ -160,7 +303,7 @@ contract TellorFlex360 {
             "still in reporter time lock, please wait!"
         );
         require(
-            _queryId == keccak256(_queryData) || uint256(_queryId) <= 100,
+            _queryId == keccak256(_queryData),
             "query id must be hash of query data"
         );
         _staker.reporterLastTimestamp = block.timestamp;
@@ -177,24 +320,112 @@ contract TellorFlex360 {
         _report.reporterByTimestamp[block.timestamp] = msg.sender;
         // Disperse Time Based Reward
         uint256 _reward = ((block.timestamp - timeOfLastNewValue) * timeBasedReward) / 300; //.5 TRB per 5 minutes
+        uint256 _totalTimeBasedRewardsBalance =
+            token.balanceOf(address(this)) -
+            (totalStakeAmount + stakingRewardsBalance + toWithdraw);
+        if (_totalTimeBasedRewardsBalance > 0 && _reward > 0) {
+            if (_totalTimeBasedRewardsBalance < _reward) {
+                token.transfer(msg.sender, _totalTimeBasedRewardsBalance);
+            } else {
+                token.transfer(msg.sender, _reward);
+            }
+        }
+        // Update last oracle value and number of values submitted by a reporter
         timeOfLastNewValue = block.timestamp;
         _staker.reportsSubmitted++;
         _staker.reportsSubmittedByQueryId[_queryId]++;
+        emit NewReport(
+            _queryId,
+            block.timestamp,
+            _value,
+            _nonce,
+            _queryData,
+            msg.sender
+        );
     }
 
     /**
      * @dev Updates the stake amount after retrieving the latest
      * 12+-hour-old staking token price from the oracle
      */
-    function updateStakeAmount(uint256 _amount) external{
-            stakeAmount = _amount;
+    function updateStakeAmount() external {
+        // get staking token price
+        (bool _valFound, bytes memory _val, ) = getDataBefore(
+            stakingTokenPriceQueryId,
+            block.timestamp - 12 hours
+        );
+        if (_valFound) {
+            uint256 _stakingTokenPrice = abi.decode(_val, (uint256));
+            require(
+                _stakingTokenPrice >= 0.01 ether && _stakingTokenPrice < 1000000 ether,
+                "invalid staking token price"
+            );
+
+            uint256 _adjustedStakeAmount = (stakeAmountDollarTarget * 1e18) / _stakingTokenPrice;
+            if(_adjustedStakeAmount < minimumStakeAmount) {
+                stakeAmount = minimumStakeAmount;
+            } else {
+                stakeAmount = _adjustedStakeAmount;
+            }
+            emit NewStakeAmount(stakeAmount);
         }
+    }
+
+    /**
+     * @dev Withdraws a reporter's stake after the lock period expires
+     */
+    function withdrawStake() external {
+        StakeInfo storage _staker = stakerDetails[msg.sender];
+        // Ensure reporter is locked and that enough time has passed
+        require(
+            block.timestamp - _staker.startDate >= 7 days,
+            "7 days didn't pass"
+        );
+        require(
+            _staker.lockedBalance > 0,
+            "reporter not locked for withdrawal"
+        );
+        require(token.transfer(msg.sender, _staker.lockedBalance));
+        toWithdraw -= _staker.lockedBalance;
+        _staker.lockedBalance = 0;
+        emit StakeWithdrawn(msg.sender);
+    }
+
     // *****************************************************************************
     // *                                                                           *
     // *                               Getters                                     *
     // *                                                                           *
     // *****************************************************************************
 
+    /**
+     * @dev Returns the block number at a given timestamp
+     * @param _queryId is ID of the specific data feed
+     * @param _timestamp is the timestamp to find the corresponding block number for
+     * @return uint256 block number of the timestamp for the given data ID
+     */
+    function getBlockNumberByTimestamp(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (uint256)
+    {
+        return reports[_queryId].timestampToBlockNum[_timestamp];
+    }
+
+    /**
+     * @dev Returns the current value of a data feed given a specific ID
+     * @param _queryId is the ID of the specific data feed
+     * @return _value the latest submitted value for the given queryId
+     */
+    function getCurrentValue(bytes32 _queryId)
+        external
+        view
+        returns (bytes memory _value)
+    {
+        bool _didGet;
+        (_didGet, _value, ) = getDataBefore(_queryId, block.timestamp + 1);
+        if(!_didGet){revert();}
+    }
+
     /**
      * @dev Retrieves the latest value for the queryId before the specified timestamp
      * @param _queryId is the queryId to look up the value for
@@ -222,6 +453,14 @@ contract TellorFlex360 {
         return (true, _value, _timestampRetrieved);
     }
 
+    /**
+     * @dev Returns governance address
+     * @return address governance
+     */
+    function getGovernanceAddress() external view returns (address) {
+        return governance;
+    }
+
     /**
      * @dev Counts the number of values that have been submitted for the request.
      * @param _queryId the id to look up
@@ -235,6 +474,39 @@ contract TellorFlex360 {
         return reports[_queryId].timestamps.length;
     }
 
+    /**
+     * @dev Returns the pending staking reward for a given address
+     * @param _stakerAddress staker address to look up
+     * @return _pendingReward - pending reward for given staker
+     */
+    function getPendingRewardByStaker(address _stakerAddress)
+        external
+        returns (uint256 _pendingReward)
+    {
+        StakeInfo storage _staker = stakerDetails[_stakerAddress];
+        _pendingReward = (_staker.stakedBalance *
+            _getUpdatedAccumulatedRewardPerShare()) /
+            1e18 -
+            _staker.rewardDebt;
+        (bool _success, bytes memory _returnData) = governance.call(
+            abi.encodeWithSignature("getVoteCount()")
+        );
+        uint256 _numberOfVotes;
+        if (_success) {
+                _numberOfVotes = uint256(abi.decode(_returnData, (uint256))) - _staker.startVoteCount;
+        }
+        if (_numberOfVotes > 0) {
+                (_success,_returnData) = governance.call(
+                    abi.encodeWithSignature("getVoteTallyByAddress(address)",_stakerAddress)
+                );
+                if(_success){
+                    _pendingReward =
+                        (_pendingReward * (abi.decode(_returnData,(uint256)) - _staker.startVoteTally))
+                        / _numberOfVotes;
+                }
+        }
+    }
+
     /**
      * @dev Returns the real staking rewards balance after accounting for unclaimed rewards
      * @return uint256 real staking rewards balance
@@ -493,12 +765,50 @@ contract TellorFlex360 {
         return (false, 0);
     }
 
+    /**
+     * @dev Returns the index of a reporter timestamp in the timestamp array for a specific data ID
+     * @param _queryId is ID of the specific data feed
+     * @param _timestamp is the timestamp to find in the timestamps array
+     * @return uint256 of the index of the reporter timestamp in the array for specific ID
+     */
+    function getTimestampIndexByTimestamp(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (uint256)
+    {
+        return reports[_queryId].timestampIndex[_timestamp];
+    }
+
+    /**
+     * @dev Returns the address of the token used for staking
+     * @return address of the token used for staking
+     */
+    function getTokenAddress() external view returns (address) {
+        return address(token);
+    }
+
+    /**
+     * @dev Returns total amount of token staked for reporting
+     * @return uint256 total amount of token staked
+     */
+    function getTotalStakeAmount() external view returns (uint256) {
+        return totalStakeAmount;
+    }
+
+    /**
+     * @dev Returns total number of current stakers. Reporters with stakedBalance less than stakeAmount are excluded from this total
+     * @return uint256 total stakers
+     */
+    function getTotalStakers() external view returns (uint256) {
+        return totalStakers;
+    }
+
     /**
      * @dev Returns total balance of time based rewards in contract
      * @return uint256 amount of trb
      */
     function getTotalTimeBasedRewardsBalance() external view returns (uint256) {
-        return token.balanceOf(address(this)) - (totalStakeAmount + stakingRewardsBalance);
+        return token.balanceOf(address(this)) - (totalStakeAmount + stakingRewardsBalance + toWithdraw);
     }
 
     /**
@@ -529,6 +839,14 @@ contract TellorFlex360 {
         return reports[_queryId].valueByTimestamp[_timestamp];
     }
 
+    /**
+     * @dev Used during the upgrade process to verify valid Tellor contracts
+     * @return bool value used to verify valid Tellor contracts
+     */
+    function verify() external pure returns (uint256) {
+        return 9999;
+    }
+
     // *****************************************************************************
     // *                                                                           *
     // *                          Internal functions                               *
@@ -591,6 +909,32 @@ contract TellorFlex360 {
                 accumulatedRewardPerShare) /
                 1e18 -
                 _staker.rewardDebt;
+            // get staker voting participation rate
+            uint256 _numberOfVotes;
+            (bool _success, bytes memory _returnData) = governance.call(
+                abi.encodeWithSignature("getVoteCount()")
+            );
+            if (_success) {
+                _numberOfVotes =
+                    uint256(abi.decode(_returnData, (uint256))) -
+                    _staker.startVoteCount;
+            }
+            if (_numberOfVotes > 0) {
+                // staking reward = pending reward * voting participation rate
+                (_success, _returnData) = governance.call(
+                    abi.encodeWithSignature("getVoteTallyByAddress(address)",_stakerAddress)
+                );
+                if(_success){
+                    uint256 _voteTally = abi.decode(_returnData,(uint256));
+                    uint256 _tempPendingReward =
+                        (_pendingReward *
+                            (_voteTally - _staker.startVoteTally)) /
+                        _numberOfVotes;
+                    if (_tempPendingReward < _pendingReward) {
+                        _pendingReward = _tempPendingReward;
+                    }
+                }
+            }
             stakingRewardsBalance -= _pendingReward;
             require(token.transfer(msg.sender, _pendingReward));
             totalRewardDebt -= _staker.rewardDebt;
@@ -615,6 +959,16 @@ contract TellorFlex360 {
             1e18;
         totalRewardDebt += _staker.rewardDebt;
         totalStakeAmount += _staker.stakedBalance;
+        // update reward rate if staking rewards are available
+        // given staker's updated parameters
+        if(rewardRate == 0) {
+            rewardRate =
+            (stakingRewardsBalance -
+                ((accumulatedRewardPerShare * totalStakeAmount) /
+                    1e18 -
+                    totalRewardDebt)) /
+            30 days;
+        }
     }
 
     /**
diff --git a/contracts/TellorFlex/TellorPlayground.sol b/contracts/Tellor/TellorPlayground.sol
similarity index 63%
rename from contracts/TellorFlex/TellorPlayground.sol
rename to contracts/Tellor/TellorPlayground.sol
index 5c690bbb..345e4b96 100644
--- a/contracts/TellorFlex/TellorPlayground.sol
+++ b/contracts/Tellor/TellorPlayground.sol
@@ -1,5 +1,5 @@
 // SPDX-License-Identifier: MIT
-pragma solidity 0.8.3;
+pragma solidity ^0.8.3;
 
 
 contract TellorPlayground {
@@ -18,19 +18,11 @@ contract TellorPlayground {
         address _reporter
     );
     event NewStaker(address _staker, uint256 _amount);
-    event TipAdded(
-        address indexed _user,
-        bytes32 indexed _queryId,
-        uint256 _tip,
-        uint256 _totalTip,
-        bytes _queryData
-    );
     event StakeWithdrawRequested(address _staker, uint256 _amount);
     event StakeWithdrawn(address _staker);
     event Transfer(address indexed from, address indexed to, uint256 value);
 
     // Storage
-    mapping(bytes32 => address) public addresses;
     mapping(bytes32 => mapping(uint256 => bool)) public isDisputed; //queryId -> timestamp -> value
     mapping(bytes32 => mapping(uint256 => address)) public reporterByTimestamp;
     mapping(address => StakeInfo) stakerDetails; //mapping from a persons address to their staking info
@@ -41,9 +33,11 @@ contract TellorPlayground {
     mapping(address => mapping(address => uint256)) private _allowances;
     mapping(address => uint256) private _balances;
 
+    uint256 public stakeAmount;
     uint256 public constant timeBasedReward = 5e17; // time based reward for a reporter for successfully submitting a value
     uint256 public tipsInContract; // number of tips within the contract
     uint256 public voteCount;
+    address public token;
     uint256 private _totalSupply;
     string private _name;
     string private _symbol;
@@ -66,14 +60,12 @@ contract TellorPlayground {
         _name = "TellorPlayground";
         _symbol = "TRBP";
         _decimals = 18;
-        addresses[
-            keccak256(abi.encodePacked("_GOVERNANCE_CONTRACT"))
-        ] = address(this);
+        token = address(this);
     }
 
     /**
-     * @dev Mock function for adding staking rewards
-     * @param _amount quantity of tokens to transfer to this contract
+     * @dev Mock function for adding staking rewards. No rewards actually given to stakers
+     * @param _amount Amount of TRB to be added to the contract
      */
     function addStakingRewards(uint256 _amount) external {
         require(_transferFrom(msg.sender, address(this), _amount));
@@ -86,11 +78,7 @@ contract TellorPlayground {
      * @return bool Whether the transaction succeeded
      *
      */
-    function approve(address _spender, uint256 _amount)
-        public
-        virtual
-        returns (bool)
-    {
+    function approve(address _spender, uint256 _amount) external returns (bool){
         _approve(msg.sender, _spender, _amount);
         return true;
     }
@@ -109,12 +97,55 @@ contract TellorPlayground {
         );
     }
 
+    /**
+     * @dev Allows a reporter to submit stake
+     * @param _amount amount of tokens to stake
+     */
+    function depositStake(uint256 _amount) external {
+        StakeInfo storage _staker = stakerDetails[msg.sender];
+        if (_staker.lockedBalance > 0) {
+            if (_staker.lockedBalance >= _amount) {
+                _staker.lockedBalance -= _amount;
+            } else {
+                require(
+                    _transferFrom(
+                        msg.sender,
+                        address(this),
+                        _amount - _staker.lockedBalance
+                    )
+                );
+                _staker.lockedBalance = 0;
+            }
+        } else {
+            require(_transferFrom(msg.sender, address(this), _amount));
+        }
+        _staker.startDate = block.timestamp; // This resets their stake start date to now
+        _staker.stakedBalance += _amount;
+        emit NewStaker(msg.sender, _amount);
+    }
+
     /**
      * @dev Public function to mint tokens to the given address
      * @param _user The address which will receive the tokens
      */
     function faucet(address _user) external {
-        _mint(_user, 1000 ether);
+        _mint(_user, 1000000 ether);
+    }
+
+    /**
+     * @dev Allows a reporter to request to withdraw their stake
+     * @param _amount amount of staked tokens requesting to withdraw
+     */
+    function requestStakingWithdraw(uint256 _amount) external {
+        StakeInfo storage _staker = stakerDetails[msg.sender];
+        require(
+            _staker.stakedBalance >= _amount,
+            "insufficient staked balance"
+        );
+        _staker.startDate = block.timestamp;
+        _staker.lockedBalance += _amount;
+        _staker.stakedBalance -= _amount;
+        emit StakeWithdrawRequested(msg.sender, _amount);
     }
 
     /**
@@ -131,6 +162,7 @@ contract TellorPlayground {
         uint256 _nonce,
         bytes memory _queryData
     ) external {
+        require(keccak256(_value) != keccak256(""), "value must be submitted");
         require(
             _nonce == timestamps[_queryId].length || _nonce == 0,
             "nonce must match timestamp index"
@@ -154,35 +186,6 @@ contract TellorPlayground {
         );
     }
 
-    /**
-     * @dev Adds a tip to a given query ID.
-     * @param _queryId is the queryId to look up
-     * @param _amount is the amount of tips
-     * @param _queryData is the extra bytes data needed to fulfill the request
-     */
-    function tipQuery(
-        bytes32 _queryId,
-        uint256 _amount,
-        bytes memory _queryData
-    ) external {
-        require(
-            _queryId == keccak256(_queryData) || uint256(_queryId) <= 100,
-            "id must be hash of bytes data"
-        );
-        _transfer(msg.sender, address(this), _amount);
-        _amount = _amount / 2;
-        _burn(address(this), _amount);
-        tipsInContract += _amount;
-        tips[_queryId] += _amount;
-        emit TipAdded(
-            msg.sender,
-            _queryId,
-            _amount,
-            tips[_queryId],
-            _queryData
-        );
-    }
-
     /**
      * @dev Transfer tokens from one user to another
      * @param _recipient The destination address
@@ -191,7 +194,6 @@ contract TellorPlayground {
      */
     function transfer(address _recipient, uint256 _amount)
         public
-        virtual
         returns (bool)
     {
         _transfer(msg.sender, _recipient, _amount);
@@ -209,7 +211,7 @@ contract TellorPlayground {
         address _sender,
         address _recipient,
         uint256 _amount
-    ) public virtual returns (bool) {
+    ) public returns (bool) {
         _transfer(_sender, _recipient, _amount);
         _approve(
             _sender,
@@ -219,50 +221,6 @@ contract TellorPlayground {
         return true;
     }
 
-    // Tellor Flex
-    /**
-     * @dev Allows a reporter to submit stake
-     * @param _amount amount of tokens to stake
-     */
-    function depositStake(uint256 _amount) external {
-        StakeInfo storage _staker = stakerDetails[msg.sender];
-        if (_staker.lockedBalance > 0) {
-            if (_staker.lockedBalance >= _amount) {
-                _staker.lockedBalance -= _amount;
-            } else {
-                require(
-                    _transferFrom(
-                        msg.sender,
-                        address(this),
-                        _amount - _staker.lockedBalance
-                    )
-                );
-                _staker.lockedBalance = 0;
-            }
-        } else {
-            require(_transferFrom(msg.sender, address(this), _amount));
-        }
-        _staker.startDate = block.timestamp; // This resets their stake start date to now
-        _staker.stakedBalance += _amount;
-        emit NewStaker(msg.sender, _amount);
-    }
-
-    /**
-     * @dev Allows a reporter to request to withdraw their stake
-     * @param _amount amount of staked tokens requesting to withdraw
-     */
-    function requestStakingWithdraw(uint256 _amount) external {
-        StakeInfo storage _staker = stakerDetails[msg.sender];
-        require(
-            _staker.stakedBalance >= _amount,
-            "insufficient staked balance"
-        );
-        _staker.startDate = block.timestamp;
-        _staker.lockedBalance += _amount;
-        _staker.stakedBalance -= _amount;
-        emit StakeWithdrawRequested(msg.sender, _amount);
-    }
-
     /**
      * @dev Withdraws a reporter's stake
      */
@@ -276,49 +234,6 @@ contract TellorPlayground {
         emit StakeWithdrawn(msg.sender);
     }
 
-    /**
-     * @dev Returns the reporter for a given timestamp and queryId
-     * @param _queryId bytes32 version of the queryId
-     * @param _timestamp uint256 timestamp of report
-     * @return address of data reporter
-     */
-    function getReporterByTimestamp(bytes32 _queryId, uint256 _timestamp)
-        external
-        view
-        returns (address)
-    {
-        return reporterByTimestamp[_queryId][_timestamp];
-    }
-
-    /**
-     * @dev Allows users to retrieve all information about a staker
-     * @param _staker address of staker inquiring about
-     * @return uint startDate of staking
-     * @return uint current amount staked
-     * @return uint current amount locked for withdrawal
-     * @return uint reporter's last reported timestamp
-     * @return uint total number of reports submitted by reporter
-     */
-    function getStakerInfo(address _staker)
-        external
-        view
-        returns (
-            uint256,
-            uint256,
-            uint256,
-            uint256,
-            uint256
-        )
-    {
-        return (
-            stakerDetails[_staker].startDate,
-            stakerDetails[_staker].stakedBalance,
-            stakerDetails[_staker].lockedBalance,
-            stakerDetails[_staker].reporterLastTimestamp,
-            stakerDetails[_staker].reportsSubmitted
-        );
-    }
-
     // Getters
     /**
      * @dev Returns the amount that an address is alowed to spend of behalf of another
@@ -326,12 +241,7 @@ contract TellorPlayground {
      * @param _spender The address that will use the tokens
      * @return uint256 The amount of allowed tokens
      */
-    function allowance(address _owner, address _spender)
-        public
-        view
-        virtual
-        returns (uint256)
-    {
+    function allowance(address _owner, address _spender) external view returns (uint256){
         return _allowances[_owner][_spender];
     }
 
@@ -340,7 +250,7 @@ contract TellorPlayground {
      * @param _account user address
      * @return uint256 user's token balance
      */
-    function balanceOf(address _account) public view returns (uint256) {
+    function balanceOf(address _account) external view returns (uint256) {
         return _balances[_account];
     }
 
@@ -348,10 +258,144 @@ contract TellorPlayground {
      * @dev Returns the number of decimals used to get its user representation.
      * @return uint8 the number of decimals; used only for display purposes
      */
-    function decimals() public view returns (uint8) {
+    function decimals() external view returns (uint8) {
         return _decimals;
     }
 
+    /**
+     * @dev Retrieves the latest value for the queryId before the specified timestamp
+     * @param _queryId is the queryId to look up the value for
+     * @param _timestamp before which to search for latest value
+     * @return _ifRetrieve bool true if able to retrieve a non-zero value
+     * @return _value the value retrieved
+     * @return _timestampRetrieved the value's timestamp
+     */
+    function getDataBefore(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (
+            bool _ifRetrieve,
+            bytes memory _value,
+            uint256 _timestampRetrieved
+        )
+    {
+        (bool _found, uint256 _index) = getIndexForDataBefore(
+            _queryId,
+            _timestamp
+        );
+        if (!_found) return (false, bytes(""), 0);
+        _timestampRetrieved = getTimestampbyQueryIdandIndex(_queryId, _index);
+        _value = values[_queryId][_timestampRetrieved];
+        return (true, _value, _timestampRetrieved);
+    }
+
+    /**
+     * @dev Retrieves latest array index of data before the specified timestamp for the queryId
+     * @param _queryId is the queryId to look up the index for
+     * @param _timestamp is the timestamp before which to search for the latest index
+     * @return _found whether the index was found
+     * @return _index the latest index found before the specified timestamp
+     */
+    // slither-disable-next-line calls-loop
+    function getIndexForDataBefore(bytes32 _queryId, uint256 _timestamp)
+        public
+        view
+        returns (bool _found, uint256 _index)
+    {
+        uint256 _count = getNewValueCountbyQueryId(_queryId);
+        if (_count > 0) {
+            uint256 _middle;
+            uint256 _start = 0;
+            uint256 _end = _count - 1;
+            uint256 _time;
+            //Checking Boundaries to short-circuit the algorithm
+            _time = getTimestampbyQueryIdandIndex(_queryId, _start);
+            if (_time >= _timestamp) return (false, 0);
+            _time = getTimestampbyQueryIdandIndex(_queryId, _end);
+            if (_time < _timestamp) {
+                while (isInDispute(_queryId, _time) && _end > 0) {
+                    _end--;
+                    _time = getTimestampbyQueryIdandIndex(_queryId, _end);
+                }
+                if (_end == 0 && isInDispute(_queryId, _time)) {
+                    return (false, 0);
+                }
+                return (true, _end);
+            }
+            //Since the value is within our boundaries, do a binary search
+            while (true) {
+                _middle = (_end - _start) / 2 + 1 + _start;
+                _time = getTimestampbyQueryIdandIndex(_queryId, _middle);
+                if (_time < _timestamp) {
+                    //get immediate next value
+                    uint256 _nextTime = getTimestampbyQueryIdandIndex(
+                        _queryId,
+                        _middle + 1
+                    );
+                    if (_nextTime >= _timestamp) {
+                        if (!isInDispute(_queryId, _time)) {
+                            // _time is correct
+                            return (true, _middle);
+                        } else {
+                            // iterate backwards until we find a non-disputed value
+                            while (
+                                isInDispute(_queryId, _time) && _middle > 0
+                            ) {
+                                _middle--;
+                                _time = getTimestampbyQueryIdandIndex(
+                                    _queryId,
+                                    _middle
+                                );
+                            }
+                            if (_middle == 0 && isInDispute(_queryId, _time)) {
+                                return (false, 0);
+                            }
+                            // _time is correct
+                            return (true, _middle);
+                        }
+                    } else {
+                        //look from middle + 1(next value) to end
+                        _start = _middle + 1;
+                    }
+                } else {
+                    uint256 _prevTime = getTimestampbyQueryIdandIndex(
+                        _queryId,
+                        _middle - 1
+                    );
+                    if (_prevTime < _timestamp) {
+                        if (!isInDispute(_queryId, _prevTime)) {
+                            // _prevTime is correct
+                            return (true, _middle - 1);
+                        } else {
+                            // iterate backwards until we find a non-disputed value
+                            _middle--;
+                            while (
+                                isInDispute(_queryId, _prevTime) && _middle > 0
+                            ) {
+                                _middle--;
+                                _prevTime = getTimestampbyQueryIdandIndex(
+                                    _queryId,
+                                    _middle
+                                );
+                            }
+                            if (
+                                _middle == 0 && isInDispute(_queryId, _prevTime)
+                            ) {
+                                return (false, 0);
+                            }
+                            // _prevtime is correct
+                            return (true, _middle);
+                        }
+                    } else {
+                        //look from start to middle -1(prev value)
+                        _end = _middle - 1;
+                    }
+                }
+            }
+        }
+        return (false, 0);
+    }
+
     /**
      * @dev Counts the number of values that have been submitted for a given ID
      * @param _queryId the ID to look up
@@ -365,6 +409,70 @@ contract TellorPlayground {
         return timestamps[_queryId].length;
     }
 
+    /**
+     * @dev Returns the reporter for a given timestamp and queryId
+     * @param _queryId bytes32 version of the queryId
+     * @param _timestamp uint256 timestamp of report
+     * @return address of data reporter
+     */
+    function getReporterByTimestamp(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (address)
+    {
+        return reporterByTimestamp[_queryId][_timestamp];
+    }
+
+    /**
+     * @dev Returns mock stake amount
+     * @return uint256 stake amount
+     */
+    function getStakeAmount() external view returns (uint256) {
+        return stakeAmount;
+    }
+
+    /**
+     * @dev Allows users to retrieve all information about a staker
+     * @param _stakerAddress address of staker inquiring about
+     * @return uint startDate of staking
+     * @return uint current amount staked
+     * @return uint current amount locked for withdrawal
+     * @return uint reward debt used to calculate staking reward
+     * @return uint reporter's last reported timestamp
+     * @return uint total number of reports submitted by reporter
+     * @return uint governance vote count when first staked
+     * @return uint number of votes case by staker when first staked
+     * @return uint whether staker is counted in totalStakers
+     */
+    function getStakerInfo(address _stakerAddress)
+        external
+        view
+        returns (
+            uint256,
+            uint256,
+            uint256,
+            uint256,
+            uint256,
+            uint256,
+            uint256,
+            uint256,
+            bool
+        )
+    {
+        StakeInfo storage _staker = stakerDetails[_stakerAddress];
+        return (
+            _staker.startDate,
+            _staker.stakedBalance,
+            _staker.lockedBalance,
+            0, // reward debt
+            _staker.reporterLastTimestamp,
+            _staker.reportsSubmitted,
+            0, // start vote count
+            0, // start vote tally
+            false
+        );
+    }
+
     /**
      * @dev Gets the timestamp for the value based on their index
      * @param _queryId is the queryId to look up
@@ -376,8 +484,8 @@ contract TellorPlayground {
         view
         returns (uint256)
     {
-        uint256 len = timestamps[_queryId].length;
-        if (len == 0 || len <= _index) return 0;
+        uint256 _len = timestamps[_queryId].length;
+        if (_len == 0 || _len <= _index) return 0;
         return timestamps[_queryId][_index];
     }
 
@@ -386,11 +494,7 @@ contract TellorPlayground {
      * @param _hash is the identifier hash for a vote
      * @return uint256[] memory dispute IDs of the vote rounds
      */
-    function getVoteRounds(bytes32 _hash)
-        public
-        view
-        returns (uint256[] memory)
-    {
+    function getVoteRounds(bytes32 _hash) public view returns (uint256[] memory){
         return voteRounds[_hash];
     }
 
@@ -398,15 +502,29 @@ contract TellorPlayground {
      * @dev Returns the governance address of the contract
      * @return address (this address)
      */
-    function governance() external view returns(address){
+    function governance() external view returns (address) {
         return address(this);
     }
 
+    /**
+     * @dev Returns whether a given value is disputed
+     * @param _queryId unique ID of the data feed
+     * @param _timestamp timestamp of the value
+     * @return bool whether the value is disputed
+     */
+    function isInDispute(bytes32 _queryId, uint256 _timestamp)
+        public
+        view
+        returns (bool)
+    {
+        return isDisputed[_queryId][_timestamp];
+    }
+
     /**
      * @dev Returns the name of the token.
      * @return string name of the token
      */
-    function name() public view returns (string memory) {
+    function name() external view returns (string memory) {
         return _name;
     }
 
@@ -417,7 +535,7 @@ contract TellorPlayground {
      * @return bytes value for queryId/timestamp submitted
      */
     function retrieveData(bytes32 _queryId, uint256 _timestamp)
-        public
+        external
         view
         returns (bytes memory)
     {
@@ -428,7 +546,7 @@ contract TellorPlayground {
      * @dev Returns the symbol of the token.
      * @return string symbol of the token
      */
-    function symbol() public view returns (string memory) {
+    function symbol() external view returns (string memory) {
         return _symbol;
     }
 
@@ -436,7 +554,7 @@ contract TellorPlayground {
      * @dev Returns the total supply of the token.
      * @return uint256 total supply of token
      */
-    function totalSupply() public view returns (uint256) {
+    function totalSupply() external view returns (uint256) {
         return _totalSupply;
     }
 
@@ -451,7 +569,7 @@ contract TellorPlayground {
         address _owner,
         address _spender,
         uint256 _amount
-    ) internal virtual {
+    ) internal {
         require(_owner != address(0), "ERC20: approve from the zero address");
         require(_spender != address(0), "ERC20: approve to the zero address");
         _allowances[_owner][_spender] = _amount;
@@ -463,7 +581,7 @@ contract TellorPlayground {
      * @param _account The address whose tokens to burn
      * @param _amount The quantity of tokens to burn
      */
-    function _burn(address _account, uint256 _amount) internal virtual {
+    function _burn(address _account, uint256 _amount) internal{
         require(_account != address(0), "ERC20: burn from the zero address");
         _balances[_account] -= _amount;
         _totalSupply -= _amount;
@@ -475,7 +593,7 @@ contract TellorPlayground {
      * @param _account The address which receives minted tokens
      * @param _amount The quantity of tokens to min
      */
-    function _mint(address _account, uint256 _amount) internal virtual {
+    function _mint(address _account, uint256 _amount) internal{
         require(_account != address(0), "ERC20: mint to the zero address");
         _totalSupply += _amount;
         _balances[_account] += _amount;
@@ -492,12 +610,9 @@ contract TellorPlayground {
         address _sender,
         address _recipient,
         uint256 _amount
-    ) internal virtual {
+    ) internal{
         require(_sender != address(0), "ERC20: transfer from the zero address");
-        require(
-            _recipient != address(0),
-            "ERC20: transfer to the zero address"
-        );
+        require( _recipient != address(0),"ERC20: transfer to the zero address");
         _balances[_sender] -= _amount;
         _balances[_recipient] += _amount;
         emit Transfer(_sender, _recipient, _amount);
@@ -514,7 +629,7 @@ contract TellorPlayground {
         address _sender,
         address _recipient,
         uint256 _amount
-    ) internal virtual returns (bool) {
+    ) internal returns (bool) {
         _transfer(_sender, _recipient, _amount);
         _approve(
             _sender,
diff --git a/contracts/TellorFlex/UsingTellor.sol b/contracts/Tellor/UsingTellor.sol
similarity index 51%
rename from contracts/TellorFlex/UsingTellor.sol
rename to contracts/Tellor/UsingTellor.sol
index dcf8f0d9..c67c3d46 100644
--- a/contracts/TellorFlex/UsingTellor.sol
+++ b/contracts/Tellor/UsingTellor.sol
@@ -1,20 +1,23 @@
 // SPDX-License-Identifier: MIT
-pragma solidity >=0.8.0;
+pragma solidity ^0.8.0;
 
 import "../../interfaces/ITellor.sol";
+import "../../interfaces/IERC2362.sol";
+import "../../interfaces/IMappingContract.sol";
 
 /**
- * @title UserContract
- * This contract allows for easy integration with the Tellor System
- * by helping smart contracts to read data from Tellor
+ @author Tellor Inc
+ @title UsingTellor
+ @dev This contract helps smart contracts read data from Tellor
  */
-contract UsingTellor {
+contract UsingTellor is IERC2362 {
     ITellor public tellor;
+    IMappingContract public idMappingContract;
 
     /*Constructor*/
     /**
-     * @dev the constructor sets the tellor address in storage
-     * @param _tellor is the TellorMaster address
+     * @dev the constructor sets the oracle address in storage
+     * @param _tellor is the Tellor Oracle address
      */
     constructor(address payable _tellor) {
         tellor = ITellor(_tellor);
@@ -55,62 +58,103 @@ contract UsingTellor {
     function getDataBefore(bytes32 _queryId, uint256 _timestamp)
         public
         view
-        returns (
-            bytes memory _value,
-            uint256 _timestampRetrieved
-        )
+        returns (bytes memory _value, uint256 _timestampRetrieved)
     {
-        (bool _found, uint256 _index) = getIndexForDataBefore(
+        (, _value, _timestampRetrieved) = tellor.getDataBefore(
             _queryId,
             _timestamp
         );
-        if (!_found) return (bytes(""), 0);
-        uint256 _time = tellor.getTimestampbyQueryIdandIndex(_queryId, _index);
-        _value = tellor.retrieveData(_queryId, _time);
-        if (keccak256(_value) != keccak256(bytes("")))
-            return (_value, _time);
-        return (bytes(""), 0);
     }
 
     /**
-     * @dev Retrieves next array index of data after the specified timestamp for the queryId
+     * @dev Retrieves latest array index of data before the specified timestamp for the queryId
      * @param _queryId is the queryId to look up the index for
-     * @param _timestamp is the timestamp after which to search for the next index
+     * @param _timestamp is the timestamp before which to search for the latest index
      * @return _found whether the index was found
-     * @return _index the next index found after the specified timestamp
+     * @return _index the latest index found before the specified timestamp
      */
+    // slither-disable-next-line calls-loop
     function getIndexForDataAfter(bytes32 _queryId, uint256 _timestamp)
         public
         view
         returns (bool _found, uint256 _index)
     {
-        (_found, _index) = tellor.getIndexForDataBefore(_queryId, _timestamp);
-        if (_found) {
-            _index++;
-        }
-        uint256 _valCount = tellor.getNewValueCountbyQueryId(_queryId);
-        // no value after timestamp
-        if (_valCount <= _index) {
-            return (false, 0);
-        }
-        uint256 _timestampRetrieved = tellor.getTimestampbyQueryIdandIndex(
-            _queryId,
-            _index
-        );
+        uint256 _count = getNewValueCountbyQueryId(_queryId);
+        if (_count == 0) return (false, 0);
+        _count--;
+        bool _search = true; // perform binary search
+        uint256 _middle = 0;
+        uint256 _start = 0;
+        uint256 _end = _count;
+        uint256 _timestampRetrieved;
+        // checking boundaries to short-circuit the algorithm
+        _timestampRetrieved = getTimestampbyQueryIdandIndex(_queryId, _end);
+        if (_timestampRetrieved <= _timestamp) return (false, 0);
+        _timestampRetrieved = getTimestampbyQueryIdandIndex(_queryId, _start);
         if (_timestampRetrieved > _timestamp) {
-            return (true, _index);
+            // candidate found, check for disputes
+            _search = false;
         }
-        // if _timestampRetrieved equals _timestamp, try next value
-        _index++;
-        // no value after timestamp
-        if (_valCount <= _index) {
-            return (false, 0);
+        // since the value is within our boundaries, do a binary search
+        while (_search) {
+            _middle = (_end + _start) / 2;
+            _timestampRetrieved = getTimestampbyQueryIdandIndex(
+                _queryId,
+                _middle
+            );
+            if (_timestampRetrieved > _timestamp) {
+                // get immediate previous value
+                uint256 _prevTime = getTimestampbyQueryIdandIndex(
+                    _queryId,
+                    _middle - 1
+                );
+                if (_prevTime <= _timestamp) {
+                    // candidate found, check for disputes
+                    _search = false;
+                } else {
+                    // look from start to middle -1(prev value)
+                    _end = _middle - 1;
+                }
+            } else {
+                // get immediate next value
+                uint256 _nextTime = getTimestampbyQueryIdandIndex(
+                    _queryId,
+                    _middle + 1
+                );
+                if (_nextTime > _timestamp) {
+                    // candidate found, check for disputes
+                    _search = false;
+                    _middle++;
+                    _timestampRetrieved = _nextTime;
+                } else {
+                    // look from middle + 1(next value) to end
+                    _start = _middle + 1;
+                }
+            }
+        }
+        // candidate found, check for disputed values
+        if (!isInDispute(_queryId, _timestampRetrieved)) {
+            // _timestampRetrieved is correct
+            return (true, _middle);
+        } else {
+            // iterate forward until we find a non-disputed value
+            while (
+                isInDispute(_queryId, _timestampRetrieved) && _middle < _count
+            ) {
+                _middle++;
+                _timestampRetrieved = getTimestampbyQueryIdandIndex(
+                    _queryId,
+                    _middle
+                );
+            }
+            if (
+                _middle == _count && isInDispute(_queryId, _timestampRetrieved)
+            ) {
+                return (false, 0);
+            }
+            // _timestampRetrieved is correct
+            return (true, _middle);
         }
-        _timestampRetrieved = tellor.getTimestampbyQueryIdandIndex(
-            _queryId,
-            _index
-        );
-        return (true, _index);
     }
 
     /**
@@ -148,6 +192,7 @@ contract UsingTellor {
         view
         returns (bytes[] memory _values, uint256[] memory _timestamps)
     {
+        // get index of first possible value
         (bool _ifRetrieve, uint256 _startIndex) = getIndexForDataAfter(
             _queryId,
             _timestamp - _maxAge
@@ -157,27 +202,34 @@ contract UsingTellor {
             return (new bytes[](0), new uint256[](0));
         }
         uint256 _endIndex;
+        // get index of last possible value
         (_ifRetrieve, _endIndex) = getIndexForDataBefore(_queryId, _timestamp);
         // no value before _timestamp
         if (!_ifRetrieve) {
             return (new bytes[](0), new uint256[](0));
         }
-        uint256 _valCount = _endIndex - _startIndex + 1;
-        // more than _maxCount values found within range
-        if (_valCount > _maxCount) {
-            _startIndex = _endIndex - _maxCount + 1;
-            _valCount = _maxCount;
+        uint256 _valCount = 0;
+        uint256 _index = 0;
+        uint256[] memory _timestampsArrayTemp = new uint256[](_maxCount);
+        // generate array of non-disputed timestamps within range
+        while (_valCount < _maxCount && _endIndex + 1 - _index > _startIndex) {
+            uint256 _timestampRetrieved = getTimestampbyQueryIdandIndex(
+                _queryId,
+                _endIndex - _index
+            );
+            if (!isInDispute(_queryId, _timestampRetrieved)) {
+                _timestampsArrayTemp[_valCount] = _timestampRetrieved;
+                _valCount++;
+            }
+            _index++;
         }
+
         bytes[] memory _valuesArray = new bytes[](_valCount);
         uint256[] memory _timestampsArray = new uint256[](_valCount);
-        bytes memory _valueRetrieved;
+        // retrieve values and reverse timestamps order
         for (uint256 _i = 0; _i < _valCount; _i++) {
-            _timestampsArray[_i] = getTimestampbyQueryIdandIndex(
-                _queryId,
-                (_startIndex + _i)
-            );
-            _valueRetrieved = retrieveData(_queryId, _timestampsArray[_i]);
-            _valuesArray[_i] = _valueRetrieved;
+            _timestampsArray[_i] = _timestampsArrayTemp[_valCount - 1 - _i];
+            _valuesArray[_i] = retrieveData(_queryId, _timestampsArray[_i]);
         }
         return (_valuesArray, _timestampsArray);
     }
@@ -223,7 +275,19 @@ contract UsingTellor {
         return tellor.getTimestampbyQueryIdandIndex(_queryId, _index);
     }
 
-
+    /**
+     * @dev Determines whether a value with a given queryId and timestamp has been disputed
+     * @param _queryId is the value id to look up
+     * @param _timestamp is the timestamp of the value to look up
+     * @return bool true if queryId/timestamp is under dispute
+     */
+    function isInDispute(bytes32 _queryId, uint256 _timestamp)
+        public
+        view
+        returns (bool)
+    {
+        return tellor.isInDispute(_queryId, _timestamp);
+    }
 
     /**
      * @dev Retrieve value from oracle based on queryId/timestamp
@@ -239,6 +303,61 @@ contract UsingTellor {
         return tellor.retrieveData(_queryId, _timestamp);
     }
 
+    /**
+     * @dev allows dev to set mapping contract for valueFor (EIP2362)
+     * @param _addy address of mapping contract
+     */
+    function setIdMappingContract(address _addy) external {
+        require(address(idMappingContract) == address(0));
+        idMappingContract = IMappingContract(_addy);
+    }
+
+    /**
+     * @dev Retrieve most recent int256 value from oracle based on queryId
+     * @param _id being requested
+     * @return _value most recent value submitted
+     * @return _timestamp timestamp of most recent value
+     * @return _statusCode 200 if value found, 404 if not found
+     */
+    function valueFor(bytes32 _id)
+        external
+        view
+        override
+        returns (
+            int256 _value,
+            uint256 _timestamp,
+            uint256 _statusCode
+        )
+    {
+        bytes32 _queryId = idMappingContract.getTellorID(_id);
+        bytes memory _valueBytes;
+        (_valueBytes, _timestamp) = getDataBefore(
+            _queryId,
+            block.timestamp + 1
+        );
+        if (_timestamp == 0) {
+            return (0, 0, 404);
+        }
+        uint256 _valueUint = _sliceUint(_valueBytes);
+        _value = int256(_valueUint);
+        return (_value, _timestamp, 200);
+    }
+
+    // Internal functions
+    /**
+     * @dev Convert bytes to uint256
+     * @param _b bytes value to convert to uint256
+     * @return _number uint256 converted from bytes
+     */
+    function _sliceUint(bytes memory _b)
+        internal
+        pure
+        returns (uint256 _number)
+    {
+        for (uint256 _i = 0; _i < _b.length; _i++) {
+            _number = _number * 256 + uint8(_b[_i]);
+        }
+    }
     /**
      * @dev Allows the user to get the latest value for the queryId specified
      * @param _queryId is the id to look up the value for
diff --git a/contracts/TellorFlex/TellorFlex.sol b/contracts/TellorFlex/TellorFlex.sol
deleted file mode 100644
index 537a4236..00000000
--- a/contracts/TellorFlex/TellorFlex.sol
+++ /dev/null
@@ -1,594 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.3;
-
-import "../../interfaces/IERC20.sol";
-
-/**
- @author Tellor Inc.
- @title TellorFlex
- @dev This is a streamlined Tellor oracle system which handles staking, reporting,
- * slashing, and user data getters in one contract. This contract is controlled
- * by a single address known as 'governance', which could be an externally owned
- * account or a contract, allowing for a flexible, modular design.
-*/
-contract TellorFlex {
-    IERC20 public token;
-    address public governance;
-    uint256 public stakeAmount; //amount required to be a staker
-    uint256 public totalStakeAmount; //total amount of tokens locked in contract (via stake)
-    uint256 public reportingLock; // base amount of time before a reporter is able to submit a value again
-    uint256 public timeOfLastNewValue = block.timestamp; // time of the last new submitted value, originally set to the block timestamp
-    mapping(bytes32 => Report) private reports; // mapping of query IDs to a report
-    mapping(address => StakeInfo) stakerDetails; //mapping from a persons address to their staking info
-
-    // Structs
-    struct Report {
-        uint256[] timestamps; // array of all newValueTimestamps reported
-        mapping(uint256 => uint256) timestampIndex; // mapping of timestamps to respective indices
-        mapping(uint256 => uint256) timestampToBlockNum; // mapping of timestamp to block number
-        mapping(uint256 => bytes) valueByTimestamp; // mapping of timestamps to values
-        mapping(uint256 => address) reporterByTimestamp; // mapping of timestamps to reporters
-    }
-
-    struct StakeInfo {
-        uint256 startDate; //stake start date
-        uint256 stakedBalance; // staked balance
-        uint256 lockedBalance; // amount locked for withdrawal
-        uint256 reporterLastTimestamp; // timestamp of reporter's last reported value
-        uint256 reportsSubmitted; // total number of reports submitted by reporter
-        mapping(bytes32 => uint256) reportsSubmittedByQueryId;
-    }
-
-    // Events
-    event NewGovernanceAddress(address _newGovernanceAddress);
-    event NewReport(
-        bytes32 _queryId,
-        uint256 _time,
-        bytes _value,
-        uint256 _nonce,
-        bytes _queryData,
-        address _reporter
-    );
-    event NewReportingLock(uint256 _newReportingLock);
-    event NewStakeAmount(uint256 _newStakeAmount);
-    event NewStaker(address _staker, uint256 _amount);
-    event ReporterSlashed(
-        address _reporter,
-        address _recipient,
-        uint256 _slashAmount
-    );
-    event StakeWithdrawRequested(address _staker, uint256 _amount);
-    event StakeWithdrawn(address _staker);
-    event ValueRemoved(bytes32 _queryId, uint256 _timestamp);
-
-    /**
-     * @dev Initializes system parameters
-     * @param _token address of token used for staking
-     * @param _governance address which controls system
-     * @param _stakeAmount amount of token needed to report oracle values
-     * @param _reportingLock base amount of time (seconds) before reporter is able to report again
-     */
-    constructor(
-        address _token,
-        address _governance,
-        uint256 _stakeAmount,
-        uint256 _reportingLock
-    ) {
-        require(_token != address(0), "must set token address");
-        require(_governance != address(0), "must set governance address");
-        token = IERC20(_token);
-        governance = _governance;
-        stakeAmount = _stakeAmount;
-        reportingLock = _reportingLock;
-    }
-
-    /**
-     * @dev Changes governance address
-     * @param _newGovernanceAddress new governance address
-     */
-    function changeGovernanceAddress(address _newGovernanceAddress) external {
-        require(msg.sender == governance, "caller must be governance address");
-        require(
-            _newGovernanceAddress != address(0),
-            "must set governance address"
-        );
-        governance = _newGovernanceAddress;
-        emit NewGovernanceAddress(_newGovernanceAddress);
-    }
-
-    /**
-     * @dev Changes base amount of time (seconds) before reporter is allowed to report again
-     * @param _newReportingLock new reporter lock time in seconds
-     */
-    function changeReportingLock(uint256 _newReportingLock) external {
-        require(msg.sender == governance, "caller must be governance address");
-        require(
-            _newReportingLock > 0,
-            "reporting lock must be greater than zero"
-        );
-        reportingLock = _newReportingLock;
-        emit NewReportingLock(_newReportingLock);
-    }
-
-    /**
-     * @dev Changes amount of token stake required to report values
-     * @param _newStakeAmount new reporter stake amount
-     */
-    function changeStakeAmount(uint256 _newStakeAmount) external {
-        require(msg.sender == governance, "caller must be governance address");
-        require(_newStakeAmount > 0, "stake amount must be greater than zero");
-        stakeAmount = _newStakeAmount;
-        emit NewStakeAmount(_newStakeAmount);
-    }
-
-    /**
-     * @dev Allows a reporter to submit stake
-     * @param _amount amount of tokens to stake
-     */
-    function depositStake(uint256 _amount) external {
-        StakeInfo storage _staker = stakerDetails[msg.sender];
-        if (_staker.lockedBalance > 0) {
-            if (_staker.lockedBalance >= _amount) {
-                _staker.lockedBalance -= _amount;
-            } else {
-                require(
-                    token.transferFrom(
-                        msg.sender,
-                        address(this),
-                        _amount - _staker.lockedBalance
-                    )
-                );
-                _staker.lockedBalance = 0;
-            }
-        } else {
-            require(token.transferFrom(msg.sender, address(this), _amount));
-        }
-        _staker.startDate = block.timestamp; // This resets their stake start date to now
-        _staker.stakedBalance += _amount;
-        totalStakeAmount += _amount;
-        emit NewStaker(msg.sender, _amount);
-    }
-
-    /**
-     * @dev Removes a value from the oracle.
-     * Note: this function is only callable by the Governance contract.
-     * @param _queryId is ID of the specific data feed
-     * @param _timestamp is the timestamp of the data value to remove
-     */
-    function removeValue(bytes32 _queryId, uint256 _timestamp) external {
-        require(msg.sender == governance, "caller must be governance address");
-        Report storage rep = reports[_queryId];
-        uint256 _index = rep.timestampIndex[_timestamp];
-        require(_timestamp == rep.timestamps[_index], "invalid timestamp");
-        // Shift all timestamps back to reflect deletion of value
-        for (uint256 _i = _index; _i < rep.timestamps.length - 1; _i++) {
-            rep.timestamps[_i] = rep.timestamps[_i + 1];
-            rep.timestampIndex[rep.timestamps[_i]] -= 1;
-        }
-        // Delete and reset timestamp and value
-        delete rep.timestamps[rep.timestamps.length - 1];
-        rep.timestamps.pop();
-        rep.valueByTimestamp[_timestamp] = "";
-        rep.timestampIndex[_timestamp] = 0;
-        emit ValueRemoved(_queryId, _timestamp);
-    }
-
-    /**
-     * @dev Allows a reporter to request to withdraw their stake
-     * @param _amount amount of staked tokens requesting to withdraw
-     */
-    function requestStakingWithdraw(uint256 _amount) external {
-        StakeInfo storage _staker = stakerDetails[msg.sender];
-        require(
-            _staker.stakedBalance >= _amount,
-            "insufficient staked balance"
-        );
-        _staker.startDate = block.timestamp;
-        _staker.lockedBalance += _amount;
-        _staker.stakedBalance -= _amount;
-        totalStakeAmount -= _amount;
-        emit StakeWithdrawRequested(msg.sender, _amount);
-    }
-
-    /**
-     * @dev Slashes a reporter and transfers their stake amount to the given recipient
-     * Note: this function is only callable by the governance address.
-     * @param _reporter is the address of the reporter being slashed
-     * @param _recipient is the address receiving the reporter's stake
-     * @return uint256 amount of token slashed and sent to recipient address
-     */
-    function slashReporter(address _reporter, address _recipient)
-        external
-        returns (uint256)
-    {
-        require(msg.sender == governance, "only governance can slash reporter");
-        StakeInfo storage _staker = stakerDetails[_reporter];
-        require(
-            _staker.stakedBalance + _staker.lockedBalance > 0,
-            "zero staker balance"
-        );
-        uint256 _slashAmount;
-        if (_staker.lockedBalance >= stakeAmount) {
-            _slashAmount = stakeAmount;
-            _staker.lockedBalance -= stakeAmount;
-        } else if (
-            _staker.lockedBalance + _staker.stakedBalance >= stakeAmount
-        ) {
-            _slashAmount = stakeAmount;
-            _staker.stakedBalance -= stakeAmount - _staker.lockedBalance;
-            totalStakeAmount -= stakeAmount - _staker.lockedBalance;
-            _staker.lockedBalance = 0;
-        } else {
-            _slashAmount = _staker.stakedBalance + _staker.lockedBalance;
-            totalStakeAmount -= _staker.stakedBalance;
-            _staker.stakedBalance = 0;
-            _staker.lockedBalance = 0;
-        }
-        token.transfer(_recipient, _slashAmount);
-        emit ReporterSlashed(_reporter, _recipient, _slashAmount);
-        return (_slashAmount);
-    }
-
-    /**
-     * @dev Allows a reporter to submit a value to the oracle
-     * @param _queryId is ID of the specific data feed. Equals keccak256(_queryData) for non-legacy IDs
-     * @param _value is the value the user submits to the oracle
-     * @param _nonce is the current value count for the query id
-     * @param _queryData is the data used to fulfill the data query
-     */
-    function submitValue(
-        bytes32 _queryId,
-        bytes calldata _value,
-        uint256 _nonce,
-        bytes memory _queryData
-    ) external {
-        Report storage rep = reports[_queryId];
-        require(
-            _nonce == rep.timestamps.length || _nonce == 0,
-            "nonce must match timestamp index"
-        );
-        StakeInfo storage _staker = stakerDetails[msg.sender];
-        require(
-            _staker.stakedBalance >= stakeAmount,
-            "balance must be greater than stake amount"
-        );
-        // Require reporter to abide by given reporting lock
-        // require(
-        //     (block.timestamp - _staker.reporterLastTimestamp) * 1000 >
-        //         (reportingLock * 1000) / (_staker.stakedBalance / stakeAmount),
-        //     "still in reporter time lock, please wait!"
-        // );
-        require(
-            _queryId == keccak256(_queryData) || uint256(_queryId) <= 100,
-            "id must be hash of bytes data"
-        );
-        _staker.reporterLastTimestamp = block.timestamp;
-        // Checks for no double reporting of timestamps
-        require(
-            rep.reporterByTimestamp[block.timestamp] == address(0),
-            "timestamp already reported for"
-        );
-        // Update number of timestamps, value for given timestamp, and reporter for timestamp
-        rep.timestampIndex[block.timestamp] = rep.timestamps.length;
-        rep.timestamps.push(block.timestamp);
-        rep.timestampToBlockNum[block.timestamp] = block.number;
-        rep.valueByTimestamp[block.timestamp] = _value;
-        rep.reporterByTimestamp[block.timestamp] = msg.sender;
-        // Update last oracle value and number of values submitted by a reporter
-        timeOfLastNewValue = block.timestamp;
-        _staker.reportsSubmitted++;
-        _staker.reportsSubmittedByQueryId[_queryId]++;
-        emit NewReport(
-            _queryId,
-            block.timestamp,
-            _value,
-            _nonce,
-            _queryData,
-            msg.sender
-        );
-    }
-
-    /**
-     * @dev Withdraws a reporter's stake
-     */
-    function withdrawStake() external {
-        StakeInfo storage _s = stakerDetails[msg.sender];
-        // Ensure reporter is locked and that enough time has passed
-        require(block.timestamp - _s.startDate >= 7 days, "7 days didn't pass");
-        require(_s.lockedBalance > 0, "reporter not locked for withdrawal");
-        token.transfer(msg.sender, _s.lockedBalance);
-        _s.lockedBalance = 0;
-        emit StakeWithdrawn(msg.sender);
-    }
-
-    //Getters
-    /**
-     * @dev Returns the block number at a given timestamp
-     * @param _queryId is ID of the specific data feed
-     * @param _timestamp is the timestamp to find the corresponding block number for
-     * @return uint256 block number of the timestamp for the given data ID
-     */
-    function getBlockNumberByTimestamp(bytes32 _queryId, uint256 _timestamp)
-        external
-        view
-        returns (uint256)
-    {
-        return reports[_queryId].timestampToBlockNum[_timestamp];
-    }
-
-    /**
-     * @dev Returns the current value of a data feed given a specific ID
-     * @param _queryId is the ID of the specific data feed
-     * @return bytes memory of the current value of data
-     */
-    function getCurrentValue(bytes32 _queryId)
-        external
-        view
-        returns (bytes memory)
-    {
-        return
-            reports[_queryId].valueByTimestamp[
-                reports[_queryId].timestamps[
-                    reports[_queryId].timestamps.length - 1
-                ]
-            ];
-    }
-
-    /**
-     * @dev Returns governance address
-     * @return address governance
-     */
-    function getGovernanceAddress() external view returns (address) {
-        return governance;
-    }
-
-    /**
-     * @dev Counts the number of values that have been submitted for the request.
-     * @param _queryId the id to look up
-     * @return uint256 count of the number of values received for the id
-     */
-    function getNewValueCountbyQueryId(bytes32 _queryId)
-        external
-        view
-        returns (uint256)
-    {
-        return reports[_queryId].timestamps.length;
-    }
-
-    /**
-     * @dev Returns reporter address and whether a value was removed for a given queryId and timestamp
-     * @param _queryId the id to look up
-     * @param _timestamp is the timestamp of the value to look up
-     * @return address reporter who submitted the value
-     * @return bool true if the value was removed
-     */
-    function getReportDetails(bytes32 _queryId, uint256 _timestamp)
-        external
-        view
-        returns (address, bool)
-    {
-        bool _wasRemoved = reports[_queryId].timestampIndex[_timestamp] == 0 &&
-            keccak256(reports[_queryId].valueByTimestamp[_timestamp]) ==
-            keccak256(bytes("")) &&
-            reports[_queryId].reporterByTimestamp[_timestamp] != address(0);
-        return (reports[_queryId].reporterByTimestamp[_timestamp], _wasRemoved);
-    }
-
-    /**
-     * @dev Returns the address of the reporter who submitted a value for a data ID at a specific time
-     * @param _queryId is ID of the specific data feed
-     * @param _timestamp is the timestamp to find a corresponding reporter for
-     * @return address of the reporter who reported the value for the data ID at the given timestamp
-     */
-    function getReporterByTimestamp(bytes32 _queryId, uint256 _timestamp)
-        external
-        view
-        returns (address)
-    {
-        return reports[_queryId].reporterByTimestamp[_timestamp];
-    }
-
-    /**
-     * @dev Returns the timestamp of the reporter's last submission
-     * @param _reporter is address of the reporter
-     * @return uint256 timestamp of the reporter's last submission
-     */
-    function getReporterLastTimestamp(address _reporter)
-        external
-        view
-        returns (uint256)
-    {
-        return stakerDetails[_reporter].reporterLastTimestamp;
-    }
-
-    /**
-     * @dev Returns the reporting lock time, the amount of time a reporter must wait to submit again
-     * @return uint256 reporting lock time
-     */
-    function getReportingLock() external view returns (uint256) {
-        return reportingLock;
-    }
-
-    /**
-     * @dev Returns the number of values submitted by a specific reporter address
-     * @param _reporter is the address of a reporter
-     * @return uint256 of the number of values submitted by the given reporter
-     */
-    function getReportsSubmittedByAddress(address _reporter)
-        external
-        view
-        returns (uint256)
-    {
-        return stakerDetails[_reporter].reportsSubmitted;
-    }
-
-    /**
-     * @dev Returns the number of values submitted to a specific queryId by a specific reporter address
-     * @param _reporter is the address of a reporter
-     * @param _queryId is the ID of the specific data feed
-     * @return uint256 of the number of values submitted by the given reporter to the given queryId
-     */
-    function getReportsSubmittedByAddressAndQueryId(
-        address _reporter,
-        bytes32 _queryId
-    ) external view returns (uint256) {
-        return stakerDetails[_reporter].reportsSubmittedByQueryId[_queryId];
-    }
-
-    /**
-     * @dev Returns amount required to report oracle values
-     * @return uint256 stake amount
-     */
-    function getStakeAmount() external view returns (uint256) {
-        return stakeAmount;
-    }
-
-    /**
-     * @dev Allows users to retrieve all information about a staker
-     * @param _staker address of staker inquiring about
-     * @return uint startDate of staking
-     * @return uint current amount staked
-     * @return uint current amount locked for withdrawal
-     * @return uint reporter's last reported timestamp
-     * @return uint total number of reports submitted by reporter
-     */
-    function getStakerInfo(address _staker)
-        external
-        view
-        returns (
-            uint256,
-            uint256,
-            uint256,
-            uint256,
-            uint256
-        )
-    {
-        return (
-            stakerDetails[_staker].startDate,
-            stakerDetails[_staker].stakedBalance,
-            stakerDetails[_staker].lockedBalance,
-            stakerDetails[_staker].reporterLastTimestamp,
-            stakerDetails[_staker].reportsSubmitted
-        );
-    }
-
-    /**
-     * @dev Returns the timestamp for the last value of any ID from the oracle
-     * @return uint256 of timestamp of the last oracle value
-     */
-    function getTimeOfLastNewValue() external view returns (uint256) {
-        return timeOfLastNewValue;
-    }
-
-    /**
-     * @dev Gets the timestamp for the value based on their index
-     * @param _queryId is the id to look up
-     * @param _index is the value index to look up
-     * @return uint256 timestamp
-     */
-    function getTimestampbyQueryIdandIndex(bytes32 _queryId, uint256 _index)
-        external
-        view
-        returns (uint256)
-    {
-        return reports[_queryId].timestamps[_index];
-    }
-
-    /**
-     * @dev Returns the index of a reporter timestamp in the timestamp array for a specific data ID
-     * @param _queryId is ID of the specific data feed
-     * @param _timestamp is the timestamp to find in the timestamps array
-     * @return uint256 of the index of the reporter timestamp in the array for specific ID
-     */
-    function getTimestampIndexByTimestamp(bytes32 _queryId, uint256 _timestamp)
-        external
-        view
-        returns (uint256)
-    {
-        return reports[_queryId].timestampIndex[_timestamp];
-    }
-
-    /**
-     * @dev Returns the address of the token used for staking
-     * @return address of the token used for staking
-     */
-    function getTokenAddress() external view returns (address) {
-        return address(token);
-    }
-
-    /**
-     * @dev Returns total amount of token staked for reporting
-     * @return uint256 total amount of token staked
-     */
-    function getTotalStakeAmount() external view returns (uint256) {
-        return totalStakeAmount;
-    }
-
-    /**
-     * @dev Retrieve value from oracle based on timestamp
-     * @param _queryId being requested
-     * @param _timestamp to retrieve data/value from
-     * @return bytes value for timestamp submitted
-     */
-    function retrieveData(bytes32 _queryId, uint256 _timestamp)
-        external
-        view
-        returns (bytes memory)
-    {
-        return reports[_queryId].valueByTimestamp[_timestamp];
-    }
-
-     function getIndexForDataBefore(bytes32 _queryId, uint256 _timestamp)
-        public
-        view
-        returns (bool _found, uint256 _index)
-    {
-        uint256 _count = this.getNewValueCountbyQueryId(_queryId);
-        if (_count > 0) {
-            uint256 _middle;
-            uint256 _start = 0;
-            uint256 _end = _count - 1;
-            uint256 _time;
-            //Checking Boundaries to short-circuit the algorithm
-            _time = this.getTimestampbyQueryIdandIndex(_queryId, _start);
-            if (_time >= _timestamp) return (false, 0);
-            _time = this.getTimestampbyQueryIdandIndex(_queryId, _end);
-            if (_time < _timestamp) {
-                return (true, _end);
-            }
-            //Since the value is within our boundaries, do a binary search
-            while (true) {
-                _middle = (_end - _start) / 2 + 1 + _start;
-                _time = this.getTimestampbyQueryIdandIndex(_queryId, _middle);
-                if (_time < _timestamp) {
-                    //get immediate next value
-                    uint256 _nextTime = this.getTimestampbyQueryIdandIndex(
-                        _queryId,
-                        _middle + 1
-                    );
-                    if (_nextTime >= _timestamp) {
-                            return (true, _middle);
-
-                    } else {
-                        //look from middle + 1(next value) to end
-                        _start = _middle + 1;
-                    }
-                } else {
-                    uint256 _prevTime = this.getTimestampbyQueryIdandIndex(
-                        _queryId,
-                        _middle - 1
-                    );
-                    if (_prevTime < _timestamp) {
-                            // _prevtime is correct
-                            return (true, _middle);
-
-                    } else {
-                        //look from start to middle -1(prev value)
-                        _end = _middle - 1;
-                    }
-                }
-            }
-        }
-        return (false, 0);
-    }
-}
diff --git a/interfaces/IERC20.sol b/interfaces/IERC20.sol
index 476d928d..9dc25850 100644
--- a/interfaces/IERC20.sol
+++ b/interfaces/IERC20.sol
@@ -1,9 +1,8 @@
 // SPDX-License-Identifier: MIT
-pragma solidity 0.8.3;
+pragma solidity ^0.8.3;
 
 interface IERC20 {
     function balanceOf(address account) external view returns (uint256);
-    function approve(address account, uint256 amount) external;
 
     function transfer(address recipient, uint256 amount)
         external
@@ -14,4 +13,8 @@ interface IERC20 {
         address recipient,
         uint256 amount
     ) external returns (bool);
+
+    function approve(address _spender, uint256 _amount)
+        external
+        returns (bool);
 }
diff --git a/interfaces/IERC2362.sol b/interfaces/IERC2362.sol
new file mode 100644
index 00000000..f474f913
--- /dev/null
+++ b/interfaces/IERC2362.sol
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.8.0;
+
+/**
+    * @dev EIP2362 Interface for pull oracles
+    * https://github.com/tellor-io/EIP-2362
+*/
+interface IERC2362
+{
+	/**
+	 * @dev Exposed function pertaining to EIP standards
+	 * @param _id bytes32 ID of the query
+	 * @return int,uint,uint returns the value, timestamp, and status code of query
+	 */
+	function valueFor(bytes32 _id) external view returns(int256,uint256,uint256);
+}
diff --git a/interfaces/IMappingContract.sol b/interfaces/IMappingContract.sol
new file mode 100644
index 00000000..88685a70
--- /dev/null
+++ b/interfaces/IMappingContract.sol
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+interface IMappingContract{
+    function getTellorID(bytes32 _id) external view returns(bytes32);
+}
diff --git a/interfaces/IOracle.sol b/interfaces/IOracle.sol
new file mode 100644
index 00000000..57f19f18
--- /dev/null
+++ b/interfaces/IOracle.sol
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.3;
+
+/**
+ @author Tellor Inc.
+ @title TellorFlex
+ @dev This is a streamlined Tellor oracle system which handles staking, reporting,
+ * slashing, and user data getters in one contract. This contract is controlled
+ * by a single address known as 'governance', which could be an externally owned
+ * account or a contract, allowing for a flexible, modular design.
+*/
+interface IOracle {
+    /**
+     * @dev Removes a value from the oracle.
+     * Note: this function is only callable by the Governance contract.
+     * @param _queryId is ID of the specific data feed
+     * @param _timestamp is the timestamp of the data value to remove
+     */
+    function removeValue(bytes32 _queryId, uint256 _timestamp) external;
+
+    /**
+     * @dev Slashes a reporter and transfers their stake amount to the given recipient
+     * Note: this function is only callable by the governance address.
+     * @param _reporter is the address of the reporter being slashed
+     * @param _recipient is the address receiving the reporter's stake
+     * @return uint256 amount of token slashed and sent to recipient address
+     */
+    function slashReporter(address _reporter, address _recipient)
+        external
+        returns (uint256);
+
+    // *****************************************************************************
+    // *                                                                           *
+    // *                               Getters                                     *
+    // *                                                                           *
+    // *****************************************************************************
+
+    /**
+     * @dev Returns the block number at a given timestamp
+     * @param _queryId is ID of the specific data feed
+     * @param _timestamp is the timestamp to find the corresponding block number for
+     * @return uint256 block number of the timestamp for the given data ID
+     */
+    function getBlockNumberByTimestamp(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (uint256);
+
+    /**
+     * @dev Returns the address of the reporter who submitted a value for a data ID at a specific time
+     * @param _queryId is ID of the specific data feed
+     * @param _timestamp is the timestamp to find a corresponding reporter for
+     * @return address of the reporter who reported the value for the data ID at the given timestamp
+     */
+    function getReporterByTimestamp(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (address);
+
+    /**
+     * @dev Returns the number of values submitted by a specific reporter address
+     * @param _reporter is the address of a reporter
+     * @return uint256 of the number of values submitted by the given reporter
+     */
+    function getReportsSubmittedByAddress(address _reporter)
+        external
+        view
+        returns (uint256);
+
+    /**
+     * @dev Returns amount required to report oracle values
+     * @return uint256 stake amount
+     */
+    function getStakeAmount() external view returns (uint256);
+
+    /**
+     * @dev Allows users to retrieve all information about a staker
+     * @param _stakerAddress address of staker inquiring about
+     * @return uint startDate of staking
+     * @return uint current amount staked
+     * @return uint current amount locked for withdrawal
+     * @return uint reward debt used to calculate staking rewards
+     * @return uint reporter's last reported timestamp
+     * @return uint total number of reports submitted by reporter
+     * @return uint governance vote count when first staked
+     * @return uint number of votes cast by staker when first staked
+     */
+    function getStakerInfo(address _stakerAddress)
+        external
+        view
+        returns (
+            uint256,
+            uint256,
+            uint256,
+            uint256,
+            uint256,
+            uint256,
+            uint256,
+            uint256
+        );
+
+    /**
+     * @dev Retrieves the latest value for the queryId before the specified timestamp
+     * @param _queryId is the queryId to look up the value for
+     * @param _timestamp before which to search for latest value
+     * @return _ifRetrieve bool true if able to retrieve a non-zero value
+     * @return _value the value retrieved
+     * @return _timestampRetrieved the value's timestamp
+     */
+    function getDataBefore(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (
+            bool _ifRetrieve,
+            bytes memory _value,
+            uint256 _timestampRetrieved
+        );
+
+    /**
+     * @dev Returns the address of the token used for staking
+     * @return address of the token used for staking
+     */
+    function getTokenAddress() external view returns (address);
+
+    /**
+     * @dev Retrieve value from oracle based on timestamp
+     * @param _queryId being requested
+     * @param _timestamp to retrieve data/value from
+     * @return bytes value for timestamp submitted
+     */
+    function retrieveData(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (bytes memory);
+}
diff --git a/interfaces/IQueryDataStorage.sol b/interfaces/IQueryDataStorage.sol
new file mode 100644
index 00000000..ac9fe99f
--- /dev/null
+++ b/interfaces/IQueryDataStorage.sol
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+interface IQueryDataStorage {
+  function storeData(bytes memory _queryData) external;
+  function getQueryData(bytes32 _queryId) external view returns (bytes memory);
+}
diff --git a/interfaces/ITellor.sol b/interfaces/ITellor.sol
index fce4d10a..1d165e99 100644
--- a/interfaces/ITellor.sol
+++ b/interfaces/ITellor.sol
@@ -1,90 +1,291 @@
 // SPDX-License-Identifier: MIT
 pragma solidity >=0.8.0;
 
-interface ITellor{
+interface ITellor {
     //Controller
-    function addresses(bytes32) external view returns(address);
-    function uints(bytes32) external view returns(uint256);
+    function addresses(bytes32) external view returns (address);
+
+    function uints(bytes32) external view returns (uint256);
+
     function burn(uint256 _amount) external;
+
     function changeDeity(address _newDeity) external;
+
     function changeOwner(address _newOwner) external;
-    function changeTellorContract(address _tContract) external;
-    function changeControllerContract(address _newController) external;
-    function changeGovernanceContract(address _newGovernance) external;
-    function changeOracleContract(address _newOracle) external;
-    function changeTreasuryContract(address _newTreasury) external;
     function changeUint(bytes32 _target, uint256 _amount) external;
+
     function migrate() external;
+
     function mint(address _reciever, uint256 _amount) external;
+
     function init() external;
-    function getAllDisputeVars(uint256 _disputeId) external view returns (bytes32,bool,bool,bool,address,address,address,uint256[9] memory,int256);
-    function getDisputeIdByDisputeHash(bytes32 _hash) external view returns (uint256);
-    function getDisputeUintVars(uint256 _disputeId, bytes32 _data) external view returns(uint256);
-    function getLastNewValueById(uint256 _requestId) external view returns (uint256, bool);
-    function retrieveData(uint256 _requestId, uint256 _timestamp) external view returns (uint256);
-    function getNewValueCountbyRequestId(uint256 _requestId) external view returns (uint256);
+
+    function getAllDisputeVars(uint256 _disputeId)
+        external
+        view
+        returns (
+            bytes32,
+            bool,
+            bool,
+            bool,
+            address,
+            address,
+            address,
+            uint256[9] memory,
+            int256
+        );
+
+    function getDisputeIdByDisputeHash(bytes32 _hash)
+        external
+        view
+        returns (uint256);
+
+    function getDisputeUintVars(uint256 _disputeId, bytes32 _data)
+        external
+        view
+        returns (uint256);
+
+    function getLastNewValueById(uint256 _requestId)
+        external
+        view
+        returns (uint256, bool);
+
+    function retrieveData(uint256 _requestId, uint256 _timestamp)
+        external
+        view
+        returns (uint256);
+
+    function getNewValueCountbyRequestId(uint256 _requestId)
+        external
+        view
+        returns (uint256);
+
     function getAddressVars(bytes32 _data) external view returns (address);
+
     function getUintVar(bytes32 _data) external view returns (uint256);
+
     function totalSupply() external view returns (uint256);
+
     function name() external pure returns (string memory);
+
     function symbol() external pure returns (string memory);
+
     function decimals() external pure returns (uint8);
+
     function isMigrated(address _addy) external view returns (bool);
-    function allowance(address _user, address _spender) external view  returns (uint256);
-    function allowedToTrade(address _user, uint256 _amount) external view returns (bool);
+
+    function allowance(address _user, address _spender)
+        external
+        view
+        returns (uint256);
+
+    function allowedToTrade(address _user, uint256 _amount)
+        external
+        view
+        returns (bool);
+
     function approve(address _spender, uint256 _amount) external returns (bool);
-    function approveAndTransferFrom(address _from, address _to, uint256 _amount) external returns(bool);
+
+    function approveAndTransferFrom(
+        address _from,
+        address _to,
+        uint256 _amount
+    ) external returns (bool);
+
     function balanceOf(address _user) external view returns (uint256);
-    function balanceOfAt(address _user, uint256 _blockNumber)external view returns (uint256);
-    function transfer(address _to, uint256 _amount)external returns (bool success);
-    function transferFrom(address _from,address _to,uint256 _amount) external returns (bool success) ;
+
+    function balanceOfAt(address _user, uint256 _blockNumber)
+        external
+        view
+        returns (uint256);
+
+    function transfer(address _to, uint256 _amount)
+        external
+        returns (bool success);
+
+    function transferFrom(
+        address _from,
+        address _to,
+        uint256 _amount
+    ) external returns (bool success);
+
     function depositStake() external;
+
     function requestStakingWithdraw() external;
+
     function withdrawStake() external;
-    function changeStakingStatus(address _reporter, uint _status) external;
+
+    function changeStakingStatus(address _reporter, uint256 _status) external;
+
     function slashReporter(address _reporter, address _disputer) external;
-    function getStakerInfo(address _staker) external view returns (uint256, uint256);
-    function getTimestampbyRequestIDandIndex(uint256 _requestId, uint256 _index) external view returns (uint256);
-    function getNewCurrentVariables()external view returns (bytes32 _c,uint256[5] memory _r,uint256 _d,uint256 _t);
-    function getNewValueCountbyQueryId(bytes32 _queryId) external view returns(uint256);
-    function getTimestampbyQueryIdandIndex(bytes32 _queryId, uint256 _index) external view returns(uint256);
-    function retrieveData(bytes32 _queryId, uint256 _timestamp) external view returns(bytes memory);
+
+    function getStakerInfo(address _staker)
+        external
+        view
+        returns (uint256, uint256);
+
+    function getTimestampbyRequestIDandIndex(uint256 _requestId, uint256 _index)
+        external
+        view
+        returns (uint256);
+
+    function getNewCurrentVariables()
+        external
+        view
+        returns (
+            bytes32 _c,
+            uint256[5] memory _r,
+            uint256 _d,
+            uint256 _t
+        );
+
+    function getNewValueCountbyQueryId(bytes32 _queryId)
+        external
+        view
+        returns (uint256);
+
+    function getTimestampbyQueryIdandIndex(bytes32 _queryId, uint256 _index)
+        external
+        view
+        returns (uint256);
+
+    function retrieveData(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (bytes memory);
+
     //Governance
-    enum VoteResult {FAILED,PASSED,INVALID}
+    enum VoteResult {
+        FAILED,
+        PASSED,
+        INVALID
+    }
+
     function setApprovedFunction(bytes4 _func, bool _val) external;
-    function beginDispute(bytes32 _queryId,uint256 _timestamp) external;
+
+    function beginDispute(bytes32 _queryId, uint256 _timestamp) external;
+
     function delegate(address _delegate) external;
-    function delegateOfAt(address _user, uint256 _blockNumber) external view returns (address);
+
+    function delegateOfAt(address _user, uint256 _blockNumber)
+        external
+        view
+        returns (address);
+
     function executeVote(uint256 _disputeId) external;
-    function proposeVote(address _contract,bytes4 _function, bytes calldata _data, uint256 _timestamp) external;
+
+    function proposeVote(
+        address _contract,
+        bytes4 _function,
+        bytes calldata _data,
+        uint256 _timestamp
+    ) external;
+
     function tallyVotes(uint256 _disputeId) external;
+
+    function governance() external view returns (address);
+
     function updateMinDisputeFee() external;
-    function verify() external pure returns(uint);
-    function vote(uint256 _disputeId, bool _supports, bool _invalidQuery) external;
-    function voteFor(address[] calldata _addys,uint256 _disputeId, bool _supports, bool _invalidQuery) external;
-    function getDelegateInfo(address _holder) external view returns(address,uint);
-    function isFunctionApproved(bytes4 _func) external view returns(bool);
-    function isApprovedGovernanceContract(address _contract) external returns (bool);
-    function getVoteRounds(bytes32 _hash) external view returns(uint256[] memory);
-    function getVoteCount() external view returns(uint256);
-    function getVoteInfo(uint256 _disputeId) external view returns(bytes32,uint256[9] memory,bool[2] memory,VoteResult,bytes memory,bytes4,address[2] memory);
-    function getDisputeInfo(uint256 _disputeId) external view returns(uint256,uint256,bytes memory, address);
-    function getOpenDisputesOnId(bytes32 _queryId) external view returns(uint256);
-    function didVote(uint256 _disputeId, address _voter) external view returns(bool);
+
+    function verify() external pure returns (uint256);
+
+    function vote(
+        uint256 _disputeId,
+        bool _supports,
+        bool _invalidQuery
+    ) external;
+
+    function voteFor(
+        address[] calldata _addys,
+        uint256 _disputeId,
+        bool _supports,
+        bool _invalidQuery
+    ) external;
+
+    function getDelegateInfo(address _holder)
+        external
+        view
+        returns (address, uint256);
+
+    function isFunctionApproved(bytes4 _func) external view returns (bool);
+
+    function isApprovedGovernanceContract(address _contract)
+        external
+        returns (bool);
+
+    function getVoteRounds(bytes32 _hash)
+        external
+        view
+        returns (uint256[] memory);
+
+    function getVoteCount() external view returns (uint256);
+
+    function getVoteInfo(uint256 _disputeId)
+        external
+        view
+        returns (
+            bytes32,
+            uint256[9] memory,
+            bool[2] memory,
+            VoteResult,
+            bytes memory,
+            bytes4,
+            address[2] memory
+        );
+
+    function getDisputeInfo(uint256 _disputeId)
+        external
+        view
+        returns (
+            uint256,
+            uint256,
+            bytes memory,
+            address
+        );
+
+    function getOpenDisputesOnId(bytes32 _queryId)
+        external
+        view
+        returns (uint256);
+
+    function didVote(uint256 _disputeId, address _voter)
+        external
+        view
+        returns (bool);
+
     //Oracle
-    function getReportTimestampByIndex(bytes32 _queryId, uint256 _index) external view returns(uint256);
-    function getValueByTimestamp(bytes32 _queryId, uint256 _timestamp) external view returns(bytes memory);
-    function getBlockNumberByTimestamp(bytes32 _queryId, uint256 _timestamp) external view returns(uint256);
-    function getReportingLock() external view returns(uint256);
-    function getReporterByTimestamp(bytes32 _queryId, uint256 _timestamp) external view returns(address);
-    function reportingLock() external view returns(uint256);
+    function getReportTimestampByIndex(bytes32 _queryId, uint256 _index)
+        external
+        view
+        returns (uint256);
+
+    function getValueByTimestamp(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (bytes memory);
+
+    function getBlockNumberByTimestamp(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (uint256);
+
+    function getReportingLock() external view returns (uint256);
+
+    function getReporterByTimestamp(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (address);
+
+    function reportingLock() external view returns (uint256);
+
     function removeValue(bytes32 _queryId, uint256 _timestamp) external;
-    function getReportsSubmittedByAddress(address _reporter) external view returns(uint256);
     function getTipsByUser(address _user) external view returns(uint256);
     function tipQuery(bytes32 _queryId, uint256 _tip, bytes memory _queryData) external;
     function submitValue(bytes32 _queryId, bytes calldata _value, uint256 _nonce, bytes memory _queryData) external;
     function burnTips() external;
+
     function changeReportingLock(uint256 _newReportingLock) external;
+    function getReportsSubmittedByAddress(address _reporter) external view returns(uint256);
     function changeTimeBasedReward(uint256 _newTimeBasedReward) external;
     function getReporterLastTimestamp(address _reporter) external view returns(uint256);
     function getTipsById(bytes32 _queryId) external view returns(uint256);
@@ -95,24 +296,189 @@ interface ITellor{
     function getCurrentValue(bytes32 _queryId) external view returns(bytes memory);
     function getDataBefore(bytes32 _queryId, uint256 _timestamp) external view returns(bool _ifRetrieve, bytes memory _value, uint256 _timestampRetrieved);
     function getTimeOfLastNewValue() external view returns(uint256);
-    function getIndexForDataBefore(bytes32 _queryId, uint256 _timestamp) external view returns (bool _found, uint256 _index);
-    //Treasury
-    function issueTreasury(uint256 _maxAmount, uint256 _rate, uint256 _duration) external;
-    function payTreasury(address _investor,uint256 _id) external;
-    function buyTreasury(uint256 _id,uint256 _amount) external;
-    function getTreasuryDetails(uint256 _id) external view returns(uint256,uint256,uint256,uint256);
-    function getTreasuryFundsByUser(address _user) external view returns(uint256);
-    function getTreasuryAccount(uint256 _id, address _investor) external view returns(uint256,uint256,bool);
-    function getTreasuryCount() external view returns(uint256);
-    function getTreasuryOwners(uint256 _id) external view returns(address[] memory);
-    function wasPaid(uint256 _id, address _investor) external view returns(bool);
+    function depositStake(uint256 _amount) external;
+    function requestStakingWithdraw(uint256 _amount) external;
+
     //Test functions
     function changeAddressVar(bytes32 _id, address _addy) external;
 
     //parachute functions
     function killContract() external;
-    function migrateFor(address _destination,uint256 _amount) external;
+
+    function migrateFor(address _destination, uint256 _amount) external;
+
     function rescue51PercentAttack(address _tokenHolder) external;
+
     function rescueBrokenDataReporting() external;
+
     function rescueFailedUpdate() external;
+
+    //Tellor 360
+    function addStakingRewards(uint256 _amount) external;
+
+    function _sliceUint(bytes memory _b)
+        external
+        pure
+        returns (uint256 _number);
+
+    function claimOneTimeTip(bytes32 _queryId, uint256[] memory _timestamps)
+        external;
+
+    function claimTip(
+        bytes32 _feedId,
+        bytes32 _queryId,
+        uint256[] memory _timestamps
+    ) external;
+
+    function fee() external view returns (uint256);
+
+    function feedsWithFunding(uint256) external view returns (bytes32);
+
+    function fundFeed(
+        bytes32 _feedId,
+        bytes32 _queryId,
+        uint256 _amount
+    ) external;
+
+    function getCurrentFeeds(bytes32 _queryId)
+        external
+        view
+        returns (bytes32[] memory);
+
+    function getCurrentTip(bytes32 _queryId) external view returns (uint256);
+
+    function getDataAfter(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (bytes memory _value, uint256 _timestampRetrieved);
+
+    function getDataFeed(bytes32 _feedId)
+        external
+        view
+        returns (Autopay.FeedDetails memory);
+
+    function getFundedFeeds() external view returns (bytes32[] memory);
+
+    function getFundedQueryIds() external view returns (bytes32[] memory);
+
+    function getIndexForDataAfter(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (bool _found, uint256 _index);
+
+    function getIndexForDataBefore(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (bool _found, uint256 _index);
+
+    function getMultipleValuesBefore(
+        bytes32 _queryId,
+        uint256 _timestamp,
+        uint256 _maxAge,
+        uint256 _maxCount
+    )
+        external
+        view
+        returns (uint256[] memory _values, uint256[] memory _timestamps);
+
+    function getPastTipByIndex(bytes32 _queryId, uint256 _index)
+        external
+        view
+        returns (Autopay.Tip memory);
+
+    function getPastTipCount(bytes32 _queryId) external view returns (uint256);
+
+    function getPastTips(bytes32 _queryId)
+        external
+        view
+        returns (Autopay.Tip[] memory);
+
+    function getQueryIdFromFeedId(bytes32 _feedId)
+        external
+        view
+        returns (bytes32);
+
+    function getRewardAmount(
+        bytes32 _feedId,
+        bytes32 _queryId,
+        uint256[] memory _timestamps
+    ) external view returns (uint256 _cumulativeReward);
+
+    function getRewardClaimedStatus(
+        bytes32 _feedId,
+        bytes32 _queryId,
+        uint256 _timestamp
+    ) external view returns (bool);
+
+    function getTipsByAddress(address _user) external view returns (uint256);
+
+    function isInDispute(bytes32 _queryId, uint256 _timestamp)
+        external
+        view
+        returns (bool);
+
+    function queryIdFromDataFeedId(bytes32) external view returns (bytes32);
+
+    function queryIdsWithFunding(uint256) external view returns (bytes32);
+
+    function queryIdsWithFundingIndex(bytes32) external view returns (uint256);
+
+    function setupDataFeed(
+        bytes32 _queryId,
+        uint256 _reward,
+        uint256 _startTime,
+        uint256 _interval,
+        uint256 _window,
+        uint256 _priceThreshold,
+        uint256 _rewardIncreasePerSecond,
+        bytes memory _queryData,
+        uint256 _amount
+    ) external;
+
+    function tellor() external view returns (address);
+
+    function tip(
+        bytes32 _queryId,
+        uint256 _amount,
+        bytes memory _queryData
+    ) external;
+
+    function tips(bytes32, uint256)
+        external
+        view
+        returns (uint256 amount, uint256 timestamp);
+
+    function token() external view returns (address);
+
+    function userTipsTotal(address) external view returns (uint256);
+
+    function valueFor(bytes32 _id)
+        external
+        view
+        returns (
+            int256 _value,
+            uint256 _timestamp,
+            uint256 _statusCode
+        );
+}
+
+interface Autopay {
+    struct FeedDetails {
+        uint256 reward;
+        uint256 balance;
+        uint256 startTime;
+        uint256 interval;
+        uint256 window;
+        uint256 priceThreshold;
+        uint256 rewardIncreasePerSecond;
+        uint256 feedsWithFundingIndex;
+    }
+
+    struct Tip {
+        uint256 amount;
+        uint256 timestamp;
+    }
+    function getStakeAmount() external view returns(uint256);
+    function stakeAmount() external view returns(uint256);
+    function token() external view returns(address);
 }
diff --git a/src/telliot_feeds/queries/catalog.py b/src/telliot_feeds/queries/catalog.py
index 2341f24d..fcfc235d 100644
--- a/src/telliot_feeds/queries/catalog.py
+++ b/src/telliot_feeds/queries/catalog.py
@@ -103,7 +103,7 @@ def find(
 
     def to_yaml(self) -> str:
         all_entries = self.find()
-        return yaml.dump(clamfig.serialize(all_entries), sort_keys=False)  # type: ignore
+        return yaml.dump(clamfig.serialize(all_entries), sort_keys=False)
 
     def to_markdown(self) -> str:
         lines = ["# TellorX Query Catalog", ""]


Test patch:
diff --git a/contracts/TellorFlex/testing/StakingToken.sol b/contracts/TellorFlex/testing/StakingToken.sol
deleted file mode 100644
index e178623f..00000000
--- a/contracts/TellorFlex/testing/StakingToken.sol
+++ /dev/null
@@ -1,12 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity 0.8.3;
-
-import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC20/ERC20.sol";
-
-contract StakingToken is ERC20 {
-  constructor() ERC20("Test Tellor Tribute", "TTRB") {}
-
-  function mint(address _to, uint256 _amount) public {
-    _mint(_to, _amount);
-  }
-}
diff --git a/tests/conftest.py b/tests/conftest.py
index ad0d156f..55663ccd 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -7,20 +7,22 @@
 from brownie import accounts
 from brownie import Autopay
 from brownie import chain
+from brownie import Governance
 from brownie import multicall as brownie_multicall
 from brownie import QueryDataStorage
-from brownie import StakingToken
 from brownie import TellorFlex
-from brownie import TellorFlex360
+from brownie import TellorPlayground
 from brownie import TellorXMasterMock
 from brownie import TellorXOracleMock
 from chained_accounts import ChainedAccount
 from chained_accounts import find_accounts
+from hexbytes import HexBytes
 from multicall import multicall
 from multicall.constants import MULTICALL2_ADDRESSES
 from multicall.constants import Network
 from telliot_core.apps.core import TelliotCore
 from telliot_core.apps.telliot_config import TelliotConfig
+from web3 import Web3
 
 from telliot_feeds.datasource import DataSource
 from telliot_feeds.dtypes.datapoint import datetime_now_utc
@@ -33,6 +35,20 @@ def shared_setup(module_isolation):
     pass
 
 
+@pytest.fixture(scope="module", autouse=True)
+def reset_chain(chain):
+    """Reset the chain to a clean state after each test module"""
+    chain.reset()
+
+
+@pytest.fixture(scope="function", autouse=True)
+def snapshot(chain):
+    """Take a snapshot of the chain before each test function and revert to it after the test completes"""
+    chain.snapshot()
+    yield
+    chain.revert()
+
+
 @pytest.fixture(scope="session")
 def event_loop():
     loop = asyncio.get_event_loop()
@@ -138,56 +154,72 @@ def local_node_cfg(chain_id: int):
     return cfg
 
 
-@pytest.fixture
-def mumbai_test_cfg(scope="function", autouse=True):
+@pytest.fixture(scope="function", autouse=True)
+def mumbai_test_cfg():
     return local_node_cfg(chain_id=80001)
 
 
-@pytest.fixture
-def rinkeby_test_cfg(scope="function", autouse=True):
+@pytest.fixture(scope="function", autouse=True)
+def rinkeby_test_cfg():
     return local_node_cfg(chain_id=4)
 
 
-@pytest.fixture
-def ropsten_test_cfg(scope="function", autouse=True):
+@pytest.fixture(scope="function", autouse=True)
+def ropsten_test_cfg():
     return local_node_cfg(chain_id=3)
 
 
-@pytest.fixture
-def goerli_test_cfg(scope="function", autouse=True):
+@pytest.fixture(scope="function", autouse=True)
+def goerli_test_cfg():
     return local_node_cfg(chain_id=5)
 
 
 @pytest.fixture(scope="function", autouse=True)
 def mock_token_contract():
     """mock token to use for staking"""
-    return accounts[0].deploy(StakingToken)
+    return accounts[0].deploy(TellorPlayground)
 
 
 @pytest.fixture(scope="function", autouse=True)
 def mock_flex_contract(mock_token_contract):
     """mock oracle(TellorFlex) contract to stake in"""
-    return accounts[0].deploy(TellorFlex, mock_token_contract.address, accounts[0], 10e18, 60)
+    return accounts[0].deploy(
+        TellorFlex,
+        mock_token_contract.address,
+        43200,  # 12 hours reporting lock
+        Web3.toWei(100, "ether"),  # $100 stake amount dollar target
+        Web3.toWei(15, "ether"),  # $15 staking token current price
+        Web3.toWei(10, "ether"),  # 10 TRB minimum stake amount
+        HexBytes(
+            "0x5c13cd9c97dbb98f2429c101a2a8150e6c7a0ddaff6124ee176a3a411067ded0"
+        ),  # TRB/USD staking token price query id
+    )
+
+
+@pytest.fixture(scope="function", autouse=True)
+def mock_gov_contract(mock_flex_contract, mock_token_contract):
+    return accounts[0].deploy(Governance, mock_flex_contract.address, mock_token_contract.address)
 
 
 @pytest.fixture(scope="function", autouse=True)
-def mock_autopay_contract(mock_flex_contract, mock_token_contract, query_data_storage_contract):
+def init_tellorflex(mock_flex_contract, mock_gov_contract):
+    mock_flex_contract.init(mock_gov_contract.address)
+
+
+@pytest.fixture(scope="function", autouse=True)
+def mock_autopay_contract(mock_flex_contract, query_data_storage_contract):
     """mock payments(Autopay) contract for tipping and claiming tips"""
     return accounts[0].deploy(
         Autopay,
         mock_flex_contract.address,
-        mock_token_contract.address,
         query_data_storage_contract.address,
-        # accounts[0],
         20,
     )
 
 
 @pytest.fixture(scope="function", autouse=True)
 def query_data_storage_contract():
-    return accounts[0].deploy(
-        QueryDataStorage,
-    )
+    return accounts[0].deploy(QueryDataStorage)
 
 
 @pytest.fixture
@@ -210,28 +242,14 @@ def multicall_contract():
     multicall.state_override_supported = lambda _: False
 
 
-@pytest.fixture(scope="function")
-def tellorflex_360_contract(mock_token_contract):
-    account_fake = accounts.add("023861e2ceee1ea600e43cbd203e9e01ea2ed059ee3326155453a1ed3b1113a9")
-    return account_fake.deploy(
-        TellorFlex360,
-        mock_token_contract.address,
-        1,
-        1,
-        1,
-        "0x5c13cd9c97dbb98f2429c101a2a8150e6c7a0ddaff6124ee176a3a411067ded0",
-    )
-
-
 @pytest_asyncio.fixture(scope="function")
-async def tellor_360(mumbai_test_cfg, tellorflex_360_contract, mock_autopay_contract, mock_token_contract):
+async def tellor_360(mumbai_test_cfg, mock_flex_contract, mock_autopay_contract, mock_token_contract):
     async with TelliotCore(config=mumbai_test_cfg) as core:
-        txn_kwargs = {"gas_limit": 3500000, "legacy_gas_price": 1}
         account = core.get_account()
 
         tellor360 = core.get_tellor360_contracts()
-        tellor360.oracle.address = tellorflex_360_contract.address
-        tellor360.oracle.abi = tellorflex_360_contract.abi
+        tellor360.oracle.address = mock_flex_contract.address
+        tellor360.oracle.abi = mock_flex_contract.abi
         tellor360.autopay.address = mock_autopay_contract.address
         tellor360.autopay.abi = mock_autopay_contract.abi
         tellor360.token.address = mock_token_contract.address
@@ -241,7 +259,7 @@ async def tellor_360(mumbai_test_cfg, tellorflex_360_contract, mock_autopay_cont
         tellor360.autopay.connect()
 
         # mint token and send to reporter address
-        mock_token_contract.mint(account.address, 100000e18)
+        mock_token_contract.faucet(account.address)
 
         # approve token to be spent by autopay contract
         mock_token_contract.approve(mock_autopay_contract.address, 100000e18, {"from": account.address})
@@ -249,9 +267,6 @@ async def tellor_360(mumbai_test_cfg, tellorflex_360_contract, mock_autopay_cont
         # send eth from brownie address to reporter address for txn fees
         accounts[1].transfer(account.address, "1 ether")
 
-        # init governance address
-        await tellor360.oracle.write("init", _governanceAddress=accounts[0].address, **txn_kwargs)
-
         return tellor360, account
 
 
@@ -282,7 +297,7 @@ async def tellor_flex_reporter(mumbai_test_cfg, mock_flex_contract, mock_autopay
             min_native_token_balance=0,
         )
         # mint token and send to reporter address
-        mock_token_contract.mint(account.address, 1000e18)
+        mock_token_contract.faucet(account.address)
 
         # send eth from brownie address to reporter address for txn fees
         accounts[1].transfer(account.address, "1 ether")
diff --git a/tests/reporters/test_360_reporter.py b/tests/reporters/test_360_reporter.py
index 54060c0e..5225ebf0 100644
--- a/tests/reporters/test_360_reporter.py
+++ b/tests/reporters/test_360_reporter.py
@@ -1,15 +1,18 @@
+import datetime
 import math
 import time
-from datetime import timedelta
 from unittest import mock
 from unittest.mock import AsyncMock
 from unittest.mock import patch
 
 import pytest
+from brownie import accounts
+from brownie import chain
 
 from telliot_feeds.feeds.eth_usd_feed import eth_usd_median_feed
 from telliot_feeds.feeds.matic_usd_feed import matic_usd_median_feed
 from telliot_feeds.feeds.snapshot_feed import snapshot_manual_feed
+from telliot_feeds.feeds.trb_usd_feed import trb_usd_median_feed
 from telliot_feeds.reporters.rewards.time_based_rewards import get_time_based_rewards
 from telliot_feeds.reporters.tellor_360 import Tellor360Reporter
 
@@ -19,7 +22,7 @@
 
 
 @pytest.mark.asyncio
-async def test_report(tellor_360, caplog, guaranteed_price_source):
+async def test_report(tellor_360, caplog, guaranteed_price_source, mock_flex_contract, mock_token_contract):
     """Test 360 reporter deposit and balance changes when stakeAmount changes"""
     contracts, account = tellor_360
     feed = eth_usd_median_feed
@@ -39,12 +42,23 @@ async def test_report(tellor_360, caplog, guaranteed_price_source):
     )
 
     await r.report_once()
-    assert r.staker_info.stake_balance == int(1e18)
+    assert r.staker_info.stake_balance == int(10e18)
     # report count before first submission
     assert r.staker_info.reports_count == 0
 
     # update stakeamount increase causes reporter to deposit more to keep reporting
-    await r.oracle.write("updateStakeAmount", _amount=int(20e18), **txn_kwargs)
+    mock_token_contract.faucet(accounts[0].address)
+    mock_token_contract.approve(mock_flex_contract.address, mock_flex_contract.stakeAmount())
+    mock_flex_contract.depositStake(mock_flex_contract.stakeAmount())
+    mock_flex_contract.submitValue(
+        trb_usd_median_feed.query.query_id,
+        "0x0000000000000000000000000000000000000000000000004563918244f40000",
+        0,
+        trb_usd_median_feed.query.query_data,
+    )
+    chain.sleep(86400)
+    mock_flex_contract.updateStakeAmount()
+    chain.mine(1, timedelta=1)
     stake_amount, status = await r.oracle.read("getStakeAmount")
     assert status.ok
     assert stake_amount == int(20e18)
@@ -56,7 +70,17 @@ async def test_report(tellor_360, caplog, guaranteed_price_source):
     # report count before second report
     assert r.staker_info.reports_count == 1
     # decrease stakeAmount should increase reporting frequency
-    await r.oracle.write("updateStakeAmount", _amount=int(10e18), **txn_kwargs)
+    mock_token_contract.approve(mock_flex_contract.address, mock_flex_contract.stakeAmount())
+    mock_flex_contract.depositStake(mock_flex_contract.stakeAmount())
+    mock_flex_contract.submitValue(
+        trb_usd_median_feed.query.query_id,
+        "0x00000000000000000000000000000000000000000000021e19e0c9bab2400000",
+        0,
+        trb_usd_median_feed.query.query_data,
+    )
+    chain.sleep(86400)
+    mock_flex_contract.updateStakeAmount()
+
     stake_amount, status = await r.oracle.read("getStakeAmount")
     assert status.ok
     assert stake_amount == int(10e18)
@@ -68,7 +92,7 @@ async def test_report(tellor_360, caplog, guaranteed_price_source):
 
 
 @pytest.mark.asyncio
-async def test_fail_get_account_nonce(tellor_360, caplog, guaranteed_price_source, monkeypatch):
+async def test_fail_get_account_nonce(tellor_360, caplog, guaranteed_price_source):
     """Test 360 reporter fails to retrieve account nonce"""
     contracts, account = tellor_360
     feed = eth_usd_median_feed
@@ -164,7 +188,7 @@ async def test_adding_stake(tellor_360, guaranteed_price_source):
     # check stake amount
     stake_amount, status = await reporter.oracle.read("getStakeAmount")
     assert status.ok
-    assert stake_amount == int(1e18), "Should be 1e18"
+    assert stake_amount == int(10e18), "Should be 10e18"
 
     # check default stake value
     assert reporter.stake == 0
@@ -172,14 +196,14 @@ async def test_adding_stake(tellor_360, guaranteed_price_source):
     # first should deposits default stake
     _, status = await reporter.report_once()
     assert status.ok
-    assert reporter.staker_info.stake_balance == int(1e18), "Staker balance should be 1e18"
+    assert reporter.staker_info.stake_balance == int(10e18), "Staker balance should be 10e18"
 
     # stake more by by changing stake from default similar to how a stake amount chosen in cli
     # high stake to bypass reporter lock
-    reporter = Tellor360Reporter(**reporter_kwargs, stake=90000)
+    reporter = Tellor360Reporter(**reporter_kwargs, stake=900000)
     _, status = await reporter.report_once()
     assert status.ok
-    assert reporter.staker_info.stake_balance == pytest.approx(90000e18), "Staker balance should be 90000e18"
+    assert reporter.staker_info.stake_balance == pytest.approx(900000e18), "Staker balance should be 90000e18"
 
 
 @pytest.mark.asyncio
@@ -247,7 +271,7 @@ async def mock_get_feed_and_tip(*args, **kwargs):
         reporter_lock = 43200 / math.floor(reporter.staker_info.stake_balance / reporter.stake_amount)
         time_remaining = round(reporter.staker_info.last_report + reporter_lock - time.time())
         if time_remaining > 0:
-            hr_min_sec = str(timedelta(seconds=time_remaining))
+            hr_min_sec = str(datetime.timedelta(seconds=time_remaining))
         assert f"Currently in reporter lock. Time left: {hr_min_sec}" in caplog.text
 
 
diff --git a/tests/reporters/test_custom_360_reporter.py b/tests/reporters/test_custom_360_reporter.py
index 7fa1912a..6852d6ca 100644
--- a/tests/reporters/test_custom_360_reporter.py
+++ b/tests/reporters/test_custom_360_reporter.py
@@ -36,11 +36,11 @@
 
 
 @pytest.fixture(scope="function")
-def mock_reporter_contract(tellorflex_360_contract, mock_token_contract, mock_autopay_contract):
+def mock_reporter_contract(mock_flex_contract, mock_token_contract, mock_autopay_contract):
     """mock custom reporter contract"""
     return account_fake.deploy(
         SampleFlexReporter,
-        tellorflex_360_contract.address,
+        mock_flex_contract.address,
         mock_autopay_contract.address,
         mock_token_contract.address,
         1,
@@ -50,7 +50,7 @@ def mock_reporter_contract(tellorflex_360_contract, mock_token_contract, mock_au
 @pytest_asyncio.fixture(scope="function")
 async def custom_reporter(
     mumbai_test_cfg,
-    tellorflex_360_contract,
+    mock_flex_contract,
     mock_autopay_contract,
     mock_token_contract,
     mock_reporter_contract,
@@ -58,8 +58,8 @@ async def custom_reporter(
 ):
     async with TelliotCore(config=mumbai_test_cfg) as core:
         contracts = core.get_tellor360_contracts()
-        contracts.oracle.abi = tellorflex_360_contract.abi
-        contracts.oracle.address = tellorflex_360_contract.address
+        contracts.oracle.abi = mock_flex_contract.abi
+        contracts.oracle.address = mock_flex_contract.address
         contracts.autopay.address = mock_autopay_contract.address
         contracts.autopay.abi = mock_autopay_contract.abi
         contracts.token.address = mock_token_contract.address
@@ -95,9 +95,9 @@ def mock_confirm(*args, **kwargs):
                 min_native_token_balance=0,
             )
             # mint token and send to reporter address
-            mock_token_contract.mint(account.address, 1000e18)
-            mock_token_contract.mint(accounts[0].address, 1000e18)
-            mock_token_contract.mint(mock_reporter_contract.address, 100e18)
+            mock_token_contract.faucet(account.address)
+            mock_token_contract.faucet(accounts[0].address)
+            mock_token_contract.faucet(mock_reporter_contract.address)
             mock_token_contract.approve(mock_autopay_contract.address, 10e18)
 
             mock_autopay_contract.tip(
@@ -106,12 +106,10 @@ def mock_confirm(*args, **kwargs):
                 "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000953706f745072696365000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003646169000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037573640000000000000000000000000000000000000000000000000000000000",  # noqa: E501
             )
             # send eth from brownie address to reporter address for txn fees
-            accounts[1].transfer(account.address, "1 ether")
-            accounts[1].transfer(contracts.oracle.account.address, "1 ether")
-            # init governance address
-            await contracts.oracle.write(
-                "init", _governanceAddress=accounts[0].address, gas_limit=350000, legacy_gas_price=1
-            )
+            accounts[1].transfer(account.address, "10 ether")
+
+            mock_token_contract.approve(mock_flex_contract.address, 10e18, {"from": account_fake})
+            mock_flex_contract.depositStake(10e18, {"from": account_fake})
 
             return r
 
diff --git a/tests/reporters/test_evm_call.py b/tests/reporters/test_evm_call.py
index 1c0f3498..2b525226 100644
--- a/tests/reporters/test_evm_call.py
+++ b/tests/reporters/test_evm_call.py
@@ -205,7 +205,7 @@ async def test_non_view_evm_call(tellor_360, caplog):
     negative could happen
     """
     contracts, account = tellor_360
-    signature = Web3.sha3(text="depositStake(uint256)")[:4].hex()
+    signature = Web3.keccak(text="updateStakeAmount()")[:4].hex()
     _amount = Web3.toHex(10)[2:].zfill(64)
     non_view_call_data = HexBytes(signature + _amount)
 
diff --git a/tests/test_autopay.py b/tests/test_autopay.py
index a9524cb3..c0358647 100644
--- a/tests/test_autopay.py
+++ b/tests/test_autopay.py
@@ -15,7 +15,12 @@
 
 @pytest.mark.asyncio
 async def test_main(
-    mumbai_test_cfg, mock_flex_contract, mock_autopay_contract, mock_token_contract, multicall_contract
+    mumbai_test_cfg,
+    mock_flex_contract,
+    mock_autopay_contract,
+    mock_token_contract,
+    multicall_contract,
+    mock_gov_contract,
 ):
     async with TelliotCore(config=mumbai_test_cfg) as core:
 
@@ -33,8 +38,7 @@ async def test_main(
         flex.autopay.connect()
 
         # mint token and send to reporter address
-        mock_token_contract.mint(account.address, 1000e18)
-        assert mock_token_contract.balanceOf(account.address) == 1000e18
+        mock_token_contract.faucet(account.address)
 
         # send eth from brownie address to reporter address for txn fees
         accounts[0].transfer(account.address, "1 ether")
@@ -42,7 +46,7 @@ async def test_main(
 
         # check governance address is brownie address
         governance_address = await flex.oracle.get_governance_address()
-        assert governance_address == accounts[0]
+        assert governance_address == mock_gov_contract.address
 
         # check stake amount is ten
         stake_amount = await flex.oracle.get_stake_amount()
diff --git a/tests/test_bct_usd.py b/tests/test_bct_usd.py
index 482686b0..9dc81d0c 100644
--- a/tests/test_bct_usd.py
+++ b/tests/test_bct_usd.py
@@ -26,7 +26,7 @@ async def test_bct_usd_reporter_submit_once(
         flex.autopay.connect()
 
         # mint token and send to reporter address
-        mock_token_contract.mint(account.address, 1000e18)
+        mock_token_contract.faucet(account.address)
 
         # send eth from brownie address to reporter address for txn fees
         accounts[1].transfer(account.address, "1 ether")
diff --git a/tests/test_dai_usd.py b/tests/test_dai_usd.py
index 97d58b42..9b23d2f9 100644
--- a/tests/test_dai_usd.py
+++ b/tests/test_dai_usd.py
@@ -26,7 +26,7 @@ async def test_dai_usd_reporter_submit_once(
         flex.autopay.connect()
 
         # mint token and send to reporter address
-        mock_token_contract.mint(account.address, 1000e18)
+        mock_token_contract.faucet(account.address)
 
         # send eth from brownie address to reporter address for txn fees
         accounts[2].transfer(account.address, "1 ether")
diff --git a/tests/test_numeric_api_response_feed.py b/tests/test_numeric_api_response_feed.py
index ab649ef2..9b1088ca 100644
--- a/tests/test_numeric_api_response_feed.py
+++ b/tests/test_numeric_api_response_feed.py
@@ -35,7 +35,7 @@ async def test_api_reporter_submit_once(
         flex.autopay.connect()
 
         # mint token and send to reporter address
-        mock_token_contract.mint(account.address, 1000e18)
+        mock_token_contract.faucet(account.address)
 
         # send eth from brownie address to reporter address for txn fees
         accounts[1].transfer(account.address, "1 ether")
diff --git a/tests/tips/conftest.py b/tests/tips/conftest.py
index 25cd2001..05dc45b4 100644
--- a/tests/tips/conftest.py
+++ b/tests/tips/conftest.py
@@ -12,13 +12,10 @@
 
 
 @pytest.fixture(scope="function")
-async def autopay_contract_setup(
-    mumbai_test_cfg, tellorflex_360_contract, mock_token_contract, query_data_storage_contract
-):
+async def autopay_contract_setup(mumbai_test_cfg, mock_flex_contract, mock_token_contract, query_data_storage_contract):
     autopay_contract = accounts[0].deploy(
         Autopay,
-        tellorflex_360_contract.address,
-        mock_token_contract.address,
+        mock_flex_contract.address,
         query_data_storage_contract.address,
         20,
     )
@@ -28,8 +25,8 @@ async def autopay_contract_setup(
         account = core.get_account()
 
         flex = core.get_tellor360_contracts()
-        flex.oracle.address = tellorflex_360_contract.address
-        flex.oracle.abi = tellorflex_360_contract.abi
+        flex.oracle.address = mock_flex_contract.address
+        flex.oracle.abi = mock_flex_contract.abi
         flex.autopay.address = autopay_contract.address
         flex.autopay.abi = autopay_contract.abi
         flex.token.address = mock_token_contract.address
@@ -39,15 +36,13 @@ async def autopay_contract_setup(
         flex.autopay.connect()
 
         # mint token and send to reporter address
-        mock_token_contract.mint(account.address, 100000e18)
+        mock_token_contract.faucet(account.address)
 
         # send eth from brownie address to reporter address for txn fees
         accounts[1].transfer(account.address, "1 ether")
-        # init governance address
-        await flex.oracle.write("init", _governanceAddress=accounts[0].address, **txn_kwargs)
 
         # approve token to be spent by oracle
-        mock_token_contract.approve(tellorflex_360_contract.address, 100000e18, {"from": account.address})
+        mock_token_contract.approve(mock_flex_contract.address, 100000e18, {"from": account.address})
 
         _, status = await flex.oracle.write("depositStake", gas_limit=350000, legacy_gas_price=1, _amount=int(10e18))
         # check txn is successful
diff --git a/tests/tips/test_tips_available_after_submission.py b/tests/tips/test_tips_available_after_submission.py
index 1770f340..397e4c47 100644
--- a/tests/tips/test_tips_available_after_submission.py
+++ b/tests/tips/test_tips_available_after_submission.py
@@ -64,6 +64,7 @@ async def test_feed_suggestion(autopay_contract_setup, caplog):
     query, tip_amount = await get_feed_and_tip(autopay=flex.autopay, current_timestamp=chain.time() + 5)
     assert query.get_state()["query"] == {"type": "SpotPrice", "asset": "albt", "currency": "usd"}
     assert tip_amount == int(0.5 * 1e18)
+    chain.sleep(43200)
     # Second in window submission which should exhaust funds in feed
     _, status = await flex.oracle.write(
         "submitValue",
diff --git a/tests/tips/test_tips_disputed_submissions.py b/tests/tips/test_tips_disputed_submissions.py
new file mode 100644
index 00000000..c8ed03f0
--- /dev/null
+++ b/tests/tips/test_tips_disputed_submissions.py
@@ -0,0 +1,132 @@
+import pytest
+from brownie import accounts
+from brownie import Autopay
+from brownie import chain
+from brownie import TellorPlayground
+from telliot_core.tellor.tellor360.autopay import Tellor360AutopayContract
+
+from telliot_feeds.feeds import matic_usd_median_feed
+from telliot_feeds.reporters.tips.suggest_datafeed import get_feed_and_tip
+
+
+# deploy new contracts
+@pytest.fixture(scope="function")
+def deploy(query_data_storage_contract):
+    # deploy playground
+    playground = accounts[0].deploy(TellorPlayground)
+    # deploy autopay
+    autopay = accounts[0].deploy(Autopay, playground.address, query_data_storage_contract.address, 20)
+    return playground, autopay
+
+
+@pytest.mark.asyncio
+async def test_tips_disputed_submissions(deploy, mumbai_test_cfg):
+    """Test to check if tipped query ids are suggested by tip listener after a tip eligible submission is disputed
+
+    - Tip a query id
+    - Check if the query id is suggested by tip listener
+    - Submit a value for the query id
+    - Check that the query id is not suggested by tip listener
+    - Dispute the reported value
+    - Check that the query id is suggested by tip listener
+    """
+    query_id = matic_usd_median_feed.query.query_id.hex()
+    query_data = matic_usd_median_feed.query.query_data.hex()
+
+    playground, autopay = deploy
+    node = mumbai_test_cfg.get_endpoint()
+    node.connect()
+    txn = playground.submitValue(query_id, int.to_bytes(100, 32, "big"), 0, query_data, {"from": accounts[0]})
+    assert txn.status == 1
+    chain.mine(timedelta=1)
+    # mint tokens
+    txn = playground.faucet(accounts[0].address, {"from": accounts[0]})
+    assert txn.status == 1
+    # approve autopay to spend tokens
+    txn = playground.approve(autopay.address, int(10e18), {"from": accounts[0]})
+    assert txn.status == 1
+    # tip query
+    txn = autopay.tip(query_id, int(10e18), query_data, {"from": accounts[0]})
+    assert txn.status == 1
+    # assemble tellor autopay contract object
+    tellor_autopay = Tellor360AutopayContract(node)
+    tellor_autopay.address = autopay.address
+    tellor_autopay.abi = autopay.abi
+    tellor_autopay.connect()
+    # get feed and tip, should not be none since we just tipped
+    datafeed, tip = await get_feed_and_tip(tellor_autopay, current_timestamp=chain.time())
+    assert datafeed is not None
+    assert tip is not None
+    # submit a value
+    playground.submitValue(query_id, int.to_bytes(100, 32, "big"), 0, query_data, {"from": accounts[0]})
+    # tip should be none since we just submitted a value
+    datafeed, tip = await get_feed_and_tip(tellor_autopay, current_timestamp=chain.time())
+    assert datafeed is None
+    assert tip is None
+    chain.mine(timedelta=1)
+    playground.beginDispute(query_id, chain.time(), {"from": accounts[0]})
+    # approve autopay to spend tokens
+    txn = playground.approve(autopay.address, int(10e18), {"from": accounts[0]})
+    assert txn.status == 1
+    # tip query
+    txn = autopay.tip(query_id, int(10e18), query_data, {"from": accounts[0]})
+    assert txn.status == 1
+    # submit a value
+    playground.submitValue(query_id, int.to_bytes(100, 32, "big"), 0, query_data, {"from": accounts[0]})
+    assert autopay.getCurrentTip(query_id) == 0
+    datafeed, tip = await get_feed_and_tip(tellor_autopay, current_timestamp=chain.time())
+    assert datafeed is None
+    assert tip is None
+    playground.beginDispute(query_id, chain.time(), {"from": accounts[0]})
+    # get feed and tip, should not be none since previous report was disputed
+    chain.mine(timedelta=1)
+    datafeed, tip = await get_feed_and_tip(tellor_autopay, current_timestamp=chain.time())
+    assert datafeed is not None
+    assert tip > 0
+    # submit a value
+    time = chain.time()
+    playground.submitValue(query_id, int.to_bytes(100, 32, "big"), 0, query_data, {"from": accounts[0]})
+    assert autopay.getCurrentTip(query_id) == 0
+    playground.beginDispute(query_id, time, {"from": accounts[0]})
+    datafeed, tip = await get_feed_and_tip(tellor_autopay, current_timestamp=chain.time())
+    assert datafeed is not None
+    assert tip > 0
+    # claimOneTimeTip
+    # advance time to bypass buffer
+    chain.sleep(86400)
+    txn = playground.submitValue(query_id, int.to_bytes(100, 32, "big"), 0, query_data, {"from": accounts[0]})
+    assert txn.status == 1
+    chain.mine(timedelta=1)
+    txn = playground.approve(autopay.address, int(10e18), {"from": accounts[0]})
+    assert txn.status == 1
+    txn = autopay.setupDataFeed(
+        query_id,
+        int(1e18),  # reward
+        chain.time(),  # start time
+        3600,  # interval
+        600,  # window
+        0,  # price threshold
+        0,  # reward increase per second
+        query_data,
+        int(10e18),
+        {"from": accounts[0]},
+    )
+    assert txn.status == 1
+    chain.mine(timedelta=1)
+    datafeed, tip = await get_feed_and_tip(tellor_autopay, current_timestamp=chain.time())
+    assert datafeed is not None
+    assert tip > 0
+    # submit a value
+    time = chain.time()
+    playground.submitValue(query_id, int.to_bytes(100, 32, "big"), 0, query_data, {"from": accounts[0]})
+    chain.mine(timedelta=1)
+    datafeed, tip = await get_feed_and_tip(tellor_autopay, current_timestamp=chain.time())
+    assert datafeed is None
+    assert tip is None
+    # dispute value
+    playground.beginDispute(query_id, time, {"from": accounts[0]})
+    chain.mine(timedelta=1)
+
+    datafeed, tip = await get_feed_and_tip(tellor_autopay, current_timestamp=chain.time())
+    assert datafeed is not None
+    assert tip > 0