Sonic Blaze Testnet

Contract Diff Checker

Contract Name:
Assessment

Contract Source Code:

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/cryptography/MerkleProof.sol)

pragma solidity ^0.8.0;

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * The proofs can be generated using the JavaScript library
 * https://github.com/miguelmota/merkletreejs[merkletreejs].
 * Note: the hashing algorithm should be keccak256 and pair sorting should be enabled.
 *
 * See `test/utils/cryptography/MerkleProof.test.js` for some examples.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the merkle tree could be reinterpreted as a leaf value.
 */
library MerkleProof {
    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     */
    function verify(
        bytes32[] memory proof,
        bytes32 root,
        bytes32 leaf
    ) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Calldata version of {verify}
     *
     * _Available since v4.7._
     */
    function verifyCalldata(
        bytes32[] calldata proof,
        bytes32 root,
        bytes32 leaf
    ) internal pure returns (bool) {
        return processProofCalldata(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leafs & pre-images are assumed to be sorted.
     *
     * _Available since v4.4._
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Calldata version of {processProof}
     *
     * _Available since v4.7._
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if the `leaves` can be proved to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * _Available since v4.7._
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProof(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Calldata version of {multiProofVerify}
     *
     * _Available since v4.7._
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and the sibling nodes in `proof`,
     * consuming from one or the other at each step according to the instructions given by
     * `proofFlags`.
     *
     * _Available since v4.7._
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            return hashes[totalHashes - 1];
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Calldata version of {processMultiProof}
     *
     * _Available since v4.7._
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            return hashes[totalHashes - 1];
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
    }

    function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, a)
            mstore(0x20, b)
            value := keccak256(0x00, 0x40)
        }
    }
}

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.18;

import "../interfaces/ISAFURAMaster.sol";
import "../interfaces/IMasterAwareV2.sol";
import "../interfaces/IMemberRoles.sol";

abstract contract MasterAwareV2 is IMasterAwareV2 {

  ISAFURAMaster public master;

  mapping(uint => address payable) public internalContracts;

  modifier onlyMember {
    require(
      IMemberRoles(internalContracts[uint(ID.MR)]).checkRole(
        msg.sender,
        uint(IMemberRoles.Role.Member)
      ),
      "Caller is not a member"
    );
    _;
  }

  modifier onlyAdvisoryBoard {
    require(
      IMemberRoles(internalContracts[uint(ID.MR)]).checkRole(
        msg.sender,
        uint(IMemberRoles.Role.AdvisoryBoard)
      ),
      "Caller is not an advisory board member"
    );
    _;
  }

  modifier onlyInternal {
    require(master.isInternal(msg.sender), "Caller is not an internal contract");
    _;
  }

  modifier onlyMaster {
    if (address(master) != address(0)) {
      require(address(master) == msg.sender, "Not master");
    }
    _;
  }

  modifier onlyGovernance {
    require(
      master.checkIsAuthToGoverned(msg.sender),
      "Caller is not authorized to govern"
    );
    _;
  }

  modifier onlyEmergencyAdmin {
    require(
      msg.sender == master.emergencyAdmin(),
      "Caller is not emergency admin"
    );
    _;
  }

  modifier whenPaused {
    require(master.isPause(), "System is not paused");
    _;
  }

  modifier whenNotPaused {
    require(!master.isPause(), "System is paused");
    _;
  }

  function getInternalContractAddress(ID id) internal view returns (address payable) {
    return internalContracts[uint(id)];
  }

  function changeMasterAddress(address masterAddress) public onlyMaster {
    master = ISAFURAMaster(masterAddress);
  }

}

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity >=0.5.0;

interface IAssessment {

  /* ========== DATA STRUCTURES ========== */

  enum UintParams {
    minVotingPeriodInDays,
    stakeLockupPeriodInDays,
    payoutCooldownInDays,
    silentEndingPeriodInDays
  }

  struct Configuration {
    // The minimum number of days the users can vote on polls
    uint8 minVotingPeriodInDays;

    // Number of days the users must wait from their last vote to withdraw their stake.
    uint8 stakeLockupPeriodInDays;

    // Number of days the users must wait after a poll closes to redeem payouts.
    uint8 payoutCooldownInDays;

    // Number of days representing the silence period. It is used to extend a poll's end date when
    // a vote is cast during the silence period before the end date.
    uint8 silentEndingPeriodInDays;
  }

  struct Stake {
    uint96 amount;
    uint104 rewardsWithdrawableFromIndex;
    uint16 fraudCount;
    /*uint32 unused,*/
  }

  // Holds data for a vote belonging to an assessor.
  //
  // The structure is used to keep track of user's votes. Each vote is used to determine
  // a user's share of rewards or to create a fraud resolution which excludes fraudulent votes
  // from the initial poll.
  struct Vote {
    // Identifier of the claim or incident
    uint80 assessmentId;

    // If the assessor votes to accept the event it's true otherwise it's false
    bool accepted;

    // Date and time when the vote was cast
    uint32 timestamp;

    // How many tokens were staked when the vote was cast
    uint96 stakedAmount;
  }

  // Holds poll results for an assessment.
  //
  // The structure is used to keep track of all votes on a given assessment such as how many NXM were
  // used to cast accept and deny votes as well as when the poll started and when it ends.
  struct Poll {
    // The amount of NXM from accept votes
    uint96 accepted;

    // The amount of NXM from deny votes
    uint96 denied;

    // Timestamp of when the poll started.
    uint32 start;

    // Timestamp of when the poll ends.
    uint32 end;
  }

  // Holds data for an assessment belonging to an assessable event (individual claims, yield token
  // incidents etc.).
  //
  // The structure is used to keep track of the total reward that should be distributed to
  // assessors, the assessment deposit the claimants made to start the assessment, and the poll
  // coresponding to this assessment.
  struct Assessment {
    // See Poll struct
    Poll poll;

    // The amount of NXM representing the assessment reward which is split among those who voted.
    uint128 totalRewardInNXM;

    // An amount of ETH which is sent back to the claimant when the poll result is positive,
    // otherwise it is kep it the pool to back the assessment rewards. This allows claimants to
    // open an unlimited amount of claims and prevents unbacked NXM to be minted through the
    // assessment process.
    uint128 assessmentDepositInETH;
  }

  /* ========== VIEWS ========== */

  function getAssessmentsCount() external view returns (uint);

  function assessments(uint id) external view
  returns (Poll memory poll, uint128 totalReward, uint128 assessmentDeposit);

  function getPoll(uint assessmentId) external view returns (Poll memory);

  function getRewards(address user) external view returns (
    uint totalPendingAmount,
    uint withdrawableAmount,
    uint withdrawableUntilIndex
  );

  function getVoteCountOfAssessor(address assessor) external view returns (uint);

  function votesOf(address user, uint id) external view
  returns (uint80 assessmentId, bool accepted, uint32 timestamp, uint96 stakedAmount);

  function stakeOf(address user) external view
  returns (uint96 amount, uint104 rewardsWithdrawableFromIndex, uint16 fraudCount);

  function config() external view returns (
    uint8 minVotingPeriodInDays,
    uint8 stakeLockupPeriodInDays,
    uint8 payoutCooldownInDays,
    uint8 silentEndingPeriodInDays
  );

  function hasAlreadyVotedOn(address voter, uint pollId) external view returns (bool);


  /* === MUTATIVE FUNCTIONS ==== */

