in this contract // SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.13;

import {CoreRef} from “@src/core/CoreRef.sol”;
import {CoreRoles} from “@src/core/CoreRoles.sol”;
import {SimplePSM} from “@src/loan/SimplePSM.sol”;
import {GuildToken} from “@src/tokens/GuildToken.sol”;
import {CreditToken} from “@src/tokens/CreditToken.sol”;

/**
@title ProfitManager
@author eswak
@notice This contract manages profits generated in the system and how it is distributed
between the various stakeholders.

This contract also manages a surplus buffer, which acts as first-loss capital in case of
bad debt. When bad debt is created beyond the surplus buffer, this contract decrements
the creditMultiplier value held in its storage, which has the effect of reducing the
value of CREDIT everywhere in the system.

When a loan generates profit (interests), the profit is traced back to users voting for
this lending term (gauge), which subsequently allows pro-rata distribution of profits to
GUILD holders that vote for the most productive gauges.

Seniority stack of the debt, in case of losses :
- per term surplus buffer (donated to global surplus buffer when loss is reported)
- global surplus buffer
- finally, credit holders (by updating down the creditMultiplier)
*/
contract ProfitManager is CoreRef {
/// @notice reference to GUILD token.
address public guild;

/// @notice reference to CREDIT token.
address public credit;

/// @notice reference to CREDIT token PSM.
address public psm;

/// @notice profit index of a given gauge
mapping(address => uint256) public gaugeProfitIndex;

/// @notice profit index of a given user in a given gauge
mapping(address => mapping(address => uint256)) public userGaugeProfitIndex;

/// @dev internal structure used to optimize storage read, public functions use
/// uint256 numbers with 18 decimals.
struct ProfitSharingConfig {
uint32 surplusBufferSplit; // percentage, with 9 decimals (!) that go to surplus buffer
uint32 guildSplit; // percentage, with 9 decimals (!) that go to GUILD holders
uint32 otherSplit; // percentage, with 9 decimals (!) that go to other address if != address(0)
address otherRecipient; // address receiving otherSplit
}

/// @notice configuration of profit sharing.
/// surplusBufferSplit, guildSplit, and otherSplit are expressed as percentages with 9 decimals,
/// so a value of 1e9 would direct 100% of profits. The sum should be <= 1e9.
/// The rest (if the sum of guildSplit + otherSplit is < 1e9) is distributed to lenders of the
/// system, CREDIT holders, through the rebasing mechanism (CreditToken.distribute()).
/// If otherRecipient is set to address(0), otherSplit must equal 0.
/// The share of profit to otherRecipient is sent through a regular ERC20.transfer().
/// This structure is optimized for storage packing, all external interfaces reference
/// percentages encoded as uint256 with 18 decimals.
ProfitSharingConfig internal profitSharingConfig;

/// @notice amount of first-loss capital in the system.
/// This is a number of CREDIT token held on this contract that can be used to absorb losses in
/// cases where a loss is reported through notifyPnL. The surplus buffer is depleted first, and
/// if the loss is greater than the surplus buffer, the creditMultiplier is updated down.
uint256 public surplusBuffer;

/// @notice amount of first-loss capital for a given term.
/// This is a number of CREDIT token held on this contract that can be used to absorb losses in
/// cases where a loss is reported through notifyPnL in a given term.
/// When a loss is reported in a given term, its termSuplusBuffer is donated to the general
/// surplusBuffer before calculating the loss.
mapping(address => uint256) public termSurplusBuffer;

/// @notice multiplier for CREDIT value in the system.
/// e.g. a value of 0.7e18 would mean that CREDIT has been discounted by 30% so far in the system,
/// and that all lending terms will allow 1/0.7=1.42 times more CREDIT to be borrowed per collateral
/// tokens, and all active debts are also affected by this multiplier during the update (e.g. if an
/// address owed 1000 CREDIT in an active loan, they now owe 1428 CREDIT).
/// The CREDIT multiplier can only go down (CREDIT can only lose value over time, when bad debt
/// is created in the system). To make CREDIT a valuable asset to hold, profits generated by the system
/// shall be redistributed to holders through a savings rate or another mechanism.
uint256 public creditMultiplier = 1e18;

/// @notice minimum size of CREDIT loans.
/// this parameter is here to ensure that the gas costs of liquidation do not
/// outsize minimum overcollateralization (which could result in bad debt
/// on otherwise sound loans).
/// This value is adjusted up when the creditMultiplier goes down.
uint256 internal _minBorrow = 100e18;

/// @notice tolerance on new borrows regarding gauge weights.
/// For a total supply or 100 credit, and 2 gauges each at 50% weight,
/// the ideal borrow amount for each gauge is 50 credit. To facilitate
/// growth of the protocol, a tolerance is allowed compared to the ideal
/// gauge weights.
/// This tolerance is expressed as a percentage with 18 decimals.
/// A tolerance of 1e18 (100% - or 0% deviation compared to ideal weights)
/// can result in a deadlock situation where no new borrows are allowed.
uint256 public gaugeWeightTolerance = 1.2e18; // 120%

constructor(address _core) CoreRef(_core) {
emit MinBorrowUpdate(block.timestamp, 100e18);
}

/// @notice emitted when a profit or loss in a gauge is notified.
event GaugePnL(address indexed gauge, uint256 indexed when, int256 pnl);

/// @notice emitted when surplus buffer is updated.
event SurplusBufferUpdate(uint256 indexed when, uint256 newValue);

/// @notice emitted when surplus buffer of a given term is updated.
event TermSurplusBufferUpdate(
uint256 indexed when,
address indexed term,
uint256 newValue
);

/// @notice emitted when CREDIT multiplier is updated.
event CreditMultiplierUpdate(uint256 indexed when, uint256 newValue);

/// @notice emitted when GUILD profit sharing is updated.
event ProfitSharingConfigUpdate(
uint256 indexed when,
uint256 surplusBufferSplit,
uint256 creditSplit,
uint256 guildSplit,
uint256 otherSplit,
address otherRecipient
);

/// @notice emitted when a GUILD member claims their CREDIT rewards.
event ClaimRewards(
uint256 indexed when,
address indexed user,
address indexed gauge,
uint256 amount
);

/// @notice emitted when minBorrow is updated
event MinBorrowUpdate(uint256 indexed when, uint256 newValue);

/// @notice emitted when gaugeWeightTolerance is updated
event GaugeWeightToleranceUpdate(uint256 indexed when, uint256 newValue);

/// @notice get the minimum borrow amount
function minBorrow() external view returns (uint256) {
return (_minBorrow * 1e18) / creditMultiplier;
}

/// @notice initialize references to GUILD & CREDIT tokens.
function initializeReferences(
address _credit,
address _guild,
address _psm
) external onlyCoreRole(CoreRoles.GOVERNOR) {
assert(
credit == address(0) && guild == address(0) && psm == address(0)
);
credit = _credit;
guild = _guild;
psm = _psm;
}

/// @notice returns the sum of all borrowed CREDIT, not including unpaid interests
/// and creditMultiplier changes that could make debt amounts higher than the initial
/// borrowed CREDIT amounts.
function totalBorrowedCredit() external view returns (uint256) {
return
CreditToken(credit).targetTotalSupply() -
SimplePSM(psm).redeemableCredit();
}

/// @notice set the minimum borrow amount
function setMinBorrow(
uint256 newValue
) external onlyCoreRole(CoreRoles.GOVERNOR) {
_minBorrow = newValue;
emit MinBorrowUpdate(block.timestamp, newValue);
}

/// @notice set the gauge weight tolerance
function setGaugeWeightTolerance(
uint256 newValue
) external onlyCoreRole(CoreRoles.GOVERNOR) {
gaugeWeightTolerance = newValue;
emit GaugeWeightToleranceUpdate(block.timestamp, newValue);
}

/// @notice set the profit sharing config.
function setProfitSharingConfig(
uint256 surplusBufferSplit,
uint256 creditSplit,
uint256 guildSplit,
uint256 otherSplit,
address otherRecipient
) external onlyCoreRole(CoreRoles.GOVERNOR) {
if (otherRecipient == address(0)) {
require(otherSplit == 0, “GuildToken: invalid config”);
} else {
require(otherSplit != 0, “GuildToken: invalid config”);
}
require(
surplusBufferSplit + otherSplit + guildSplit + creditSplit == 1e18,
“GuildToken: invalid config”
);

profitSharingConfig = ProfitSharingConfig({
surplusBufferSplit: uint32(surplusBufferSplit / 1e9),
guildSplit: uint32(guildSplit / 1e9),
otherSplit: uint32(otherSplit / 1e9),
otherRecipient: otherRecipient
});

emit ProfitSharingConfigUpdate(
block.timestamp,
surplusBufferSplit,
creditSplit,
guildSplit,
otherSplit,
otherRecipient
);
}

/// @notice get the profit sharing config.
function getProfitSharingConfig()
external
view
returns (
uint256 surplusBufferSplit,
uint256 creditSplit,
uint256 guildSplit,
uint256 otherSplit,
address otherRecipient
)
{
surplusBufferSplit =
uint256(profitSharingConfig.surplusBufferSplit) *
1e9;
guildSplit = uint256(profitSharingConfig.guildSplit) * 1e9;
otherSplit = uint256(profitSharingConfig.otherSplit) * 1e9;
creditSplit = 1e18 - surplusBufferSplit - guildSplit - otherSplit;
otherRecipient = profitSharingConfig.otherRecipient;
}

/// @notice donate to surplus buffer
function donateToSurplusBuffer(uint256 amount) external {
CreditToken(credit).transferFrom(msg.sender, address(this), amount);
uint256 newSurplusBuffer = surplusBuffer + amount;
surplusBuffer = newSurplusBuffer;
emit SurplusBufferUpdate(block.timestamp, newSurplusBuffer);
}

/// @notice donate to surplus buffer of a given term
function donateToTermSurplusBuffer(address term, uint256 amount) external {
CreditToken(credit).transferFrom(msg.sender, address(this), amount);
uint256 newSurplusBuffer = termSurplusBuffer[term] + amount;
termSurplusBuffer[term] = newSurplusBuffer;
emit TermSurplusBufferUpdate(block.timestamp, term, newSurplusBuffer);
}

/// @notice withdraw from surplus buffer
function withdrawFromSurplusBuffer(
address to,
uint256 amount
) external onlyCoreRole(CoreRoles.GUILD_SURPLUS_BUFFER_WITHDRAW) {
uint256 newSurplusBuffer = surplusBuffer - amount; // this would revert due to underflow if withdrawing > surplusBuffer
surplusBuffer = newSurplusBuffer;
CreditToken(credit).transfer(to, amount);
emit SurplusBufferUpdate(block.timestamp, newSurplusBuffer);
}

/// @notice withdraw from surplus buffer of a given term
function withdrawFromTermSurplusBuffer(
address term,
address to,
uint256 amount
) external onlyCoreRole(CoreRoles.GUILD_SURPLUS_BUFFER_WITHDRAW) {
uint256 newSurplusBuffer = termSurplusBuffer[term] - amount; // this would revert due to underflow if withdrawing > termSurplusBuffer
termSurplusBuffer[term] = newSurplusBuffer;
CreditToken(credit).transfer(to, amount);
emit TermSurplusBufferUpdate(block.timestamp, term, newSurplusBuffer);
}

/// @notice notify profit and loss in a given gauge
/// if amount is > 0, the same number of CREDIT tokens are expected to be transferred to this contract
/// before notifyPnL is called.
function notifyPnL(
address gauge,
int256 amount
) external onlyCoreRole(CoreRoles.GAUGE_PNL_NOTIFIER) {
uint256 _surplusBuffer = surplusBuffer;
uint256 _termSurplusBuffer = termSurplusBuffer[gauge];
address _credit = credit;

// handling loss
if (amount < 0) {
uint256 loss = uint256(-amount);

// save gauge loss
GuildToken(guild).notifyGaugeLoss(gauge);

// deplete the term surplus buffer, if any, and
// donate its content to the general surplus buffer
if (_termSurplusBuffer != 0) {
termSurplusBuffer[gauge] = 0;
emit TermSurplusBufferUpdate(block.timestamp, gauge, 0);
_surplusBuffer += _termSurplusBuffer;
}

if (loss < _surplusBuffer) {
// deplete the surplus buffer
surplusBuffer = _surplusBuffer - loss;
emit SurplusBufferUpdate(
block.timestamp,
_surplusBuffer - loss
);
CreditToken(_credit).burn(loss);
} else {
// empty the surplus buffer
loss -= _surplusBuffer;
surplusBuffer = 0;
CreditToken(_credit).burn(_surplusBuffer);
emit SurplusBufferUpdate(block.timestamp, 0);

// update the CREDIT multiplier
uint256 creditTotalSupply = CreditToken(_credit).totalSupply();
uint256 newCreditMultiplier = (creditMultiplier *
(creditTotalSupply - loss)) / creditTotalSupply;
creditMultiplier = newCreditMultiplier;
emit CreditMultiplierUpdate(
block.timestamp,
newCreditMultiplier
);
}
}
// handling profit
else if (amount > 0) {
ProfitSharingConfig
memory _profitSharingConfig = profitSharingConfig;

uint256 amountForSurplusBuffer = (uint256(amount) *
uint256(_profitSharingConfig.surplusBufferSplit)) / 1e9;

uint256 amountForGuild = (uint256(amount) *
uint256(_profitSharingConfig.guildSplit)) / 1e9;

uint256 amountForOther = (uint256(amount) *
uint256(_profitSharingConfig.otherSplit)) / 1e9;

uint256 amountForCredit = uint256(amount) -
amountForSurplusBuffer -
amountForGuild -
amountForOther;

// distribute to surplus buffer
if (amountForSurplusBuffer != 0) {
surplusBuffer = _surplusBuffer + amountForSurplusBuffer;
emit SurplusBufferUpdate(
block.timestamp,
_surplusBuffer + amountForSurplusBuffer
);
}

// distribute to other
if (amountForOther != 0) {
CreditToken(_credit).transfer(
_profitSharingConfig.otherRecipient,
amountForOther
);
}

// distribute to lenders
if (amountForCredit != 0) {
CreditToken(_credit).distribute(amountForCredit);
}

// distribute to the guild
if (amountForGuild != 0) {
// update the gauge profit index
// if the gauge has 0 weight, does not update the profit index, this is unnecessary
// because the profit index is used to reattribute profit to users voting for the gauge,
// and if the weigth is 0, there are no users voting for the gauge.
uint256 _gaugeWeight = uint256(
GuildToken(guild).getGaugeWeight(gauge)
);
if (_gaugeWeight != 0) {
uint256 _gaugeProfitIndex = gaugeProfitIndex[gauge];
if (_gaugeProfitIndex == 0) {
_gaugeProfitIndex = 1e18;
}
gaugeProfitIndex[gauge] =
_gaugeProfitIndex +
(amountForGuild * 1e18) /
_gaugeWeight;
}
}
}

emit GaugePnL(gauge, block.timestamp, amount);
}

/// @notice claim a user’s rewards for a given gauge.
/// @dev This should be called every time the user’s weight changes in the gauge.
function claimGaugeRewards(
address user,
address gauge
) public returns (uint256 creditEarned) {
uint256 _userGaugeWeight = uint256(
GuildToken(guild).getUserGaugeWeight(user, gauge)
);
if (_userGaugeWeight == 0) {
return 0;
}
uint256 _gaugeProfitIndex = gaugeProfitIndex[gauge];
uint256 _userGaugeProfitIndex = userGaugeProfitIndex[user][gauge];
if (_gaugeProfitIndex == 0) {
_gaugeProfitIndex = 1e18;
}
if (_userGaugeProfitIndex == 0) {
_userGaugeProfitIndex = 1e18;
}
uint256 deltaIndex = _gaugeProfitIndex - _userGaugeProfitIndex;
if (deltaIndex != 0) {
creditEarned = (_userGaugeWeight * deltaIndex) / 1e18;
userGaugeProfitIndex[user][gauge] = _gaugeProfitIndex;
}
if (creditEarned != 0) {
emit ClaimRewards(block.timestamp, user, gauge, creditEarned);
CreditToken(credit).transfer(user, creditEarned);
}
}

/// @notice claim a user’s rewards across all their active gauges.
function claimRewards(
address user
) external returns (uint256 creditEarned) {
address[] memory gauges = GuildToken(guild).userGauges(user);
for (uint256 i = 0; i < gauges.length; ) {
creditEarned += claimGaugeRewards(user, gauges[i]);
unchecked {
++i;
}
}
}

/// @notice read & return pending undistributed rewards for a given user
function getPendingRewards(
address user
)
external
view
returns (
address[] memory gauges,
uint256[] memory creditEarned,
uint256 totalCreditEarned
)
{
address _guild = guild;
gauges = GuildToken(_guild).userGauges(user);
creditEarned = new uint256;

for (uint256 i = 0; i < gauges.length; ) {
address gauge = gauges[i];
uint256 _gaugeProfitIndex = gaugeProfitIndex[gauge];
uint256 _userGaugeProfitIndex = userGaugeProfitIndex[user][gauge];

if (_gaugeProfitIndex == 0) {
_gaugeProfitIndex = 1e18;
}
if (_userGaugeProfitIndex == 0) {
_userGaugeProfitIndex = 1e18;
}
uint256 deltaIndex = _gaugeProfitIndex - _userGaugeProfitIndex;
if (deltaIndex != 0) {
uint256 _userGaugeWeight = uint256(
GuildToken(_guild).getUserGaugeWeight(user, gauge)
);
creditEarned[i] = (_userGaugeWeight * deltaIndex) / 1e18;
totalCreditEarned += creditEarned[i];
}

unchecked {
++i;
}
}
}
} review this the function if the gauge’s profit index or user’s profit index for that gauge is zero upon claiming, they are both set to 1e18 by default let’s say a gaugeProfitIndex is supposed to be uninitialized for a new gauge or a gauge that has not yet generated profits then both _gaugeProfitIndex and _userGaugeProfitIndex would be set to 1e18, which incorrectly implies that profits exist to be claimed, he contract code does not differentiate between actual profits and this default state, which could erroneously reward users ,the issue it’s can allow users to claim tokens from gauges when no profits are available or when no user participation has occurred for that gauge i think it’s need to handle the initial state of profit indexes and ensure that claims can only be made when there has been genuine profit allocation to the gauge in question that what i understand or i miss something and also i would to thank you