// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "../utils/DAOUpgradeableContract.sol"; /** * based on https://github.com/daostack/infra/blob/60a79a1be02942174e21156c3c9655a7f0695dbd/contracts/Reputation.sol * @title Reputation system * @dev A DAO has Reputation System which allows peers to rate other peers in order to build trust . * A reputation is used to assign influence measure to a DAO'S peers. * Reputation is similar to regular tokens but with one crucial difference: It is non-transferable. * The Reputation contract maintain a map of address to reputation value. * It provides an only minter role functions to mint and burn reputation _to (or _from) a specific address. */ contract Reputation is DAOUpgradeableContract, AccessControlUpgradeable { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); string public name; string public symbol; uint8 public decimals; //Number of decimals of the smallest unit // Event indicating minting of reputation to an address. event Mint(address indexed _to, uint256 _amount); // Event indicating burning of reputation for an address. event Burn(address indexed _from, uint256 _amount); uint256 private constant ZERO_HALF_256 = 0xffffffffffffffffffffffffffffffff; /// @dev `Checkpoint` is the structure that attaches a block number to a /// given value, the block number attached is the one that last changed the /// value //Checkpoint is uint256 : // bits 0-127 `fromBlock` is the block number that the value was generated from // bits 128-255 `value` is the amount of reputation at a specific block number // `balances` is the map that tracks the balance of each address, in this // contract when the balance changes the block number that the change // occurred is also included in the map mapping(address => uint256[]) public balances; // Tracks the history of the `totalSupply` of the reputation uint256[] public totalSupplyHistory; /** * @dev initialize */ function initialize(INameService _ns) public initializer { __Reputation_init(_ns); } function __Reputation_init(INameService _ns) internal { decimals = 18; name = "GoodDAO"; symbol = "GOOD"; __Context_init_unchained(); __ERC165_init_unchained(); __AccessControl_init_unchained(); if (address(_ns) != address(0)) { setDAO(_ns); _setupRole(DEFAULT_ADMIN_ROLE, address(avatar)); _setupRole(MINTER_ROLE, address(avatar)); } } function _canMint() internal view virtual { require(hasRole(MINTER_ROLE, _msgSender()), "Reputation: need minter role"); } /// @notice Generates `_amount` reputation that are assigned to `_owner` /// @param _user The address that will be assigned the new reputation /// @param _amount The quantity of reputation generated /// @return True if the reputation are generated correctly function mint(address _user, uint256 _amount) public returns (bool) { _canMint(); _mint(_user, _amount); return true; } function _mint(address _user, uint256 _amount) internal virtual returns (uint256) { uint256 curTotalSupply = totalSupplyLocalAt(block.number); uint256 previousBalanceTo = balanceOfLocalAt(_user, block.number); updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount); updateValueAtNow(balances[_user], previousBalanceTo + _amount); emit Mint(_user, _amount); return _amount; } /// @notice Burns `_amount` reputation from `_owner` /// @param _user The address that will lose the reputation /// @param _amount The quantity of reputation to burn /// @return True if the reputation are burned correctly function burn(address _user, uint256 _amount) public returns (bool) { //user can burn his own rep other wise we check _canMint if (_user != _msgSender()) _canMint(); _burn(_user, _amount); return true; } function _burn(address _user, uint256 _amount) internal virtual returns (uint256) { uint256 curTotalSupply = totalSupplyLocalAt(block.number); uint256 amountBurned = _amount; uint256 previousBalanceFrom = balanceOfLocalAt(_user, block.number); if (previousBalanceFrom < amountBurned) { amountBurned = previousBalanceFrom; } updateValueAtNow(totalSupplyHistory, curTotalSupply - amountBurned); updateValueAtNow(balances[_user], previousBalanceFrom - amountBurned); emit Burn(_user, amountBurned); return amountBurned; } function balanceOfLocal(address _owner) public view returns (uint256) { return balanceOfLocalAt(_owner, block.number); } /// @dev Queries the balance of `_owner` at a specific `_blockNumber` /// @param _owner The address from which the balance will be retrieved /// @param _blockNumber The block number when the balance is queried /// @return The balance at `_blockNumber` function balanceOfLocalAt(address _owner, uint256 _blockNumber) public view virtual returns (uint256) { if ( (balances[_owner].length == 0) || (uint128(balances[_owner][0]) > _blockNumber) ) { return 0; // This will return the expected balance during normal situations } else { return getValueAt(balances[_owner], _blockNumber); } } function totalSupplyLocal() public view virtual returns (uint256) { return totalSupplyLocalAt(block.number); } /// @notice Total amount of reputation at a specific `_blockNumber`. /// @param _blockNumber The block number when the totalSupply is queried /// @return The total amount of reputation at `_blockNumber` function totalSupplyLocalAt(uint256 _blockNumber) public view virtual returns (uint256) { if ( (totalSupplyHistory.length == 0) || (uint128(totalSupplyHistory[0]) > _blockNumber) ) { return 0; // This will return the expected totalSupply during normal situations } else { return getValueAt(totalSupplyHistory, _blockNumber); } } //////////////// // Internal helper functions to query and set a value in a snapshot array //////////////// /// @dev `getValueAt` retrieves the number of reputation at a given block number /// @param checkpoints The history of values being queried /// @param _block The block number to retrieve the value at /// @return The number of reputation being queried function getValueAt(uint256[] storage checkpoints, uint256 _block) internal view returns (uint256) { uint256 len = checkpoints.length; if (len == 0) { return 0; } // Shortcut for the actual value uint256 cur = checkpoints[len - 1]; if (_block >= uint128(cur)) { return cur >> 128; } if (_block < uint128(checkpoints[0])) { return 0; } // Binary search of the value in the array uint256 min = 0; uint256 max = len - 1; while (max > min) { uint256 mid = (max + min + 1) / 2; if (uint128(checkpoints[mid]) <= _block) { min = mid; } else { max = mid - 1; } } return checkpoints[min] >> 128; } /// @dev `updateValueAtNow` used to update the `balances` map and the /// `totalSupplyHistory` /// @param checkpoints The history of data being updated /// @param _value The new number of reputation function updateValueAtNow(uint256[] storage checkpoints, uint256 _value) internal { require(uint128(_value) == _value, "reputation overflow"); //check value is in the 128 bits bounderies if ( (checkpoints.length == 0) || (uint128(checkpoints[checkpoints.length - 1]) < block.number) ) { checkpoints.push(uint256(uint128(block.number)) | (_value << 128)); } else { checkpoints[checkpoints.length - 1] = uint256( (checkpoints[checkpoints.length - 1] & uint256(ZERO_HALF_256)) | (_value << 128) ); } } }