// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)pragmasolidity ^0.8.0;/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/interfaceIERC20{
/**
* @dev Returns the amount of tokens in existence.
*/functiontotalSupply() externalviewreturns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/functionbalanceOf(address account) externalviewreturns (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.
*/functiontransfer(address recipient, uint256 amount) externalreturns (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.
*/functionallowance(address owner, address spender) externalviewreturns (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.
*/functionapprove(address spender, uint256 amount) externalreturns (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.
*/functiontransferFrom(address sender,
address recipient,
uint256 amount
) externalreturns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/eventTransfer(addressindexedfrom, addressindexed 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.
*/eventApproval(addressindexed owner, addressindexed spender, uint256 value);
}
pragmasolidity 0.8.10;/***
*@title Liquidity Gauge
*@author InsureDAO
* SPDX-License-Identifier: MIT
*@notice Used for measuring liquidity and insurance
*///dao-contractsimport"./interfaces/dao/IGaugeController.sol";
import"./interfaces/dao/IInsureToken.sol";
import"./interfaces/dao/IMinter.sol";
import"./interfaces/dao/IVotingEscrow.sol";
import"./interfaces/pool/IOwnership.sol";
//librariesimport"@openzeppelin/contracts/token/ERC20/IERC20.sol";
import"@openzeppelin/contracts/utils/math/Math.sol";
import"@openzeppelin/contracts/security/ReentrancyGuard.sol";
contractLiquidityGaugeisReentrancyGuard{
eventDeposit(addressindexed provider, uint256 value);
eventWithdraw(addressindexed provider, uint256 value);
eventUpdateLiquidityLimit(address user,
uint256 original_balance,
uint256 original_supply,
uint256 working_balance,
uint256 working_supply,
uint256 voting_balance,
uint256 voting_total
);
uint256constant TOKENLESS_PRODUCTION =40;
uint256constant BOOST_WARMUP =86400*14;
uint256constant WEEK =604800;
//Contracts
IMinter public minter;
IInsureToken public insure_token;
IERC20 public template;
IGaugeController public controller;
IVotingEscrow public voting_escrow;
mapping(address=>uint256) public balanceOf;
uint256public totalSupply;
uint256public future_epoch_time;
// caller -> recipient -> can deposit?mapping(address=>mapping(address=>bool)) public approved_to_deposit;
mapping(address=>uint256) public working_balances;
uint256public working_supply;
// The goal is to be able to calculate ∫(rate * balance / totalSupply dt) from 0 till checkpoint// All values are kept in units of being multiplied by 1e18uint256public period; //modified from "int256 public period" since it never be minus.uint256[100000000000000000000000000000] public period_timestamp;
// 1e18 * ∫(rate(t) / totalSupply(t) dt) from 0 till checkpointuint256[100000000000000000000000000000] public integrate_inv_supply; // bump epoch when rate() changes. Iis(t)=int(r'(t)/S(t))dt (from CurveDAO whitepaper)// 1e18 * ∫(rate(t) / totalSupply(t) dt) from (last_action) till checkpointmapping(address=>uint256) public integrate_inv_supply_of;
mapping(address=>uint256) public integrate_checkpoint_of;
// ∫(balance * rate(t) / totalSupply(t) dt) from 0 till checkpoint// Units rate * t = already number of coins per address to issuemapping(address=>uint256) public integrate_fraction; //Mintable Token amount (include minted amount)uint256public inflation_rate;
boolpublic is_killed;
IOwnership publicimmutable ownership;
modifieronlyOwner() {
require(
ownership.owner() ==msg.sender,
"Caller is not allowed to operate"
);
_;
}
/***
*@notice Contract constructor
*@param _lp_addr Liquidity Pool contract address
*@param _minter Minter contract address
*@param _admin Admin who can kill the gauge
*/constructor(address _lp_addr,
address _minter,
address _ownership
) {
require(_lp_addr !=address(0));
require(_minter !=address(0));
template = IERC20(_lp_addr);
minter = IMinter(_minter);
address _insure_addr = minter.insure_token();
insure_token = IInsureToken(_insure_addr);
controller = IGaugeController(minter.gauge_controller());
voting_escrow = IVotingEscrow(controller.get_voting_escrow());
period_timestamp[0] =block.timestamp;
inflation_rate = insure_token.rate();
future_epoch_time = insure_token.future_epoch_time_write();
ownership = IOwnership(_ownership);
}
/***
*@notice Calculate limits which depend on the amount of INSURE Token per-user.
* Effectively it calculates working balances to apply amplification
* of INSURE production by INSURE
*@param _addr User address
*@param _l User's amount of liquidity (LP tokens)
*@param _L Total amount of liquidity (LP tokens)
*/function_update_liquidity_limit(address _addr,
uint256 _l,
uint256 _L
) internal{
// To be called after totalSupply is updateduint256 _voting_balance = voting_escrow.balanceOf(
_addr,
block.timestamp
);
uint256 _voting_total = voting_escrow.totalSupply(block.timestamp);
uint256 _lim = (_l * TOKENLESS_PRODUCTION) /100;
if (
(_voting_total >0) &&
(block.timestamp> period_timestamp[0] + BOOST_WARMUP)
) {
_lim +=
(_L * _voting_balance * (100- TOKENLESS_PRODUCTION)) /
_voting_total /100;
}
_lim = min(_l, _lim);
uint256 _old_bal = working_balances[_addr];
working_balances[_addr] = _lim;
uint256 _working_supply = working_supply + _lim - _old_bal;
working_supply = _working_supply;
emit UpdateLiquidityLimit(
_addr,
_l,
_L,
_lim,
_working_supply,
_voting_balance,
_voting_total
);
}
//to avoid "stack too deep"structCheckPointParameters {
uint256 period;
uint256 period_time;
uint256 integrate_inv_supply;
uint256 rate;
uint256 new_rate;
uint256 prev_future_epoch;
uint256 working_balance;
uint256 working_supply;
}
/***
*@notice Checkpoint for a user
*@param _addr User address
*
*This function does,
*1. Calculate Iis for All: Calc and add Iis for every week. Iis only increses over time.
*2. Calculate Iu for _addr: Calc by (defferece between Iis(last time) and Iis(this time))* LP deposit amount of _addr(include INSURE locking boost)
*
* working_supply & working_balance = total_supply & total_balance with INSURE locking boost。
* Check whitepaper about Iis and Iu.
*/function_checkpoint(address _addr) internal{
CheckPointParameters memory _st;
_st.period = period;
_st.period_time = period_timestamp[_st.period];
_st.integrate_inv_supply = integrate_inv_supply[_st.period];
_st.rate = inflation_rate;
_st.new_rate = _st.rate;
_st.prev_future_epoch = future_epoch_time;
if (_st.prev_future_epoch >= _st.period_time) {
//update future_epoch_time & inflation_rate
future_epoch_time = insure_token.future_epoch_time_write();
_st.new_rate = insure_token.rate();
inflation_rate = _st.new_rate;
}
controller.checkpoint_gauge(address(this));
uint256 _working_balance = working_balances[_addr];
uint256 _working_supply = working_supply;
if (is_killed) {
_st.rate =0; // Stop distributing inflation as soon as killed
}
// Update integral of 1/supplyif (block.timestamp> _st.period_time) {
uint256 _prev_week_time = _st.period_time;
uint256 _week_time;
unchecked {
_week_time = min(
((_st.period_time + WEEK) / WEEK) * WEEK,
block.timestamp
);
}
for (uint256 i; i <500;) {
uint256 _dt = _week_time - _prev_week_time;
uint256 _w = controller.gauge_relative_weight(
address(this),
(_prev_week_time / WEEK) * WEEK
);
if (_working_supply >0) {
if (
_st.prev_future_epoch >= _prev_week_time &&
_st.prev_future_epoch < _week_time
) {
// If we went across one or multiple epochs, apply the rate// of the first epoch until it ends, and then the rate of// the last epoch.// If more than one epoch is crossed - the gauge gets less,// but that'd meen it wasn't called for more than 1 year
_st.integrate_inv_supply +=
(_st.rate *
_w *
(_st.prev_future_epoch - _prev_week_time)) /
_working_supply;
_st.rate = _st.new_rate;
_st.integrate_inv_supply +=
(_st.rate *
_w *
(_week_time - _st.prev_future_epoch)) /
_working_supply;
} else {
_st.integrate_inv_supply +=
(_st.rate * _w * _dt) /
_working_supply;
}
// On precisions of the calculation// rate ~= 10e18// last_weight > 0.01 * 1e18 = 1e16 (if pool weight is 1%)// _working_supply ~= TVL * 1e18 ~= 1e26 ($100M for example)// The largest loss is at dt = 1// Loss is 1e-9 - acceptable
}
if (_week_time ==block.timestamp) {
break;
}
_prev_week_time = _week_time;
_week_time = min(_week_time + WEEK, block.timestamp);
unchecked {
++i;
}
}
}
_st.period +=1;
period = _st.period;
period_timestamp[_st.period] =block.timestamp;
integrate_inv_supply[_st.period] = _st.integrate_inv_supply;
// Update user-specific integrals// Calc the ΔIu of _addr and add it to Iu.
integrate_fraction[_addr] +=
(_working_balance *
(_st.integrate_inv_supply - integrate_inv_supply_of[_addr])) /10**18;
integrate_inv_supply_of[_addr] = _st.integrate_inv_supply;
integrate_checkpoint_of[_addr] =block.timestamp;
}
/***
*@notice Record a checkpoint for `_addr`
*@param _addr User address
*@return bool success
*/functionuser_checkpoint(address _addr) externalreturns(bool) {
require(
(msg.sender== _addr) || (msg.sender==address(minter)),
"dev: unauthorized"
);
_checkpoint(_addr);
_update_liquidity_limit(_addr, balanceOf[_addr], totalSupply);
returntrue;
}
/***
*@notice Get the number of claimable tokens per user
*@dev This function should be manually changed to "view" in the ABI
*@return uint256 number of claimable tokens per user
*/functionclaimable_tokens(address _addr) externalreturns(uint256) {
_checkpoint(_addr);
return (integrate_fraction[_addr] -
minter.minted(_addr, address(this)));
}
/***
*@notice Kick `_addr` for abusing their boost
*@dev Only if either they had another voting event, or their voting escrow lock expired
*@param _addr Address to kick
*/functionkick(address _addr) external{
uint256 _t_last = integrate_checkpoint_of[_addr];
uint256 _t_ve = voting_escrow.user_point_history__ts(
_addr,
voting_escrow.get_user_point_epoch(_addr)
);
uint256 _balance = balanceOf[_addr];
require(
voting_escrow.balanceOf(_addr, block.timestamp) ==0||
_t_ve > _t_last,
"dev: kick not allowed"
);
require(
working_balances[_addr] > (_balance * TOKENLESS_PRODUCTION) /100,
"dev: kick not needed"
);
_checkpoint(_addr);
_update_liquidity_limit(_addr, balanceOf[_addr], totalSupply);
}
/***
*@notice Set whether `_addr` can deposit tokens for `msg.sender`
*@param _addr Address to set approval on
*@param can_deposit bool - can this account deposit for `msg.sender`?
*/functionset_approve_deposit(address _addr, bool can_deposit) external{
approved_to_deposit[_addr][msg.sender] = can_deposit;
}
/***
*@notice Deposit `_value` LP tokens
*@param _value Number of tokens to deposit
*@param _addr Address to deposit for
*/functiondeposit(uint256 _value, address _addr) externalnonReentrant{
if (_addr !=msg.sender) {
require(approved_to_deposit[msg.sender][_addr], "Not approved");
}
_checkpoint(_addr);
if (_value !=0) {
uint256 _balance = balanceOf[_addr] + _value;
uint256 _supply = totalSupply + _value;
balanceOf[_addr] = _balance;
totalSupply = _supply;
_update_liquidity_limit(_addr, _balance, _supply);
require(template.transferFrom(msg.sender, address(this), _value));
}
emit Deposit(_addr, _value);
}
/***
*@notice Withdraw `_value` LP tokens
*@param _value Number of tokens to withdraw
*/functionwithdraw(uint256 _value) externalnonReentrant{
_checkpoint(msg.sender);
uint256 _balance = balanceOf[msg.sender] - _value;
uint256 _supply = totalSupply - _value;
balanceOf[msg.sender] = _balance;
totalSupply = _supply;
_update_liquidity_limit(msg.sender, _balance, _supply);
require(template.transfer(msg.sender, _value));
emit Withdraw(msg.sender, _value);
}
functionintegrate_checkpoint() externalviewreturns(uint256) {
return period_timestamp[period];
}
functionkill_me() externalonlyOwner{
is_killed =!is_killed;
}
functionmin(uint256 a, uint256 b) internalpurereturns(uint256) {
return a < b ? a : b;
}
}
Contract Source Code
File 8 of 9: Math.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (utils/math/Math.sol)pragmasolidity ^0.8.0;/**
* @dev Standard math utilities missing in the Solidity language.
*/libraryMath{
/**
* @dev Returns the largest of two numbers.
*/functionmax(uint256 a, uint256 b) internalpurereturns (uint256) {
return a >= b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/functionmin(uint256 a, uint256 b) internalpurereturns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/functionaverage(uint256 a, uint256 b) internalpurereturns (uint256) {
// (a + b) / 2 can overflow.return (a & b) + (a ^ b) /2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/functionceilDiv(uint256 a, uint256 b) internalpurereturns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.return a / b + (a % b ==0 ? 0 : 1);
}
}
Contract Source Code
File 9 of 9: ReentrancyGuard.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)pragmasolidity ^0.8.0;/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/abstractcontractReentrancyGuard{
// Booleans are more expensive than uint256 or any type that takes up a full// word because each write operation emits an extra SLOAD to first read the// slot's contents, replace the bits taken up by the boolean, and then write// back. This is the compiler's defense against contract upgrades and// pointer aliasing, and it cannot be disabled.// The values being non-zero value makes deployment a bit more expensive,// but in exchange the refund on every call to nonReentrant will be lower in// amount. Since refunds are capped to a percentage of the total// transaction's gas, it is best to keep them low in cases like this one, to// increase the likelihood of the full refund coming into effect.uint256privateconstant _NOT_ENTERED =1;
uint256privateconstant _ENTERED =2;
uint256private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/modifiernonReentrant() {
// On the first call to nonReentrant, _notEntered will be truerequire(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}