  function stake(uint96 amount) external;

  function unstake(uint96 amount, address to) external;

  function withdrawRewards(
    address user,
    uint104 batchSize
  ) external returns (uint withdrawn, uint withdrawnUntilIndex);

  function withdrawRewardsTo(
    address destination,
    uint104 batchSize
  ) external returns (uint withdrawn, uint withdrawnUntilIndex);

  function startAssessment(uint totalReward, uint assessmentDeposit) external
  returns (uint);

  function castVotes(
    uint[] calldata assessmentIds,
    bool[] calldata votes,
    string[] calldata ipfsAssessmentDataHashes,
    uint96 stakeIncrease
  ) external;

  function submitFraud(bytes32 root) external;

  function processFraud(
    uint256 rootIndex,
    bytes32[] calldata proof,
    address assessor,
    uint256 lastFraudulentVoteIndex,
    uint96 burnAmount,
    uint16 fraudCount,
    uint256 voteBatchSize
  ) external;

  function updateUintParameters(UintParams[] calldata paramNames, uint[] calldata values) external;

  /* ========== EVENTS ========== */

  event StakeDeposited(address user, uint104 amount);
  event StakeWithdrawn(address indexed user, address to, uint96 amount);
  event VoteCast(address indexed user, uint256 assessmentId, uint96 stakedAmount, bool accepted, string ipfsAssessmentDataHash);
  event RewardWithdrawn(address user, address to, uint256 amount);
  event FraudProcessed(uint assessmentId, address assessor, Poll poll);
  event FraudSubmitted(bytes32 root);

}

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity >=0.5.0;

interface IMasterAwareV2 {

  // TODO: if you update this enum, update lib/constants.js as well
  enum ID {
    TC, // TokenController.sol
    P1, // Pool.sol
    MR, // MemberRoles.sol
    MC, // MCR.sol
    CO, // Cover.sol
    SP, // StakingProducts.sol
    PS, // LegacyPooledStaking.sol
    GV, // Governance.sol
    GW, // LegacyGateway.sol - removed
    CL, // CoverMigrator.sol - removed
    AS, // Assessment.sol
    CI, // IndividualClaims.sol - Claims for Individuals
    CG, // YieldTokenIncidents.sol - Claims for Groups
    RA, // Ramm.sol
    ST,  // SafeTracker.sol
    CP  // CoverProducts.sol
  }

  function changeMasterAddress(address masterAddress) external;

  function changeDependentContractAddress() external;

  function internalContracts(uint) external view returns (address payable);
}

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity >=0.5.0;

interface IMemberRoles {

  enum Role {Unassigned, AdvisoryBoard, Member, Owner, Auditor}

  function join(address _userAddress, uint nonce, bytes calldata signature) external payable;

  function switchMembership(address _newAddress) external;

  function switchMembershipAndAssets(
    address newAddress,
    uint[] calldata coverIds,
    uint[] calldata stakingTokenIds
  ) external;

  function switchMembershipOf(address member, address _newAddress) external;

  function totalRoles() external view returns (uint256);

  function changeAuthorized(uint _roleId, address _newAuthorized) external;

  function setKycAuthAddress(address _add) external;

  function members(uint _memberRoleId) external view returns (uint, address[] memory memberArray);

  function numberOfMembers(uint _memberRoleId) external view returns (uint);

  function authorized(uint _memberRoleId) external view returns (address);

  function roles(address _memberAddress) external view returns (uint[] memory);

  function checkRole(address _memberAddress, uint _roleId) external view returns (bool);

  function getMemberLengthForAllRoles() external view returns (uint[] memory totalMembers);

  function memberAtIndex(uint _memberRoleId, uint index) external view returns (address, bool);

  function membersLength(uint _memberRoleId) external view returns (uint);

  event MemberRole(uint256 indexed roleId, bytes32 roleName, string roleDescription);

  event MemberJoined(address indexed newMember, uint indexed nonce);

  event switchedMembership(address indexed previousMember, address indexed newMember, uint timeStamp);
  
  event MembershipWithdrawn(address indexed member, uint timestamp);
}

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity >=0.5.0;

import "./IPriceFeedOracle.sol";

struct SwapDetails {
  uint104 minAmount;
  uint104 maxAmount;
  uint32 lastSwapTime;
  // 2 decimals of precision. 0.01% -> 0.0001 -> 1e14
  uint16 maxSlippageRatio;
}

struct Asset {
  address assetAddress;
  bool isCoverAsset;
  bool isAbandoned;
}

interface IPool {

  function swapOperator() external view returns (address);

  function getAsset(uint assetId) external view returns (Asset memory);

  function getAssets() external view returns (Asset[] memory);

  function transferAssetToSwapOperator(address asset, uint amount) external;

  function setSwapDetailsLastSwapTime(address asset, uint32 lastSwapTime) external;

  function getAssetSwapDetails(address assetAddress) external view returns (SwapDetails memory);

  function sendPayout(uint assetIndex, address payable payoutAddress, uint amount, uint ethDepositAmount) external;

  function sendEth(address payoutAddress, uint amount) external;

  function upgradeCapitalPool(address payable newPoolAddress) external;

  function priceFeedOracle() external view returns (IPriceFeedOracle);

  function getPoolValueInEth() external view returns (uint);

  function calculateMCRRatio(uint totalAssetValue, uint mcrEth) external pure returns (uint);

  function getInternalTokenPriceInAsset(uint assetId) external view returns (uint tokenPrice);

  function getInternalTokenPriceInAssetAndUpdateTwap(uint assetId) external returns (uint tokenPrice);

  function getTokenPrice() external view returns (uint tokenPrice);

  function getMCRRatio() external view returns (uint);

  function setSwapValue(uint value) external;
}

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity >=0.5.0;

interface Aggregator {
  function decimals() external view returns (uint8);
  function latestAnswer() external view returns (int);
}

interface IPriceFeedOracle {

  struct OracleAsset {
    Aggregator aggregator;
    uint8 decimals;
  }

  function ETH() external view returns (address);
  function assets(address) external view returns (Aggregator, uint8);

  function getAssetToEthRate(address asset) external view returns (uint);
  function getAssetForEth(address asset, uint ethIn) external view returns (uint);
  function getEthForAsset(address asset, uint amount) external view returns (uint);

}

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity >=0.5.0;

import "./IPool.sol";
import "./ISAFURAToken.sol";
import "./ITokenController.sol";

interface IRamm {

  // storage structs

  struct Slot0 {
    uint128 nxmReserveA;
    uint128 nxmReserveB;
  }

  struct Slot1 {
    uint128 ethReserve;
    uint88 budget;
    uint32 updatedAt;
    bool swapPaused; // emergency pause
  }

  struct Observation {
    uint32 timestamp;
    uint112 priceCumulativeAbove;
    uint112 priceCumulativeBelow;
  }

  // memory structs

  struct State {
    uint nxmA;
    uint nxmB;
    uint eth;
    uint budget;
    uint ratchetSpeedB;
    uint timestamp;
  }

  struct Context {
    uint capital;
    uint supply;
    uint mcr;
  }

  struct CumulativePriceCalculationProps {
    uint previousEthReserve;
    uint currentEthReserve;
    uint previousNxmA;
    uint currentNxmA;
    uint previousNxmB;
    uint currentNxmB;
    uint previousTimestamp;
    uint observationTimestamp;
  }

