Contract Name:
UnifiedBribe
Contract Source Code:
File 1 of 1 : UnifiedBribe
/**
*Submitted for verification at SonicScan.org on 2024-12-18
*/
/**
*Submitted for verification at basescan.org on 2023-09-19
*/
/**v1.0.0
*0x8868ae6De5e723e6840Cdc21370e415bF5123684
*Submitted for verification at FtmScan.com on 2022-11-04
*/
/**
* EQUALIZER EXCHANGE
* The New Liquidity Hub of Fantom chain!
* https://equalizer.exchange (Dapp)
* https://discord.gg/MaMhbgHMby (Community)
*
*
* Version: 1.4.0
* - Remove the whole concept of Internal Bribes (Trade Fees Streamer), Unify.
* - Prevent breaking of rewards in earned() from reading 0-supply checkpoints
* - Voter.team() can siphon out unclaimed rewards.
* - This contract does not take or allow deposits of any user funds.
* - Only the Bribes can be rescue()'d.
*
*
* Contributors:
* - Andre Cronje, Solidly.Exchange
* - Team, Velodrome.finance
* - @smartcoding51, Equalizer.exchange
* - 543#3017 (Sam), ftm.guru & Equalizer.exchange
*
*
* SPDX-License-Identifier: UNLICENSED
*/
// File: contracts/interfaces/IGauge.sol
pragma solidity 0.8.9;
interface IGauge {
function notifyRewardAmount(address token, uint amount) external;
function getReward(address account, address[] memory tokens) external;
function claimFees() external returns (uint claimed0, uint claimed1);
function left(address token) external view returns (uint);
function isForPair() external view returns (bool);
}
// File: contracts/interfaces/IVotingEscrow.sol
pragma solidity 0.8.9;
interface IVotingEscrow {
struct Point {
int128 bias;
int128 slope; // # -dweight / dt
uint256 ts;
uint256 blk; // block
}
function team() external returns (address);
function epoch() external view returns (uint);
function token() external view returns (address);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint);
function point_history(uint loc) external view returns (Point memory);
function user_point_history(uint tokenId, uint loc) external view returns (Point memory);
function user_point_epoch(uint tokenId) external view returns (uint);
function ownerOf(uint) external view returns (address);
function isApprovedOrOwner(address, uint) external view returns (bool);
function transferFrom(address, address, uint) external;
function voting(uint tokenId) external;
function abstain(uint tokenId) external;
function attach(uint tokenId) external;
function detach(uint tokenId) external;
function checkpoint() external;
function deposit_for(uint tokenId, uint value) external;
function create_lock_for(uint, uint, address) external returns (uint);
function balanceOf(address) external view returns (uint);
function balanceOfNFT(uint) external view returns (uint);
function totalSupply() external view returns (uint);
function tokenOfOwnerByIndex(address _owner, uint _tokenIndex) external view returns (uint);
}
// File: contracts/interfaces/IVoter.sol
pragma solidity 0.8.9;
interface IVoter {
function _ve() external view returns (address);
function governor() external view returns (address);
function emergencyCouncil() external view returns (address);
function attachTokenToGauge(uint _tokenId, address account) external;
function detachTokenFromGauge(uint _tokenId, address account) external;
function emitDeposit(uint _tokenId, address account, uint amount) external;
function emitWithdraw(uint _tokenId, address account, uint amount) external;
function isWhitelisted(address token) external view returns (bool);
function notifyRewardAmount(uint amount) external;
function distribute(address _gauge) external;
}
// File: contracts/interfaces/IERC20.sol
pragma solidity 0.8.9;
interface IERC20 {
function totalSupply() external view returns (uint256);
function transfer(address recipient, uint amount) external returns (bool);
function decimals() external view returns (uint8);
function symbol() external view returns (string memory);
function balanceOf(address) external view returns (uint);
function transferFrom(address sender, address recipient, uint amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}
// File: contracts/libraries/Math.sol
pragma solidity 0.8.9;
library Math {
function max(uint a, uint b) internal pure returns (uint) {
return a >= b ? a : b;
}
function min(uint a, uint b) internal pure returns (uint) {
return a < b ? a : b;
}
function sqrt(uint y) internal pure returns (uint z) {
if (y > 3) {
z = y;
uint x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
function cbrt(uint256 n) internal pure returns (uint256) { unchecked {
uint256 x = 0;
for (uint256 y = 1 << 255; y > 0; y >>= 3) {
x <<= 1;
uint256 z = 3 * x * (x + 1) + 1;
if (n / y >= z) {
n -= y * z;
x += 1;
}
}
return x;
}}
}
// File: @openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// File: @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original
* initialization step. This is essential to configure modules that are added through upgrades and that require
* initialization.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized < type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
}
// File: contracts/Bribe.sol
pragma solidity 0.8.9;
/**
* @notice Bribes pay out rewards for a given pool based on the votes that were received from the user
* goes hand in hand with Voter.vote()
*/
contract UnifiedBribe is Initializable {
/// @notice A checkpoint for marking balance
struct Checkpoint {
uint timestamp;
uint balanceOf;
}
/// @notice A checkpoint for marking supply
struct SupplyCheckpoint {
uint timestamp;
uint supply;
}
address public voter; // only voter can modify balances (since it only happens on vote())
address public _ve;
uint public constant DURATION = 7 days; // rewards are released over the voting period
uint internal constant MAX_REWARD_TOKENS = 8;
uint internal constant PRECISION = 10 ** 18;
uint public totalSupply;
mapping(uint => uint) public balanceOfId;
mapping(address => mapping(uint => uint)) public tokenRewardsPerEpoch;
mapping(address => uint) public periodFinish;
mapping(address => mapping(uint => uint)) public lastEarn;
address[] public rewards;
mapping(address => bool) public isReward;
/// @notice A record of balance checkpoints for each account, by index
mapping (uint => mapping (uint => Checkpoint)) public checkpoints;
/// @notice The number of checkpoints for each account
mapping (uint => uint) public numCheckpoints;
/// @notice A record of balance checkpoints for each token, by index
mapping (uint => SupplyCheckpoint) public supplyCheckpoints;
/// @notice The number of checkpoints
uint public supplyNumCheckpoints;
/// @notice simple re-entrancy check
bool internal _locked;
/// Helper vars for analytics, read-only
mapping(address => uint) public payouts; /// token -> amountSinceGenesis
mapping(address => uint) public payoutHistory; /// token -> amountSinceGenesis
mapping(address => mapping(address => uint)) public earnings; /// user -> token -> amountSinceGenesis
mapping(uint => mapping(address => uint)) public rewardHistory; /// epoch -> token -> amountDuringThatEpoch
event Transfer(address indexed from, address indexed to, uint value);
event Deposit(address indexed voter, address indexed owner, uint indexed tokenId, uint amount);
event Withdraw(address indexed voter, address indexed owner, uint indexed tokenId, uint amount);
event NotifyReward(address indexed from, address indexed reward, uint indexed epoch, uint amount);
event ClaimRewards(address indexed from, address indexed reward, uint indexed tokenId, uint amount);
event ClaimRewardsForDelegate(address indexed owner, address indexed claimer, address indexed reward, uint tokenId, uint amount);
modifier lock() {
require(!_locked, "No re-entrancy");
_locked = true;
_;
_locked = false;
}
function initialize(address _voter, address[] memory _allowedRewardTokens) public initializer {
voter = _voter;
_ve = IVoter(_voter)._ve();
for (uint i; i < _allowedRewardTokens.length; i++) {
if (_allowedRewardTokens[i] != address(0)) {
isReward[_allowedRewardTokens[i]] = true;
rewards.push(_allowedRewardTokens[i]);
}
}
}
function _bribeStart(uint timestamp) internal pure returns (uint) {
return timestamp - (timestamp % (DURATION));
}
function getEpochStart(uint timestamp) public pure returns (uint) {
uint bribeStart = _bribeStart(timestamp);
uint bribeEnd = bribeStart + DURATION;
return timestamp < bribeEnd ? bribeStart : bribeStart + DURATION;
}
/**
* @notice Determine the prior balance for an account as of a block number
* @dev Block number must be a finalized block or else this function will revert to prevent misinformation.
* @param tokenId The token of the NFT to check
* @param timestamp The timestamp to get the balance at
* @return The balance the account had as of the given block
*/
function getPriorBalanceIndex(uint tokenId, uint timestamp) public view returns (uint) {
uint nCheckpoints = numCheckpoints[tokenId];
if (nCheckpoints == 0) {
return 0;
}
// First check most recent balance
if (checkpoints[tokenId][nCheckpoints - 1].timestamp <= timestamp) {
return (nCheckpoints - 1);
}
// Next check implicit zero balance
if (checkpoints[tokenId][0].timestamp > timestamp) {
return 0;
}
uint lower = 0;
uint upper = nCheckpoints - 1;
while (upper > lower) {
uint center = upper - (upper - lower) / 2; // ceil, avoiding overflow
Checkpoint memory cp = checkpoints[tokenId][center];
if (cp.timestamp == timestamp) {
return center;
} else if (cp.timestamp < timestamp) {
lower = center;
} else {
upper = center - 1;
}
}
return lower;
}
function getPriorSupplyIndex(uint timestamp) public view returns (uint) {
uint nCheckpoints = supplyNumCheckpoints;
if (nCheckpoints == 0) {
return 0;
}
// First check most recent balance
if (supplyCheckpoints[nCheckpoints - 1].timestamp <= timestamp) {
return (nCheckpoints - 1);
}
// Next check implicit zero balance
if (supplyCheckpoints[0].timestamp > timestamp) {
return 0;
}
uint lower = 0;
uint upper = nCheckpoints - 1;
while (upper > lower) {
uint center = upper - (upper - lower) / 2; // ceil, avoiding overflow
SupplyCheckpoint memory cp = supplyCheckpoints[center];
if (cp.timestamp == timestamp) {
return center;
} else if (cp.timestamp < timestamp) {
lower = center;
} else {
upper = center - 1;
}
}
return lower;
}
function name() external view returns (string memory) {
return string(abi.encodePacked(IVotingEscrow(_ve).name()," Votes",unicode" 🗳️"));
}
function symbol() external view returns (string memory) {
return string(abi.encodePacked(IVotingEscrow(_ve).symbol(),".votes"));
}
function decimals() external view returns (uint256) {
return IVotingEscrow(_ve).decimals();
}
function _writeCheckpoint(uint tokenId, uint balance) internal {
uint _timestamp = block.timestamp;
uint _nCheckPoints = numCheckpoints[tokenId];
if (_nCheckPoints > 0 && checkpoints[tokenId][_nCheckPoints - 1].timestamp == _timestamp) {
checkpoints[tokenId][_nCheckPoints - 1].balanceOf = balance;
} else {
checkpoints[tokenId][_nCheckPoints] = Checkpoint(_timestamp, balance);
numCheckpoints[tokenId] = _nCheckPoints + 1;
}
}
function _writeSupplyCheckpoint() internal {
uint _nCheckPoints = supplyNumCheckpoints;
uint _timestamp = block.timestamp;
if (_nCheckPoints > 0 && supplyCheckpoints[_nCheckPoints - 1].timestamp == _timestamp) {
supplyCheckpoints[_nCheckPoints - 1].supply = totalSupply;
} else {
supplyCheckpoints[_nCheckPoints] = SupplyCheckpoint(_timestamp, totalSupply);
supplyNumCheckpoints = _nCheckPoints + 1;
}
}
function rewardsListLength() external view returns (uint) {
return rewards.length;
}
// returns the last time the reward was modified or periodFinish if the reward has ended
function lastTimeRewardApplicable(address token) public view returns (uint) {
return Math.min(block.timestamp, periodFinish[token]);
}
// allows a user to claim rewards for a given token
function getReward(uint tokenId, address[] memory tokens) external lock {
require(IVotingEscrow(_ve).isApprovedOrOwner(msg.sender, tokenId), "Neither approved nor owner");
address _owner = IVotingEscrow(_ve).ownerOf(tokenId);
for (uint i = 0; i < tokens.length; i++) {
uint _reward = earned(tokens[i], tokenId);
lastEarn[tokens[i]][tokenId] = block.timestamp;
if (_reward > 0) _safeTransfer(tokens[i], msg.sender, _reward);
emit ClaimRewards(_owner, tokens[i], tokenId, _reward);
payouts[tokens[i]] += _reward;
earnings[_owner][tokens[i]] += _reward;
if(_owner != msg.sender) {
emit ClaimRewardsForDelegate(_owner, msg.sender, tokens[i], tokenId, _reward);
}
}
}
// used by Voter to allow batched reward claims
function getRewardForOwner(uint tokenId, address[] memory tokens) external lock {
require(msg.sender == voter, "Not voter");
address _owner = IVotingEscrow(_ve).ownerOf(tokenId);
for (uint i = 0; i < tokens.length; i++) {
uint _reward = earned(tokens[i], tokenId);
lastEarn[tokens[i]][tokenId] = block.timestamp;
if (_reward > 0) _safeTransfer(tokens[i], _owner, _reward);
emit ClaimRewards(_owner, tokens[i], tokenId, _reward);
payouts[tokens[i]] += _reward;
earnings[_owner][tokens[i]] += _reward;
}
}
function earned(address token, uint tokenId) public view returns (uint) {
if (numCheckpoints[tokenId] == 0) {
return 0;
}
uint reward = 0;
uint _ts = 0;
uint _bal = 0;
uint _supply = 1;
uint _index = 0;
uint _currTs = _bribeStart(lastEarn[token][tokenId]); // take epoch last claimed in as starting point
_index = getPriorBalanceIndex(tokenId, _currTs);
_ts = checkpoints[tokenId][_index].timestamp;
_bal = checkpoints[tokenId][_index].balanceOf;
// accounts for case where lastEarn is before first checkpoint
_currTs = Math.max(_currTs, _bribeStart(_ts));
// get epochs between current epoch and first checkpoint in same epoch as last claim
uint numEpochs = (_bribeStart(block.timestamp) - _currTs) / DURATION;
if (numEpochs > 0) {
for (uint256 i = 0; i < numEpochs; i++) {
// get index of last checkpoint in this epoch
_index = getPriorBalanceIndex(tokenId, _currTs + DURATION);
// get checkpoint in this epoch
_ts = checkpoints[tokenId][_index].timestamp;
_bal = checkpoints[tokenId][_index].balanceOf;
// get supply of last checkpoint in this epoch
_supply = supplyCheckpoints[getPriorSupplyIndex(_currTs + DURATION)].supply;
/// Bribes but no voters? Let admin decide, via rescue.
/// Previously this bricked claims for all (division by Zero)
if (_supply > 0) {
reward += _bal * tokenRewardsPerEpoch[token][_currTs] / _supply;
}
_currTs += DURATION;
}
}
return reward;
}
// Total User Votes
function balanceOf(uint _vid) external view returns(uint _bal) {
_bal = balanceOfId[_vid];
}
// Total User Votes
function balanceOf(address _usr) external view returns(uint _bal) {
IVotingEscrow _VE = IVotingEscrow(_ve);
uint _n = _VE.balanceOf(_usr);
for(uint i;i<_n;i++) {
_bal += balanceOfId[_VE.tokenOfOwnerByIndex(_usr, i)];
}
}
// This is an external function, but internal notation is used since it can only be called "internally" from Gauges
function _deposit(uint amount, uint tokenId, address _vtr, address _onr) external {
require(msg.sender == voter, "Not voter");
require(amount > 0, "Zero amount");
totalSupply += amount;
balanceOfId[tokenId] += amount;
_writeCheckpoint(tokenId, balanceOfId[tokenId]);
_writeSupplyCheckpoint();
emit Deposit(_vtr, _onr, tokenId, amount);
emit Transfer(address(0), _onr, amount);
}
function _withdraw(uint amount, uint tokenId, address _vtr, address _onr) external {
require(msg.sender == voter, "Not voter");
totalSupply -= amount;
balanceOfId[tokenId] -= amount;
_writeCheckpoint(tokenId, balanceOfId[tokenId]);
_writeSupplyCheckpoint();
emit Withdraw(_vtr, _onr, tokenId, amount);
emit Transfer(_onr, address(0), amount);
}
function left(address token) external view returns (uint) {
uint adjustedTstamp = getEpochStart(block.timestamp);
return tokenRewardsPerEpoch[token][adjustedTstamp];
}
function notifyRewardAmount(address token, uint amount) external lock {
if (!isReward[token] && !(IVotingEscrow(_ve).team()==msg.sender)) {
require(IVoter(voter).isWhitelisted(token), "bribe tokens must be whitelisted");
require(rewards.length < MAX_REWARD_TOKENS, "too many rewards tokens");
}
// bribes kick in at the start of next bribe period
uint adjustedTstamp = getEpochStart(block.timestamp);
uint epochRewards = tokenRewardsPerEpoch[token][adjustedTstamp];
uint rtbb = IERC20(token).balanceOf(address(this));
_safeTransferFrom(token, msg.sender, address(this), amount);
uint rtba = IERC20(token).balanceOf(address(this));
amount = rtba - rtbb;
require(amount > 0, "Amount must be greater than 0");
tokenRewardsPerEpoch[token][adjustedTstamp] = epochRewards + amount;
periodFinish[token] = adjustedTstamp + DURATION;
if (!isReward[token]) {
isReward[token] = true;
rewards.push(token);
}
emit NotifyReward(msg.sender, token, adjustedTstamp, amount);
rewardHistory[adjustedTstamp][token] += amount;
payoutHistory[token] += amount;
}
function swapOutRewardToken(uint i, address oldToken, address newToken) external {
require(msg.sender == IVotingEscrow(_ve).team(), "only team");
require(rewards[i] == oldToken);
isReward[oldToken] = false;
isReward[newToken] = true;
rewards[i] = newToken;
}
/// This can break claims of bribes! Useful during a platform-wide upgrade (optional)
function rescue(uint _amt, address _token, address _to) external {
require(msg.sender == IVotingEscrow(_ve).team(), "only team");
IERC20(_token).transfer(_to, _amt);
}
function _safeTransfer(address token, address to, uint256 value) internal {
require(token.code.length > 0, "Invalid token address");
(bool success, bytes memory data) =
token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "UnifiedBribe: TransferFrom failed");
}
function _safeTransferFrom(address token, address from, address to, uint256 value) internal {
require(token.code.length > 0, "Invalid token address");
(bool success, bytes memory data) =
token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "UnifiedBribe: TransferFrom failed");
}
}
// File: contracts/interfaces/IBribeFactory.sol
pragma solidity 0.8.9;
interface IBribeFactory {
function createBribe(address[] memory) external returns (address);
}
// File: contracts/factories/BribeFactory.sol
pragma solidity 0.8.9;
contract BribeFactory is IBribeFactory {
address public lastBribe;
function createBribe(address[] memory _allowedRewards) external returns (address) {
UnifiedBribe unifiedBribe = new UnifiedBribe();
unifiedBribe.initialize(msg.sender, _allowedRewards);
lastBribe = address(unifiedBribe);
return address(unifiedBribe);
}
}