文件 1 的 1:Gauge.sol
pragma solidity 0.8.11;
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;
}
}
interface erc20 {
function totalSupply() external view returns (uint256);
function transfer(address recipient, uint amount) external returns (bool);
function balanceOf(address) external view returns (uint);
function transferFrom(address sender, address recipient, uint amount) external returns (bool);
function approve(address spender, uint value) external returns (bool);
}
interface ve {
function token() external view returns (address);
function balanceOfNFT(uint) external view returns (uint);
function isApprovedOrOwner(address, uint) external view returns (bool);
function isUnlocked() external view returns (bool);
function locked__end(uint) external view returns (uint);
function create_lock_for(uint, uint, address) external returns (uint);
function deposit_for(uint, uint) external;
function ownerOf(uint) external view returns (address);
function transferFrom(address, address, uint) external;
}
interface IBribe {
function notifyRewardAmount(address token, uint amount) external;
function left(address token) external view returns (uint);
}
interface Voter {
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 distribute(address _gauge) external;
}
contract Gauge {
address public immutable stake;
address public immutable _ve;
address public immutable bribe;
address public immutable voter;
bool internal depositsOpen;
uint public derivedSupply;
mapping(address => uint) public derivedBalances;
uint internal constant DURATION = 7 days;
uint internal constant PRECISION = 10 ** 18;
mapping(address => uint) public rewardRate;
mapping(address => uint) public periodFinish;
mapping(address => uint) public lastUpdateTime;
mapping(address => uint) public rewardPerTokenStored;
mapping(address => mapping(address => uint)) public lastEarn;
mapping(address => mapping(address => uint)) public userRewardPerTokenStored;
mapping(address => uint) public tokenIds;
uint public totalSupply;
mapping(address => uint) public balanceOf;
address[] public rewards;
mapping(address => bool) public isReward;
struct Checkpoint {
uint timestamp;
uint balanceOf;
}
struct RewardPerTokenCheckpoint {
uint timestamp;
uint rewardPerToken;
}
struct SupplyCheckpoint {
uint timestamp;
uint supply;
}
mapping (address => mapping (uint => Checkpoint)) public checkpoints;
mapping (address => uint) public numCheckpoints;
mapping (uint => SupplyCheckpoint) public supplyCheckpoints;
uint public supplyNumCheckpoints;
mapping (address => mapping (uint => RewardPerTokenCheckpoint)) public rewardPerTokenCheckpoints;
mapping (address => uint) public rewardPerTokenNumCheckpoints;
event Deposit(address indexed from, uint tokenId, uint amount);
event Withdraw(address indexed from, uint tokenId, uint amount);
event NotifyReward(address indexed from, address indexed reward, uint amount);
event ClaimRewards(address indexed from, address indexed reward, uint amount);
constructor(address _stake, address _bribe, address __ve, address _voter) {
stake = _stake;
bribe = _bribe;
_ve = __ve;
voter = _voter;
depositsOpen = true;
_safeApprove(ve(__ve).token(), __ve, type(uint).max);
}
modifier whenDepositsOpen() {
require(depositsOpen, "This gauge is not open for deposits");
_;
}
function stopDeposits() external {
require(msg.sender == voter, "must be from voter");
depositsOpen = false;
}
function openDeposits() external {
require(msg.sender == voter, "must be from voter");
depositsOpen = true;
}
function isDepositsOpen() external view returns (bool) {
return depositsOpen;
}
uint internal _unlocked = 1;
modifier lock() {
require(_unlocked == 1);
_unlocked = 2;
_;
_unlocked = 1;
}
function getPriorBalanceIndex(address account, uint timestamp) public view returns (uint) {
uint nCheckpoints = numCheckpoints[account];
if (nCheckpoints == 0) {
return 0;
}
if (checkpoints[account][nCheckpoints - 1].timestamp <= timestamp) {
return (nCheckpoints - 1);
}
if (checkpoints[account][0].timestamp > timestamp) {
return 0;
}
uint lower = 0;
uint upper = nCheckpoints - 1;
while (upper > lower) {
uint center = upper - (upper - lower) / 2;
Checkpoint memory cp = checkpoints[account][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;
}
if (supplyCheckpoints[nCheckpoints - 1].timestamp <= timestamp) {
return (nCheckpoints - 1);
}
if (supplyCheckpoints[0].timestamp > timestamp) {
return 0;
}
uint lower = 0;
uint upper = nCheckpoints - 1;
while (upper > lower) {
uint center = upper - (upper - lower) / 2;
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 getPriorRewardPerToken(address token, uint timestamp) public view returns (uint, uint) {
uint nCheckpoints = rewardPerTokenNumCheckpoints[token];
if (nCheckpoints == 0) {
return (0,0);
}
if (rewardPerTokenCheckpoints[token][nCheckpoints - 1].timestamp <= timestamp) {
return (rewardPerTokenCheckpoints[token][nCheckpoints - 1].rewardPerToken, rewardPerTokenCheckpoints[token][nCheckpoints - 1].timestamp);
}
if (rewardPerTokenCheckpoints[token][0].timestamp > timestamp) {
return (0,0);
}
uint lower = 0;
uint upper = nCheckpoints - 1;
while (upper > lower) {
uint center = upper - (upper - lower) / 2;
RewardPerTokenCheckpoint memory cp = rewardPerTokenCheckpoints[token][center];
if (cp.timestamp == timestamp) {
return (cp.rewardPerToken, cp.timestamp);
} else if (cp.timestamp < timestamp) {
lower = center;
} else {
upper = center - 1;
}
}
return (rewardPerTokenCheckpoints[token][lower].rewardPerToken, rewardPerTokenCheckpoints[token][lower].timestamp);
}
function _writeCheckpoint(address account, uint balance) internal {
uint _timestamp = block.timestamp;
uint _nCheckPoints = numCheckpoints[account];
if (_nCheckPoints > 0 && checkpoints[account][_nCheckPoints - 1].timestamp == _timestamp) {
checkpoints[account][_nCheckPoints - 1].balanceOf = balance;
} else {
checkpoints[account][_nCheckPoints] = Checkpoint(_timestamp, balance);
numCheckpoints[account] = _nCheckPoints + 1;
}
}
function _writeRewardPerTokenCheckpoint(address token, uint reward, uint timestamp) internal {
uint _nCheckPoints = rewardPerTokenNumCheckpoints[token];
if (_nCheckPoints > 0 && rewardPerTokenCheckpoints[token][_nCheckPoints - 1].timestamp == timestamp) {
rewardPerTokenCheckpoints[token][_nCheckPoints - 1].rewardPerToken = reward;
} else {
rewardPerTokenCheckpoints[token][_nCheckPoints] = RewardPerTokenCheckpoint(timestamp, reward);
rewardPerTokenNumCheckpoints[token] = _nCheckPoints + 1;
}
}
function _writeSupplyCheckpoint() internal {
uint _nCheckPoints = supplyNumCheckpoints;
uint _timestamp = block.timestamp;
if (_nCheckPoints > 0 && supplyCheckpoints[_nCheckPoints - 1].timestamp == _timestamp) {
supplyCheckpoints[_nCheckPoints - 1].supply = derivedSupply;
} else {
supplyCheckpoints[_nCheckPoints] = SupplyCheckpoint(_timestamp, derivedSupply);
supplyNumCheckpoints = _nCheckPoints + 1;
}
}
function rewardsListLength() external view returns (uint) {
return rewards.length;
}
function lastTimeRewardApplicable(address token) public view returns (uint) {
return Math.min(block.timestamp, periodFinish[token]);
}
function getReward(address account, address[] memory tokens) external lock {
require(msg.sender == account || msg.sender == voter);
_unlocked = 1;
Voter(voter).distribute(address(this));
_unlocked = 2;
for (uint i = 0; i < tokens.length; i++) {
(rewardPerTokenStored[tokens[i]], lastUpdateTime[tokens[i]]) = _updateRewardPerToken(tokens[i]);
uint _reward = earned(tokens[i], account);
lastEarn[tokens[i]][account] = block.timestamp;
userRewardPerTokenStored[tokens[i]][account] = rewardPerTokenStored[tokens[i]];
if (_reward > 0) {
if (ve(_ve).isUnlocked()) {
_safeTransfer(tokens[i], account, _reward);
} else {
uint tokenId = tokenIds[msg.sender];
if (tokenId == 0 || block.timestamp > ve(_ve).locked__end(tokenId)) {
tokenIds[msg.sender] = ve(_ve).create_lock_for(_reward, DURATION * 8, msg.sender);
} else {
ve(_ve).deposit_for(tokenId, _reward);
}
}
}
emit ClaimRewards(msg.sender, tokens[i], _reward);
}
uint _derivedBalance = derivedBalances[account];
derivedSupply -= _derivedBalance;
_derivedBalance = derivedBalance(account);
derivedBalances[account] = _derivedBalance;
derivedSupply += _derivedBalance;
_writeCheckpoint(account, derivedBalances[account]);
_writeSupplyCheckpoint();
}
function rewardPerToken(address token) public view returns (uint) {
if (derivedSupply == 0) {
return rewardPerTokenStored[token];
}
return rewardPerTokenStored[token] + ((lastTimeRewardApplicable(token) - Math.min(lastUpdateTime[token], periodFinish[token])) * rewardRate[token] * PRECISION / derivedSupply);
}
function derivedBalance(address account) public view returns (uint) {
uint _tokenId = tokenIds[account];
uint _balance = balanceOf[account];
uint _derived = _balance * 40 / 100;
uint _adjusted = 0;
uint _supply = erc20(_ve).totalSupply();
if (account == ve(_ve).ownerOf(_tokenId) && _supply > 0 && ve(_ve).isUnlocked()) {
_adjusted = ve(_ve).balanceOfNFT(_tokenId);
_adjusted = (totalSupply * _adjusted / _supply) * 60 / 100;
}
return Math.min((_derived + _adjusted), _balance);
}
function batchRewardPerToken(address token, uint maxRuns) external {
(rewardPerTokenStored[token], lastUpdateTime[token]) = _batchRewardPerToken(token, maxRuns);
}
function _batchRewardPerToken(address token, uint maxRuns) internal returns (uint, uint) {
uint _startTimestamp = lastUpdateTime[token];
uint reward = rewardPerTokenStored[token];
if (supplyNumCheckpoints == 0) {
return (reward, _startTimestamp);
}
if (rewardRate[token] == 0) {
return (reward, block.timestamp);
}
uint _startIndex = getPriorSupplyIndex(_startTimestamp);
uint _endIndex = Math.min(supplyNumCheckpoints-1, maxRuns);
for (uint i = _startIndex; i < _endIndex; i++) {
SupplyCheckpoint memory sp0 = supplyCheckpoints[i];
if (sp0.supply > 0) {
SupplyCheckpoint memory sp1 = supplyCheckpoints[i+1];
(uint _reward, uint _endTime) = _calcRewardPerToken(token, sp1.timestamp, sp0.timestamp, sp0.supply, _startTimestamp);
reward += _reward;
_writeRewardPerTokenCheckpoint(token, reward, _endTime);
_startTimestamp = _endTime;
}
}
return (reward, _startTimestamp);
}
function _calcRewardPerToken(address token, uint timestamp1, uint timestamp0, uint supply, uint startTimestamp) internal view returns (uint, uint) {
uint endTime = Math.max(timestamp1, startTimestamp);
return (((Math.min(endTime, periodFinish[token]) - Math.min(Math.max(timestamp0, startTimestamp), periodFinish[token])) * rewardRate[token] * PRECISION / supply), endTime);
}
function _updateRewardPerToken(address token) internal returns (uint, uint) {
uint _startTimestamp = lastUpdateTime[token];
uint reward = rewardPerTokenStored[token];
if (supplyNumCheckpoints == 0) {
return (reward, _startTimestamp);
}
if (rewardRate[token] == 0) {
return (reward, block.timestamp);
}
uint _startIndex = getPriorSupplyIndex(_startTimestamp);
uint _endIndex = supplyNumCheckpoints-1;
if (_endIndex - _startIndex > 1) {
for (uint i = _startIndex; i < _endIndex-1; i++) {
SupplyCheckpoint memory sp0 = supplyCheckpoints[i];
if (sp0.supply > 0) {
SupplyCheckpoint memory sp1 = supplyCheckpoints[i+1];
(uint _reward, uint _endTime) = _calcRewardPerToken(token, sp1.timestamp, sp0.timestamp, sp0.supply, _startTimestamp);
reward += _reward;
_writeRewardPerTokenCheckpoint(token, reward, _endTime);
_startTimestamp = _endTime;
}
}
}
SupplyCheckpoint memory sp = supplyCheckpoints[_endIndex];
if (sp.supply > 0) {
(uint _reward,) = _calcRewardPerToken(token, lastTimeRewardApplicable(token), Math.max(sp.timestamp, _startTimestamp), sp.supply, _startTimestamp);
reward += _reward;
_writeRewardPerTokenCheckpoint(token, reward, block.timestamp);
_startTimestamp = block.timestamp;
}
return (reward, _startTimestamp);
}
function earned(address token, address account) public view returns (uint) {
uint _startTimestamp = Math.max(lastEarn[token][account], rewardPerTokenCheckpoints[token][0].timestamp);
if (numCheckpoints[account] == 0) {
return 0;
}
uint _startIndex = getPriorBalanceIndex(account, _startTimestamp);
uint _endIndex = numCheckpoints[account]-1;
uint reward = 0;
if (_endIndex - _startIndex > 1) {
for (uint i = _startIndex; i < _endIndex-1; i++) {
Checkpoint memory cp0 = checkpoints[account][i];
Checkpoint memory cp1 = checkpoints[account][i+1];
(uint _rewardPerTokenStored0,) = getPriorRewardPerToken(token, cp0.timestamp);
(uint _rewardPerTokenStored1,) = getPriorRewardPerToken(token, cp1.timestamp);
reward += cp0.balanceOf * (_rewardPerTokenStored1 - _rewardPerTokenStored0) / PRECISION;
}
}
Checkpoint memory cp = checkpoints[account][_endIndex];
(uint _rewardPerTokenStored,) = getPriorRewardPerToken(token, cp.timestamp);
reward += cp.balanceOf * (rewardPerToken(token) - Math.max(_rewardPerTokenStored, userRewardPerTokenStored[token][account])) / PRECISION;
return reward;
}
function depositAll(uint tokenId) external {
deposit(erc20(stake).balanceOf(msg.sender), tokenId);
}
function deposit(uint amount, uint tokenId) public whenDepositsOpen lock {
require(amount > 0);
_safeTransferFrom(stake, msg.sender, address(this), amount);
totalSupply += amount;
balanceOf[msg.sender] += amount;
if (tokenId > 0) {
require(ve(_ve).ownerOf(tokenId) == msg.sender);
if (tokenIds[msg.sender] == 0) {
tokenIds[msg.sender] = tokenId;
Voter(voter).attachTokenToGauge(tokenId, msg.sender);
}
require(tokenIds[msg.sender] == tokenId);
} else {
tokenId = tokenIds[msg.sender];
}
uint _derivedBalance = derivedBalances[msg.sender];
derivedSupply -= _derivedBalance;
_derivedBalance = derivedBalance(msg.sender);
derivedBalances[msg.sender] = _derivedBalance;
derivedSupply += _derivedBalance;
_writeCheckpoint(msg.sender, _derivedBalance);
_writeSupplyCheckpoint();
Voter(voter).emitDeposit(tokenId, msg.sender, amount);
emit Deposit(msg.sender, tokenId, amount);
}
function withdrawAll() external {
withdraw(balanceOf[msg.sender]);
}
function withdraw(uint amount) public {
uint tokenId = 0;
if (amount == balanceOf[msg.sender]) {
tokenId = tokenIds[msg.sender];
}
withdrawToken(amount, tokenId);
}
function withdrawToken(uint amount, uint tokenId) public lock {
totalSupply -= amount;
balanceOf[msg.sender] -= amount;
_safeTransfer(stake, msg.sender, amount);
if (tokenId > 0) {
require(tokenId == tokenIds[msg.sender]);
tokenIds[msg.sender] = 0;
Voter(voter).detachTokenFromGauge(tokenId, msg.sender);
} else {
tokenId = tokenIds[msg.sender];
}
uint _derivedBalance = derivedBalances[msg.sender];
derivedSupply -= _derivedBalance;
_derivedBalance = derivedBalance(msg.sender);
derivedBalances[msg.sender] = _derivedBalance;
derivedSupply += _derivedBalance;
_writeCheckpoint(msg.sender, derivedBalances[msg.sender]);
_writeSupplyCheckpoint();
Voter(voter).emitWithdraw(tokenId, msg.sender, amount);
emit Withdraw(msg.sender, tokenId, amount);
}
function left(address token) external view returns (uint) {
if (block.timestamp >= periodFinish[token]) return 0;
uint _remaining = periodFinish[token] - block.timestamp;
return _remaining * rewardRate[token];
}
function notifyRewardAmount(address token, uint amount) external lock {
require(token != stake);
require(amount > 0);
if (rewardRate[token] == 0) _writeRewardPerTokenCheckpoint(token, 0, block.timestamp);
(rewardPerTokenStored[token], lastUpdateTime[token]) = _updateRewardPerToken(token);
if (block.timestamp >= periodFinish[token]) {
_safeTransferFrom(token, msg.sender, address(this), amount);
rewardRate[token] = amount / DURATION;
} else {
uint _remaining = periodFinish[token] - block.timestamp;
uint _left = _remaining * rewardRate[token];
require(amount > _left);
_safeTransferFrom(token, msg.sender, address(this), amount);
rewardRate[token] = (amount + _left) / DURATION;
}
require(rewardRate[token] > 0);
uint balance = erc20(token).balanceOf(address(this));
require(rewardRate[token] <= balance / DURATION, "Provided reward too high");
periodFinish[token] = block.timestamp + DURATION;
if (!isReward[token]) {
isReward[token] = true;
rewards.push(token);
}
emit NotifyReward(msg.sender, token, amount);
}
function _safeTransfer(address token, address to, uint256 value) internal {
require(token.code.length > 0);
(bool success, bytes memory data) =
token.call(abi.encodeWithSelector(erc20.transfer.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))));
}
function _safeTransferFrom(address token, address from, address to, uint256 value) internal {
require(token.code.length > 0);
(bool success, bytes memory data) =
token.call(abi.encodeWithSelector(erc20.transferFrom.selector, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))));
}
function _safeApprove(address token, address spender, uint256 value) internal {
require(token.code.length > 0);
(bool success, bytes memory data) =
token.call(abi.encodeWithSelector(erc20.approve.selector, spender, value));
require(success && (data.length == 0 || abi.decode(data, (bool))));
}
}
contract GaugeFactory {
address public last_gauge;
function createGauge(address _asset, address _bribe, address _ve) external returns (address) {
last_gauge = address(new Gauge(_asset, _bribe, _ve, msg.sender));
return last_gauge;
}
function createGaugeSingle(address _asset, address _bribe, address _ve, address _voter) external returns (address) {
last_gauge = address(new Gauge(_asset, _bribe, _ve, _voter));
return last_gauge;
}
}