  struct CumulativePriceCalculationTimes {
    uint secondsUntilBVAbove;
    uint secondsUntilBVBelow;
    uint timeElapsed;
    uint bvTimeBelow;
    uint bvTimeAbove;
    uint ratchetTimeAbove;
    uint ratchetTimeBelow;
  }

  /* ========== VIEWS ========== */

  function getReserves() external view returns (
    uint ethReserve,
    uint nxmA,
    uint nxmB,
    uint remainingBudget
  );

  function getSpotPrices() external view returns (uint spotPriceA, uint spotPriceB);

  function getBookValue() external view returns (uint bookValue);

  function getInternalPrice() external view returns (uint internalPrice);

  /* ==== MUTATIVE FUNCTIONS ==== */

  function updateTwap() external;

  function getInternalPriceAndUpdateTwap() external returns (uint internalPrice);

  function swap(uint nxmIn, uint minAmountOut, uint deadline) external payable returns (uint amountOut);

  function removeBudget() external;

  function setEmergencySwapPause(bool _swapPaused) external;

  /* ========== EVENTS AND ERRORS ========== */

  event EthSwappedForNxm(address indexed member, uint ethIn, uint nxmOut);
  event NxmSwappedForEth(address indexed member, uint nxmIn, uint ethOut);
  event ObservationUpdated(uint32 timestamp, uint112 priceCumulativeAbove, uint112 priceCumulativeBelow);
  event BudgetRemoved();
  event SwapPauseConfigured(bool paused);
  event EthInjected(uint value);
  event EthExtracted(uint value);

  // Pause
  error SystemPaused();
  error SwapPaused();

  // Input
  error OneInputOnly();
  error OneInputRequired();

  // Expiry
  error SwapExpired(uint deadline, uint blockTimestamp);

  // Insufficient amount out
  error InsufficientAmountOut(uint amountOut, uint minAmountOut);

  // Buffer Zone
  error NoSwapsInBufferZone();

  // ETH Transfer
  error EthTransferFailed();

  // Circuit breakers
  error EthCircuitBreakerHit();
  error NxmCircuitBreakerHit();
}

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity >=0.5.0;

interface ISAFURAMaster {

  function tokenAddress() external view returns (address);

  function owner() external view returns (address);

  function emergencyAdmin() external view returns (address);

  function masterInitialized() external view returns (bool);

  function isInternal(address _add) external view returns (bool);

  function isPause() external view returns (bool check);

  function isMember(address _add) external view returns (bool);

  function checkIsAuthToGoverned(address _add) external view returns (bool);

  function getLatestAddress(bytes2 _contractName) external view returns (address payable contractAddress);

  function contractAddresses(bytes2 code) external view returns (address payable);

  function upgradeMultipleContracts(
    bytes2[] calldata _contractCodes,
    address payable[] calldata newAddresses
  ) external;

  function removeContracts(bytes2[] calldata contractCodesToRemove) external;

  function addNewInternalContracts(
    bytes2[] calldata _contractCodes,
    address payable[] calldata newAddresses,
    uint[] calldata _types
  ) external;

  function updateOwnerParameters(bytes8 code, address payable val) external;
}

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity >=0.5.0;

interface ISAFURAToken {

  function burn(uint256 amount) external returns (bool);

  function burnFrom(address from, uint256 value) external returns (bool);

  function operatorTransfer(address from, uint256 value) external returns (bool);

  function mint(address account, uint256 amount) external;

  function isLockedForMV(address member) external view returns (uint);

  function whiteListed(address member) external view returns (bool);

  function addToWhiteList(address _member) external returns (bool);

  function removeFromWhiteList(address _member) external returns (bool);

  function changeOperator(address _newOperator) external returns (bool);

  function lockForMemberVote(address _of, uint _days) external;

  /**
 * @dev Returns the amount of tokens in existence.
 */
  function totalSupply() external view returns (uint256);

  /**
   * @dev Returns the amount of tokens owned by `account`.
   */
  function balanceOf(address account) external view returns (uint256);

  /**
   * @dev Moves `amount` tokens from the caller's account to `recipient`.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * Emits a {Transfer} event.
   */
  function transfer(address recipient, uint256 amount) external returns (bool);

  /**
   * @dev Returns the remaining number of tokens that `spender` will be
   * allowed to spend on behalf of `owner` through {transferFrom}. This is
   * zero by default.
   *
   * This value changes when {approve} or {transferFrom} are called.
   */
  function allowance(address owner, address spender) external view returns (uint256);

  /**
   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * IMPORTANT: Beware that changing an allowance with this method brings the risk
   * that someone may use both the old and the new allowance by unfortunate
   * transaction ordering. One possible solution to mitigate this race
   * condition is to first reduce the spender's allowance to 0 and set the
   * desired value afterwards:
   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
   *
   * Emits an {Approval} event.
   */
  function approve(address spender, uint256 amount) external returns (bool);

  /**
   * @dev Moves `amount` tokens from `sender` to `recipient` using the
   * allowance mechanism. `amount` is then deducted from the caller's
   * allowance.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * Emits a {Transfer} event.
   */
  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

  /**
   * @dev Emitted when `value` tokens are moved from one account (`from`) to
   * another (`to`).
   *
   * Note that `value` may be zero.
   */
  event Transfer(address indexed from, address indexed to, uint256 value);

  /**
   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
   * a call to {approve}. `value` is the new allowance.
   */
  event Approval(address indexed owner, address indexed spender, uint256 value);
}

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity >=0.5.0;

import "./ISAFURAToken.sol";

interface ITokenController {

  struct StakingPoolNXMBalances {
    uint128 rewards;
    uint128 deposits;
  }

  struct CoverInfo {
    uint16 claimCount;
    bool hasOpenClaim;
    bool hasAcceptedClaim;
    uint96 requestedPayoutAmount;
    // note: still 128 bits available here, can be used later
  }

  struct StakingPoolOwnershipOffer {
    address proposedManager;
    uint96 deadline;
  }

  function coverInfo(uint id) external view returns (
    uint16 claimCount,
    bool hasOpenClaim,
    bool hasAcceptedClaim,
    uint96 requestedPayoutAmount
  );

  function withdrawCoverNote(
    address _of,
    uint[] calldata _coverIds,
    uint[] calldata _indexes
  ) external;

  function changeOperator(address _newOperator) external;

  function operatorTransfer(address _from, address _to, uint _value) external returns (bool);

  function burnFrom(address _of, uint amount) external returns (bool);

  function addToWhitelist(address _member) external;

  function removeFromWhitelist(address _member) external;

  function mint(address _member, uint _amount) external;

  function lockForMemberVote(address _of, uint _days) external;

  function withdrawClaimAssessmentTokens(address[] calldata users) external;

  function getLockReasons(address _of) external view returns (bytes32[] memory reasons);

  function totalSupply() external view returns (uint);

  function totalBalanceOf(address _of) external view returns (uint amount);

  function totalBalanceOfWithoutDelegations(address _of) external view returns (uint amount);

  function getTokenPrice() external view returns (uint tokenPrice);

  function token() external view returns (ISAFURAToken);

  function getStakingPoolManager(uint poolId) external view returns (address manager);

  function getManagerStakingPools(address manager) external view returns (uint[] memory poolIds);

  function isStakingPoolManager(address member) external view returns (bool);

  function getStakingPoolOwnershipOffer(uint poolId) external view returns (address proposedManager, uint deadline);

  function transferStakingPoolsOwnership(address from, address to) external;

  function assignStakingPoolManager(uint poolId, address manager) external;

  function createStakingPoolOwnershipOffer(uint poolId, address proposedManager, uint deadline) external;

  function acceptStakingPoolOwnershipOffer(uint poolId) external;

  function cancelStakingPoolOwnershipOffer(uint poolId) external;

  function mintStakingPoolNXMRewards(uint amount, uint poolId) external;

  function burnStakingPoolNXMRewards(uint amount, uint poolId) external;

  function depositStakedNXM(address from, uint amount, uint poolId) external;

  function withdrawNXMStakeAndRewards(address to, uint stakeToWithdraw, uint rewardsToWithdraw, uint poolId) external;

  function burnStakedNXM(uint amount, uint poolId) external;

  function stakingPoolNXMBalances(uint poolId) external view returns(uint128 rewards, uint128 deposits);

  function tokensLocked(address _of, bytes32 _reason) external view returns (uint256 amount);

  function getWithdrawableCoverNotes(
    address coverOwner
  ) external view returns (
    uint[] memory coverIds,
    bytes32[] memory lockReasons,
    uint withdrawableAmount
  );

  function getPendingRewards(address member) external view returns (uint);
}

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.18;

/**
 * @dev Simple library that defines min, max and babylonian sqrt functions
 */
library Math {

  function min(uint a, uint b) internal pure returns (uint) {
    return a < b ? a : b;
  }

  function max(uint a, uint b) internal pure returns (uint) {
    return a > b ? a : b;
  }

  function sum(uint[] memory items) internal pure returns (uint) {
    uint count = items.length;
    uint total;

    for (uint i = 0; i < count; i++) {
      total += items[i];
    }

    return total;
  }

  function divRound(uint a, uint b) internal pure returns (uint) {
    return (a + b / 2) / b;
  }

  function divCeil(uint a, uint b) internal pure returns (uint) {
    return (a + b - 1) / b;
  }

  function roundUp(uint a, uint b) internal pure returns (uint) {
    return divCeil(a, b) * b;
  }

  // babylonian method
  function sqrt(uint y) internal pure returns (uint) {

    if (y > 3) {
      uint z = y;
      uint x = y / 2 + 1;
      while (x < z) {
        z = x;
        x = (y / x + x) / 2;
      }
      return z;
    }

    if (y != 0) {
      return 1;
    }

    return 0;
  }

}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

/**
 * @dev Wrappers over Solidity's uintXX casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeUintCast {
  /**
   * @dev Returns the downcasted uint248 from uint256, reverting on
   * overflow (when the input is greater than largest uint248).
   *
   * Counterpart to Solidity's `uint248` operator.
   *
   * Requirements:
   *
   * - input must fit into 248 bits
   */
  function toUint248(uint256 value) internal pure returns (uint248) {
    require(value < 2**248, "SafeCast: value doesn\'t fit in 248 bits");
    return uint248(value);
  }

  /**
   * @dev Returns the downcasted uint240 from uint256, reverting on
   * overflow (when the input is greater than largest uint240).
   *
   * Counterpart to Solidity's `uint240` operator.
   *
   * Requirements:
   *
   * - input must fit into 240 bits
   */
  function toUint240(uint256 value) internal pure returns (uint240) {
    require(value < 2**240, "SafeCast: value doesn\'t fit in 240 bits");
    return uint240(value);
  }

  /**
   * @dev Returns the downcasted uint232 from uint256, reverting on
   * overflow (when the input is greater than largest uint232).
   *
   * Counterpart to Solidity's `uint232` operator.
   *
   * Requirements:
   *
   * - input must fit into 232 bits
   */
  function toUint232(uint256 value) internal pure returns (uint232) {
    require(value < 2**232, "SafeCast: value doesn\'t fit in 232 bits");
    return uint232(value);
  }

  /**
   * @dev Returns the downcasted uint224 from uint256, reverting on
   * overflow (when the input is greater than largest uint224).
   *
   * Counterpart to Solidity's `uint224` operator.
   *
   * Requirements:
   *
   * - input must fit into 224 bits
   */
  function toUint224(uint256 value) internal pure returns (uint224) {
    require(value < 2**224, "SafeCast: value doesn\'t fit in 224 bits");
    return uint224(value);
  }

  /**
   * @dev Returns the downcasted uint216 from uint256, reverting on
   * overflow (when the input is greater than largest uint216).
   *
   * Counterpart to Solidity's `uint216` operator.
   *
   * Requirements:
   *
   * - input must fit into 216 bits
   */
  function toUint216(uint256 value) internal pure returns (uint216) {
    require(value < 2**216, "SafeCast: value doesn\'t fit in 216 bits");
    return uint216(value);
  }

  /**
   * @dev Returns the downcasted uint208 from uint256, reverting on
   * overflow (when the input is greater than largest uint208).
   *
   * Counterpart to Solidity's `uint208` operator.
   *
   * Requirements:
   *
   * - input must fit into 208 bits
   */
  function toUint208(uint256 value) internal pure returns (uint208) {
    require(value < 2**208, "SafeCast: value doesn\'t fit in 208 bits");
    return uint208(value);
  }

  /**
   * @dev Returns the downcasted uint200 from uint256, reverting on
   * overflow (when the input is greater than largest uint200).
   *
   * Counterpart to Solidity's `uint200` operator.
   *
   * Requirements:
   *
   * - input must fit into 200 bits
   */
  function toUint200(uint256 value) internal pure returns (uint200) {
    require(value < 2**200, "SafeCast: value doesn\'t fit in 200 bits");
    return uint200(value);
  }

  /**
   * @dev Returns the downcasted uint192 from uint256, reverting on
   * overflow (when the input is greater than largest uint192).
   *
   * Counterpart to Solidity's `uint192` operator.
   *
   * Requirements:
   *
   * - input must fit into 192 bits
   */
  function toUint192(uint256 value) internal pure returns (uint192) {
    require(value < 2**192, "SafeCast: value doesn\'t fit in 192 bits");
    return uint192(value);
  }

  /**
   * @dev Returns the downcasted uint184 from uint256, reverting on
   * overflow (when the input is greater than largest uint184).
   *
   * Counterpart to Solidity's `uint184` operator.
   *
   * Requirements:
   *
   * - input must fit into 184 bits
   */
  function toUint184(uint256 value) internal pure returns (uint184) {
    require(value < 2**184, "SafeCast: value doesn\'t fit in 184 bits");
    return uint184(value);
  }

  /**
   * @dev Returns the downcasted uint176 from uint256, reverting on
   * overflow (when the input is greater than largest uint176).
   *
   * Counterpart to Solidity's `uint176` operator.
   *
   * Requirements:
   *
   * - input must fit into 176 bits
   */
  function toUint176(uint256 value) internal pure returns (uint176) {
    require(value < 2**176, "SafeCast: value doesn\'t fit in 176 bits");
    return uint176(value);
  }

  /**
   * @dev Returns the downcasted uint168 from uint256, reverting on
   * overflow (when the input is greater than largest uint168).
   *
   * Counterpart to Solidity's `uint168` operator.
   *
   * Requirements:
   *
   * - input must fit into 168 bits
   */
  function toUint168(uint256 value) internal pure returns (uint168) {
    require(value < 2**168, "SafeCast: value doesn\'t fit in 168 bits");
    return uint168(value);
  }

  /**
   * @dev Returns the downcasted uint160 from uint256, reverting on
   * overflow (when the input is greater than largest uint160).
   *
   * Counterpart to Solidity's `uint160` operator.
   *
   * Requirements:
   *
   * - input must fit into 160 bits
   */
  function toUint160(uint256 value) internal pure returns (uint160) {
    require(value < 2**160, "SafeCast: value doesn\'t fit in 160 bits");
    return uint160(value);
  }

  /**
   * @dev Returns the downcasted uint152 from uint256, reverting on
   * overflow (when the input is greater than largest uint152).
   *
   * Counterpart to Solidity's `uint152` operator.
   *
   * Requirements:
   *
   * - input must fit into 152 bits
   */
  function toUint152(uint256 value) internal pure returns (uint152) {
    require(value < 2**152, "SafeCast: value doesn\'t fit in 152 bits");
    return uint152(value);
  }

  /**
   * @dev Returns the downcasted uint144 from uint256, reverting on
   * overflow (when the input is greater than largest uint144).
   *
   * Counterpart to Solidity's `uint144` operator.
   *
   * Requirements:
   *
   * - input must fit into 144 bits
   */
  function toUint144(uint256 value) internal pure returns (uint144) {
    require(value < 2**144, "SafeCast: value doesn\'t fit in 144 bits");
    return uint144(value);
  }

  /**
   * @dev Returns the downcasted uint136 from uint256, reverting on
   * overflow (when the input is greater than largest uint136).
   *
   * Counterpart to Solidity's `uint136` operator.
   *
   * Requirements:
   *
   * - input must fit into 136 bits
   */
  function toUint136(uint256 value) internal pure returns (uint136) {
    require(value < 2**136, "SafeCast: value doesn\'t fit in 136 bits");
    return uint136(value);
  }

  /**
   * @dev Returns the downcasted uint128 from uint256, reverting on
   * overflow (when the input is greater than largest uint128).
   *
   * Counterpart to Solidity's `uint128` operator.
   *
   * Requirements:
   *
   * - input must fit into 128 bits
   */
  function toUint128(uint256 value) internal pure returns (uint128) {
    require(value < 2**128, "SafeCast: value doesn\'t fit in 128 bits");
    return uint128(value);
  }

  /**
   * @dev Returns the downcasted uint120 from uint256, reverting on
   * overflow (when the input is greater than largest uint120).
   *
   * Counterpart to Solidity's `uint120` operator.
   *
   * Requirements:
   *
   * - input must fit into 120 bits
   */
  function toUint120(uint256 value) internal pure returns (uint120) {
    require(value < 2**120, "SafeCast: value doesn\'t fit in 120 bits");
    return uint120(value);
  }

  /**
   * @dev Returns the downcasted uint112 from uint256, reverting on
   * overflow (when the input is greater than largest uint112).
   *
   * Counterpart to Solidity's `uint112` operator.
   *
   * Requirements:
   *
   * - input must fit into 112 bits
   */
  function toUint112(uint256 value) internal pure returns (uint112) {
    require(value < 2**112, "SafeCast: value doesn\'t fit in 112 bits");
    return uint112(value);
  }

  /**
   * @dev Returns the downcasted uint104 from uint256, reverting on
   * overflow (when the input is greater than largest uint104).
   *
   * Counterpart to Solidity's `uint104` operator.
   *
   * Requirements:
   *
   * - input must fit into 104 bits
   */
  function toUint104(uint256 value) internal pure returns (uint104) {
    require(value < 2**104, "SafeCast: value doesn\'t fit in 104 bits");
    return uint104(value);
  }

  /**
   * @dev Returns the downcasted uint96 from uint256, reverting on
   * overflow (when the input is greater than largest uint96).
   *
   * Counterpart to Solidity's `uint104` operator.
   *
   * Requirements:
   *
   * - input must fit into 96 bits
   */
  function toUint96(uint256 value) internal pure returns (uint96) {
    require(value < 2**96, "SafeCast: value doesn\'t fit in 96 bits");
    return uint96(value);
  }

  /**
   * @dev Returns the downcasted uint88 from uint256, reverting on
   * overflow (when the input is greater than largest uint88).
   *
   * Counterpart to Solidity's `uint104` operator.
   *
   * Requirements:
   *
   * - input must fit into 88 bits
   */
  function toUint88(uint256 value) internal pure returns (uint88) {
    require(value < 2**88, "SafeCast: value doesn\'t fit in 88 bits");
    return uint88(value);
  }

  /**
   * @dev Returns the downcasted uint80 from uint256, reverting on
   * overflow (when the input is greater than largest uint80).
   *
   * Counterpart to Solidity's `uint104` operator.
   *
   * Requirements:
   *
   * - input must fit into 80 bits
   */
  function toUint80(uint256 value) internal pure returns (uint80) {
    require(value < 2**80, "SafeCast: value doesn\'t fit in 80 bits");
    return uint80(value);
  }

  /**
   * @dev Returns the downcasted uint64 from uint256, reverting on
   * overflow (when the input is greater than largest uint64).
   *
   * Counterpart to Solidity's `uint64` operator.
   *
   * Requirements:
   *
   * - input must fit into 64 bits
   */
  function toUint64(uint256 value) internal pure returns (uint64) {
    require(value < 2**64, "SafeCast: value doesn\'t fit in 64 bits");
    return uint64(value);
  }

  /**
   * @dev Returns the downcasted uint56 from uint256, reverting on
   * overflow (when the input is greater than largest uint56).
   *
   * Counterpart to Solidity's `uint56` operator.
   *
   * Requirements:
   *
   * - input must fit into 56 bits
   */
  function toUint56(uint256 value) internal pure returns (uint56) {
    require(value < 2**56, "SafeCast: value doesn\'t fit in 56 bits");
    return uint56(value);
  }

  /**
   * @dev Returns the downcasted uint48 from uint256, reverting on
   * overflow (when the input is greater than largest uint48).
   *
   * Counterpart to Solidity's `uint48` operator.
   *
   * Requirements:
   *
   * - input must fit into 48 bits
   */
  function toUint48(uint256 value) internal pure returns (uint48) {
    require(value < 2**48, "SafeCast: value doesn\'t fit in 48 bits");
    return uint48(value);
  }

  /**
   * @dev Returns the downcasted uint40 from uint256, reverting on
   * overflow (when the input is greater than largest uint40).
   *
   * Counterpart to Solidity's `uint40` operator.
   *
   * Requirements:
   *
   * - input must fit into 40 bits
   */
  function toUint40(uint256 value) internal pure returns (uint40) {
    require(value < 2**40, "SafeCast: value doesn\'t fit in 40 bits");
    return uint40(value);
  }

  /**
   * @dev Returns the downcasted uint32 from uint256, reverting on
   * overflow (when the input is greater than largest uint32).
   *
   * Counterpart to Solidity's `uint32` operator.
   *
   * Requirements:
   *
   * - input must fit into 32 bits
   */
  function toUint32(uint256 value) internal pure returns (uint32) {
    require(value < 2**32, "SafeCast: value doesn\'t fit in 32 bits");
    return uint32(value);
  }

  /**
   * @dev Returns the downcasted uint24 from uint256, reverting on
   * overflow (when the input is greater than largest uint24).
   *
   * Counterpart to Solidity's `uint24` operator.
   *
   * Requirements:
   *
   * - input must fit into 24 bits
   */
  function toUint24(uint256 value) internal pure returns (uint24) {
    require(value < 2**24, "SafeCast: value doesn\'t fit in 24 bits");
    return uint24(value);
  }

  /**
   * @dev Returns the downcasted uint16 from uint256, reverting on
   * overflow (when the input is greater than largest uint16).
   *
   * Counterpart to Solidity's `uint16` operator.
   *
   * Requirements:
   *
   * - input must fit into 16 bits
   */
  function toUint16(uint256 value) internal pure returns (uint16) {
    require(value < 2**16, "SafeCast: value doesn\'t fit in 16 bits");
    return uint16(value);
  }

  /**
   * @dev Returns the downcasted uint8 from uint256, reverting on
   * overflow (when the input is greater than largest uint8).
   *
   * Counterpart to Solidity's `uint8` operator.
   *
   * Requirements:
   *
   * - input must fit into 8 bits.
   */
  function toUint8(uint256 value) internal pure returns (uint8) {
    require(value < 2**8, "SafeCast: value doesn\'t fit in 8 bits");
    return uint8(value);
  }
}

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.18;

import "@openzeppelin/contracts-v4/utils/cryptography/MerkleProof.sol";

import "../../abstract/MasterAwareV2.sol";
import "../../interfaces/IAssessment.sol";
import "../../interfaces/IMemberRoles.sol";
import "../../interfaces/ISAFURAToken.sol";
import "../../interfaces/ITokenController.sol";
import "../../interfaces/IRamm.sol";
import "../../libraries/Math.sol";
import "../../libraries/SafeUintCast.sol";

/// Provides the assessment mechanism for members to decide the outcome of the events that can lead
/// to payouts. Mints rewards for stakers that act benevolently and allows burning fraudulent ones.
contract Assessment is IAssessment, MasterAwareV2 {

  ISAFURAToken public immutable nxm;

  /* ========== STATE VARIABLES ========== */

  // Parameters configurable through governance.
  Configuration public override config;

  // Stake states of users. (See Stake struct)
  mapping(address => Stake) public override stakeOf;

  // Votes of users. (See Vote struct)
  mapping(address => Vote[]) public override votesOf;

  // Mapping used to determine if a user has already voted
  mapping(address => mapping(uint => bool)) public override hasAlreadyVotedOn;

  // An array of merkle tree roots used to indicate fraudulent assessors. Each root represents a
  // fraud attempt by one or multiple addresses. Once the root is submitted by adivsory board
  // members through governance, burnFraud uses this root to burn the fraudulent assessors' stakes
  // and correct the outcome of the poll.
  bytes32[] public fraudResolution;

  Assessment[] public override assessments;

  /* ========== CONSTRUCTOR ========== */

  constructor(address nxmAddress) {
    nxm = ISAFURAToken(nxmAddress);
  }

  /* ========== VIEWS ========== */

  /// @dev Returns the vote count of an assessor.
  ///
  /// @param assessor  The address of the assessor.
  function getVoteCountOfAssessor(address assessor) external override view returns (uint) {
    return votesOf[assessor].length;
  }

  /// @dev Returns the number of assessments.
  function getAssessmentsCount() external override view returns (uint) {
    return assessments.length;
  }

  /// @dev Returns only the poll from the assessment struct to make only one SLOAD. Is used by
  /// other contracts.
  ///
  /// @param assessmentId  The index of the assessment
  function getPoll(uint assessmentId) external override view returns (Poll memory) {
    return assessments[assessmentId].poll;
  }

  /// Returns all pending rewards, the withdrawable amount and the index until which they can be
  /// withdrawn.
  ///
  /// @param staker  The address of the staker
  function getRewards(address staker) external override view returns (
    uint totalPendingAmountInNXM,
    uint withdrawableAmountInNXM,
    uint withdrawableUntilIndex
  ) {
    uint104 rewardsWithdrawableFromIndex = stakeOf[staker].rewardsWithdrawableFromIndex;
    Vote memory vote;
    Assessment memory assessment;
    uint voteCount = votesOf[staker].length;
    bool hasReachedUnwithdrawableReward = false;

    for (uint i = rewardsWithdrawableFromIndex; i < voteCount; i++) {
      vote = votesOf[staker][i];
      assessment = assessments[vote.assessmentId];

      // If hasReachedUnwithdrawableReward is true, skip and continue calculating the pending total
      // rewards.
      if (
        !hasReachedUnwithdrawableReward &&
        uint(assessment.poll.end) + uint(config.payoutCooldownInDays) * 1 days >= block.timestamp
      ) {
        hasReachedUnwithdrawableReward = true;
        // Store the index of the vote until which rewards can be withdrawn.
        withdrawableUntilIndex = i;
        // Then, also store the pending total value that can be withdrawn until this index.
        withdrawableAmountInNXM = totalPendingAmountInNXM;
      }

      totalPendingAmountInNXM += uint(assessment.totalRewardInNXM) * uint(vote.stakedAmount) /
        (uint(assessment.poll.accepted) + uint(assessment.poll.denied));
    }

    if (!hasReachedUnwithdrawableReward) {
      withdrawableUntilIndex = voteCount;
      withdrawableAmountInNXM = totalPendingAmountInNXM;
    }
  }

  /* === MUTATIVE FUNCTIONS ==== */

  /// Increases the sender's stake by the specified amount and transfers NXM to this contract
  ///
  /// @param amount  The amount of nxm to stake
  function stake(uint96 amount) public whenNotPaused {
    // only owner, advisory board, auditor can stake
    require(
      IMemberRoles(internalContracts[uint(ID.MR)]).checkRole(msg.sender, uint(IMemberRoles.Role.Owner)) ||
      IMemberRoles(internalContracts[uint(ID.MR)]).checkRole(msg.sender, uint(IMemberRoles.Role.AdvisoryBoard)) ||
      IMemberRoles(internalContracts[uint(ID.MR)]).checkRole(msg.sender, uint(IMemberRoles.Role.Auditor)), "Only owner, advisory board, auditor can stake");

    stakeOf[msg.sender].amount += amount;
    ITokenController(getInternalContractAddress(ID.TC))
      .operatorTransfer(msg.sender, address(this), amount);

    emit StakeDeposited(msg.sender, amount);
  }

  /// Withdraws a portion or all of the user's stake
  ///
  /// @dev At least stakeLockupPeriodInDays must have passed since the last vote.
  ///
  /// @param amount  The amount of nxm to unstake
  /// @param to      The member address where the NXM is transfered to. Useful for switching
  ///                membership during stake lockup period and thus allowing the user to withdraw
  ///                their staked amount to the new address when possible.
  function unstake(uint96 amount, address to) external whenNotPaused override {
    // Restrict unstaking to a different account if still locked for member vote
    if (block.timestamp < nxm.isLockedForMV(msg.sender)) {
      require(to == msg.sender, "Assessment: NXM is locked for voting in governance");
    }

    uint voteCount = votesOf[msg.sender].length;
    if (voteCount > 0) {
      Vote memory vote = votesOf[msg.sender][voteCount - 1];
      require(
        block.timestamp > vote.timestamp + uint(config.stakeLockupPeriodInDays) * 1 days,
        "Stake is in lockup period"
      );
    }

    stakeOf[msg.sender].amount -= amount;
    nxm.transfer(to, amount);

    emit StakeWithdrawn(msg.sender, to, amount);
  }

  /// Withdraws a staker's accumulated rewards to a destination address but only the staker can
  /// call this.
  ///
  /// @dev Only withdraws until the last finalized poll.
  ///
  /// @param staker      The address of the staker for which the rewards are withdrawn
  /// @param batchSize   The index until which (but not including) the rewards should be withdrawn.
  ///                    Used if a large number of assessments accumulates and the function doesn't
  ///                    fit in one block, thus requiring multiple batched transactions.
  function withdrawRewards(
    address staker,
    uint104 batchSize
  ) external override whenNotPaused returns (uint withdrawn, uint withdrawnUntilIndex) {
    return _withdrawRewards(staker, staker, batchSize);
  }

  /// Withdraws a staker's accumulated rewards.
  ///
  /// @dev Only withdraws until the last finalized poll.
  ///
  /// @param destination The destination address where the rewards will be withdrawn.
  /// @param batchSize   The index until which (but not including) the rewards should be withdrawn.
  ///                    Used if a large number of assessments accumulates and the function doesn't
  ///                    fit in one block, thus requiring multiple batched transactions.
  function withdrawRewardsTo(
    address destination,
    uint104 batchSize
  ) external override whenNotPaused returns (uint withdrawn, uint withdrawnUntilIndex) {
    return _withdrawRewards(msg.sender, destination, batchSize);
  }

  function _withdrawRewards(
    address staker,
    address destination,
    uint104 batchSize
  ) internal returns (uint withdrawn, uint withdrawnUntilIndex) {
    require(
      IMemberRoles(internalContracts[uint(ID.MR)]).checkRole(
        destination,
        uint(IMemberRoles.Role.Member)
      ),
      "Destination address is not a member"
    );

    // This is the index until which (but not including) the previous withdrawal was processed.
    // The current withdrawal starts from this index.
    uint104 rewardsWithdrawableFromIndex = stakeOf[staker].rewardsWithdrawableFromIndex;
    {
      uint voteCount = votesOf[staker].length;
      require(rewardsWithdrawableFromIndex < voteCount, "No withdrawable rewards");
      // If batchSize is a non-zero value, it means the withdrawal is going to be batched in
      // multiple transactions.
      withdrawnUntilIndex = batchSize > 0 ? rewardsWithdrawableFromIndex + batchSize : voteCount;
    }

    Vote memory vote;
    Assessment memory assessment;
    for (uint i = rewardsWithdrawableFromIndex; i < withdrawnUntilIndex; i++) {
      vote = votesOf[staker][i];
      assessment = assessments[vote.assessmentId];
      if (uint(assessment.poll.end) + uint(config.payoutCooldownInDays) * 1 days >= block.timestamp) {
        // Poll is not final
        withdrawnUntilIndex = i;
        break;
      }

      withdrawn += uint(assessment.totalRewardInNXM) * uint(vote.stakedAmount) /
        (uint(assessment.poll.accepted) + uint(assessment.poll.denied));
    }

    // This is the index where the next withdrawReward call will start iterating from
    stakeOf[staker].rewardsWithdrawableFromIndex = SafeUintCast.toUint104(withdrawnUntilIndex);
    ITokenController(getInternalContractAddress(ID.TC)).mint(destination, withdrawn);

    emit RewardWithdrawn(staker, destination, withdrawn);
  }


  /// Creates a new assessment
  ///
  /// @dev Is used only by contracts acting as redemption methods for cover product types.
  ///
  /// @param totalAssessmentReward   The total reward that is shared among the stakers participating
  ///                                the assessment.
  /// @param assessmentDepositInETH  The deposit that covers assessment rewards in case it's denied.
  ///                                If the assessment verdict is positive, the contract that relies
  ///                                on it can send back the deposit at payout.
  function startAssessment(
    uint totalAssessmentReward,
    uint assessmentDepositInETH
  ) external override onlyInternal returns (uint) {
    assessments.push(Assessment(
      Poll(
        0, // accepted
        0, // denied
        uint32(block.timestamp), // start
        uint32(block.timestamp + uint32(config.minVotingPeriodInDays) * 1 days) // end
      ),
      uint128(totalAssessmentReward),
      uint128(assessmentDepositInETH)
    ));
    return assessments.length - 1;
  }

  /// Casts multiple votes on assessments and optionally allows to increase the stake in the same
  /// transaction.
  ///
  /// @dev See stake and castVote functions.
  ///
  /// @param assessmentIds  Array of assessment indexes for which the votes are cast.
  /// @param votes          Array of votes corresponding to each assessment id from the
  ///                       assessmentIds param. Elements that are false represent a deny vote and
  ///                       those that are true represent an accept vote.
  /// @param stakeIncrease  When a non-zero value is given, this function will also increase the
  ///                       stake in the same transaction.
  function castVotes(
    uint[] calldata assessmentIds,
    bool[] calldata votes,
    string[] calldata ipfsAssessmentDataHashes,
    uint96 stakeIncrease
  ) external override onlyMember whenNotPaused {
    require(
      assessmentIds.length == votes.length,
      "The lengths of the assessment ids and votes arrays mismatch"
    );
    require(
      assessmentIds.length == ipfsAssessmentDataHashes.length,
      "The lengths of the assessment ids and ipfs assessment data hashes arrays mismatch"
    );

    if (stakeIncrease > 0) {
      stake(stakeIncrease);
    }

    for (uint i = 0; i < assessmentIds.length; i++) {
      castVote(assessmentIds[i], votes[i], ipfsAssessmentDataHashes[i]);
    }
  }

  /// Casts a vote on an assessment
  ///
  /// @dev Resets the poll's end date on the first vote. The first vote can only be an accept vote.
  /// If no votes are cast during minVotingPeriodInDays it is automatically considered denied. When
  /// the poll ends in less than silentEndingPeriodInDays, the end date is extended with a potion of
  /// silentEndingPeriodInDays proportional to the user's stake compared to the
  /// total stake on that assessment, namely the sum of tokens used for both accept and deny votes,
  /// but no greater than silentEndingPeriodInDays.
  ///
  /// @param assessmentId  The index of the assessment for which the vote is cast
  /// @param isAcceptVote  True to accept, false to deny
  function castVote(uint assessmentId, bool isAcceptVote, string memory ipfsAssessmentDataHash) internal {
    {
      require(!hasAlreadyVotedOn[msg.sender][assessmentId], "Already voted");
      hasAlreadyVotedOn[msg.sender][assessmentId] = true;
    }

    uint96 stakeAmount = stakeOf[msg.sender].amount;
    require(stakeAmount > 0, "A stake is required to cast votes");

    Poll memory poll = assessments[assessmentId].poll;
    require(block.timestamp < poll.end, "Voting is closed");
    require(
      poll.accepted > 0 || isAcceptVote,
      "At least one accept vote is required to vote deny"
    );

    if (poll.accepted == 0) {
      // Reset the poll end date on the first accept vote
      poll.end = uint32(block.timestamp + uint(config.minVotingPeriodInDays) * 1 days);
    }

    // Check if poll ends in less than 24 hours
    uint silentEndingPeriod = uint(config.silentEndingPeriodInDays) * 1 days;
    if (poll.end - block.timestamp < silentEndingPeriod) {
      // Extend proportionally to the user's stake but up to 1 day maximum
      poll.end += uint32(
        Math.min(
          silentEndingPeriod,
          silentEndingPeriod * uint(stakeAmount) / (uint(poll.accepted + poll.denied))
        )
      );
    }

    if (isAcceptVote) {
      poll.accepted += stakeAmount;
    } else {
      poll.denied += stakeAmount;
    }

    assessments[assessmentId].poll = poll;

    votesOf[msg.sender].push(Vote(
      uint80(assessmentId),
      isAcceptVote,
      uint32(block.timestamp),
      stakeAmount
    ));
    emit VoteCast(msg.sender, assessmentId, stakeAmount, isAcceptVote, ipfsAssessmentDataHash);
  }

  /// Allows governance to submit a merkle tree root hash representing fraudulent stakers
  ///
  /// @dev Leaves' inputs are the sequence of bytes obtained by concatenating:
  /// - Staker address (20 bytes or 160 bits)
  /// - The index of the last fraudulent vote (32 bytes or 256 bits)
  /// - Amount of stake to be burned (12 bytes or 96 bits)
  /// - The number of previous fraud attempts (2 bytes or 16 bits)
  ///
  /// @param root  The merkle tree root hash
  function submitFraud(bytes32 root) external override onlyGovernance {
    fraudResolution.push(root);
    emit FraudSubmitted(root);
  }

  /// Allows anyone to undo fraudulent votes and burn the fraudulent assessors present in the
  /// merkle tree whose hash is submitted through submitFraud
  ///
  /// @param rootIndex                The index of the merkle tree root hash stored in
  ///                                 fraudResolution.
  /// @param proof                    The path from the leaf to the root.
  /// @param assessor                 The address of the fraudulent assessor.
  /// @param lastFraudulentVoteIndex  The index of the last fraudulent vote cast by the assessor.
  /// @param burnAmount               The amount of stake that needs to be burned.
  /// @param fraudCount               The number of times the assessor has taken part in fraudulent
  ///                                 voting.
  /// @param voteBatchSize            The number of iterations that prevents an unbounded loop and
  ///                                 allows chunked processing. Can also be 0 if chunking is not
  ///                                 necessary.
  function processFraud(
    uint256 rootIndex,
    bytes32[] calldata proof,
    address assessor,
    uint256 lastFraudulentVoteIndex,
    uint96 burnAmount,
    uint16 fraudCount,
    uint256 voteBatchSize
  ) external override whenNotPaused {
    require(
      MerkleProof.verify(
        proof,
        fraudResolution[rootIndex],
        keccak256(abi.encodePacked(assessor, lastFraudulentVoteIndex, burnAmount, fraudCount))
      ),
      "Invalid merkle proof"
    );

    Stake memory _stake = stakeOf[assessor];

    // Make sure we don't burn beyond lastFraudulentVoteIndex
    uint processUntil = _stake.rewardsWithdrawableFromIndex + voteBatchSize;

    if (processUntil >= lastFraudulentVoteIndex) {
      processUntil = lastFraudulentVoteIndex + 1;
    }

    for (uint j = _stake.rewardsWithdrawableFromIndex; j < processUntil; j++) {
      IAssessment.Vote memory vote = votesOf[assessor][j];
      IAssessment.Poll memory poll = assessments[vote.assessmentId].poll;

      {
        if (uint32(block.timestamp) >= uint(poll.end) + uint(config.payoutCooldownInDays) * 1 days) {
          // Once the cooldown period ends the poll result is final, thus skip
          continue;
        }
      }

      if (vote.accepted) {
        poll.accepted -= vote.stakedAmount;
      } else {
        poll.denied -= vote.stakedAmount;
      }

      // If the poll ends in less than 24h, extend it to 24h
      uint32 nextDay = uint32(block.timestamp + 1 days);
      if (poll.end < nextDay) {
        poll.end = nextDay;
      }

      emit FraudProcessed(vote.assessmentId, assessor, poll);
      assessments[vote.assessmentId].poll = poll;
    }

    // Burns an assessor only once for each merkle tree root, no matter how many times this function
    // runs on the same account. When a transaction is too big to fit in one block, it is batched
    // in multiple transactions according to voteBatchSize. After burning the tokens, fraudCount
    // is incremented. If another merkle root is submitted that contains the same addres, the leaf
    // should use the updated fraudCount stored in the Stake struct as input.
    if (fraudCount == _stake.fraudCount) {
      // Make sure this doesn't revert if the stake amount is already subtracted due to a previous
      // burn from a different merkle tree.
      burnAmount = burnAmount > _stake.amount ? _stake.amount : burnAmount;
      _stake.amount -= burnAmount;
      _stake.fraudCount++;

      // TODO: consider burning the tokens in the token controller contract
      ramm().updateTwap();
      nxm.burn(burnAmount);
    }

    if (uint104(processUntil) > _stake.rewardsWithdrawableFromIndex) {
      _stake.rewardsWithdrawableFromIndex = uint104(processUntil);
    }

    stakeOf[assessor] = _stake;
  }

  /// Updates configurable parameters through governance
  ///
  /// @param paramNames  An array of elements from UintParams enum
  /// @param values      An array of the new values, each one corresponding to the parameter
  ///                    from paramNames on the same position.
  function updateUintParameters(
    UintParams[] calldata paramNames,
    uint[] calldata values
  ) external override onlyGovernance {
    Configuration memory newConfig = config;
    for (uint i = 0; i < paramNames.length; i++) {
      if (paramNames[i] == IAssessment.UintParams.minVotingPeriodInDays) {
        newConfig.minVotingPeriodInDays = uint8(values[i]);
        continue;
      }
      if (paramNames[i] == IAssessment.UintParams.stakeLockupPeriodInDays) {
        newConfig.stakeLockupPeriodInDays = uint8(values[i]);
        continue;
      }
      if (paramNames[i] == IAssessment.UintParams.payoutCooldownInDays) {
        newConfig.payoutCooldownInDays = uint8(values[i]);
        continue;
      }
      if (paramNames[i] == IAssessment.UintParams.silentEndingPeriodInDays) {
        newConfig.silentEndingPeriodInDays = uint8(values[i]);
        continue;
      }
    }
    config = newConfig;
  }

  /// @dev Updates internal contract addresses to the ones stored in master. This function is
  /// automatically called by the master contract when a contract is added or upgraded.
  function changeDependentContractAddress() external override {
    internalContracts[uint(ID.TC)] = master.getLatestAddress("TC");
    internalContracts[uint(ID.MR)] = master.getLatestAddress("MR");
    internalContracts[uint(ID.RA)] = master.getLatestAddress("RA");

    Configuration memory currentConfig = config;
    bool notInitialized = bytes32(
      abi.encodePacked(
        currentConfig.minVotingPeriodInDays,
        currentConfig.payoutCooldownInDays,
        currentConfig.stakeLockupPeriodInDays,
        currentConfig.silentEndingPeriodInDays
      )
    ) == bytes32(0);

    if (notInitialized) {
      // initialize config
      config.minVotingPeriodInDays = 3; // days
      config.payoutCooldownInDays = 1; // days
      config.stakeLockupPeriodInDays = 14; // days
      config.silentEndingPeriodInDays = 1; // days
      // whitelist current contract
      ITokenController(getInternalContractAddress(ID.TC)).addToWhitelist(address(this));
    }
  }

  function ramm() internal view returns (IRamm) {
    return IRamm(internalContracts[uint(ID.RA)]);
  }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):