Skip to content

Commit 9be84c7

Browse files
committed
update math
1 parent 4669a4f commit 9be84c7

File tree

2 files changed

+259
-22
lines changed

2 files changed

+259
-22
lines changed

contracts/MultiplierPointMath.sol

Lines changed: 106 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,135 @@
11
// SPDX-License-Identifier: MIT-1.0
2-
pragma solidity ^0.8.18;
2+
pragma solidity ^0.8.26;
33

44
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
55

66
abstract contract MultiplierPointMath {
7-
uint256 public constant YEAR = 365 days;
8-
uint256 public constant MP_APY = 1;
7+
/// @notice One (mean) tropical year, in seconds.
8+
uint256 public constant YEAR = 365 days + 5 hours + 48 minutes + 45 seconds;
9+
/// @notice Multiplier points annual percentage yield.
10+
uint256 public constant MP_APY = 100;
11+
/// @notice Accrued multiplier points maximum multiplier.
912
uint256 public constant MAX_MULTIPLIER = 4;
13+
/// @notice The accrue rate period of time over which multiplier points are calculated.
14+
uint256 public constant ACCURE_RATE = 1 weeks;
15+
/// @notice Minimal value to generate 1 multiplier point in the accrue rate period (rounded up).
16+
uint256 public constant MIN_BALANCE = (((YEAR * 100) - 1) / (MP_APY * ACCURE_RATE)) + 1;
17+
/// @notice Multiplier points absolute maximum multiplier
18+
uint256 public constant MAX_MULTIPLIER_ABSOLUTE = 1 + (2 * (MAX_MULTIPLIER * MP_APY) / 100);
19+
/// @notice Maximum lockup period
20+
uint256 public constant MAX_LOCKUP_PERIOD = MAX_MULTIPLIER * YEAR;
1021

1122
/**
12-
* @notice Calculates multiplier points accurred for given `_amount` and `_seconds` time passed
13-
* @param _amount quantity of tokens
14-
* @param _seconds time in seconds
23+
* @notice Calculates the accrued multiplier points (MPs) over a time period Δt, based on the account balance
24+
* @param _balance Represents the current account balance
25+
* @param _deltaTime The time difference or the duration over which the multiplier points are accrued, expressed in
26+
* seconds
1527
* @return _accuredMP points accured for given `_amount` and `_seconds`
28+
* 51584438
29+
* 10000000
1630
*/
17-
function _calculateAccuredMP(uint256 _amount, uint256 _seconds) internal pure returns (uint256 _accuredMP) {
18-
return Math.mulDiv(_amount, _seconds, YEAR) * MP_APY;
31+
function _calculateAccuredMP(uint256 _balance, uint256 _deltaTime) public pure returns (uint256 _accuredMP) {
32+
return Math.mulDiv(_balance, _deltaTime * MP_APY, YEAR * 100);
1933
}
2034

2135
/**
22-
* @notice Calculates bonus multiplier points for given `_amount` and `_lockedSeconds`
36+
* @notice Calculates the bonus multiplier points (MPs) earned when a balance Δa is locked for a specified duration
37+
* t_lock.
38+
* It is equivalent to the accrued multiplier points function but specifically applied in the context of a locked
39+
* balance.
2340
* @param _amount quantity of tokens
2441
* @param _lockedSeconds time in seconds locked
2542
* @return _bonusMP bonus multiplier points for given `_amount` and `_lockedSeconds`
2643
*/
27-
function _calculateBonusMP(uint256 _amount, uint256 _lockedSeconds) internal pure returns (uint256 _bonusMP) {
28-
_bonusMP = _amount;
29-
if (_lockedSeconds > 0) {
30-
_bonusMP += _calculateAccuredMP(_amount, _lockedSeconds);
31-
}
44+
function _calculateBonusMP(uint256 _amount, uint256 _lockedSeconds) public pure returns (uint256 _bonusMP) {
45+
return _calculateAccuredMP(_amount, _lockedSeconds);
46+
}
47+
48+
/**
49+
* @notice Calculates the initial multiplier points (MPs) based on the balance change Δa. The result is equal to
50+
* the amount of balance added.
51+
* @param _amount Represents the change in balance.
52+
*/
53+
function _calculateInitialMP(uint256 _amount) public pure returns (uint256 _initialMP) {
54+
return _amount;
3255
}
3356

3457
/**
35-
* @notice Calculates minimum stake to genarate 1 multiplier points for given `_seconds`
36-
* @param _seconds time in seconds
37-
* @return _minimumStake minimum quantity of tokens
58+
* @notice Calculates the reduction in multiplier points (MPs) when a portion of the balance Δa `_reducedAmount` is
59+
* removed from the total balance a_bal `_currentBalance`.
60+
* The reduction is proportional to the ratio of the removed balance to the total balance, applied to the current
61+
* multiplier points $mp$.
62+
* @param _mp Represents the current multiplier points
63+
* @param _currentBalance The total account balance before the removal of Δa `_reducedBalance`
64+
* @param _reducedAmount reduced balance
65+
* @return _reducedMP Multiplier points to reduce from `_mp`
3866
*/
39-
function _calculateMinimumStake(uint256 _seconds) internal pure returns (uint256 _minimumStake) {
40-
return YEAR / (_seconds * MP_APY);
67+
function _calculateReducedMP(
68+
uint256 _mp,
69+
uint256 _currentBalance,
70+
uint256 _reducedAmount
71+
)
72+
public
73+
pure
74+
returns (uint256 _reducedMP)
75+
{
76+
return Math.mulDiv(_mp, _currentBalance, _reducedAmount);
4177
}
4278

4379
/**
4480
* @notice Calculates maximum stake a given `_amount` can be generated with `MAX_MULTIPLIER`
45-
* @param _amount quantity of tokens
81+
* @param _balance quantity of tokens
4682
* @return _maxMPAccured maximum quantity of muliplier points that can be generated for given `_amount`
4783
*/
48-
function _calculateMaxAccuredMP(uint256 _amount) internal pure returns (uint256 _maxMPAccured) {
49-
return _calculateAccuredMP(_amount, MAX_MULTIPLIER * YEAR);
84+
function _calculateMaxAccuredMP(uint256 _balance) public pure returns (uint256 _maxMPAccured) {
85+
return Math.mulDiv(_balance, MAX_MULTIPLIER * MP_APY, 100);
86+
}
87+
88+
/**
89+
* @notice The maximum total multiplier points that can be generated for a determined amount of balance and lock
90+
* duration.
91+
* @param _balance Represents the current account balance
92+
* @param _lockTime The time duration for which the balance is locked
93+
* @return _maxMP Maximum multiplier points that can be generated for given `_balance` and `_lockTime`
94+
*/
95+
function _calculateMaxMP(uint256 _balance, uint256 _lockTime) public pure returns (uint256 _maxMP) {
96+
return _balance + Math.mulDiv(_balance * MP_APY, (MAX_MULTIPLIER * YEAR) + _lockTime, YEAR * 100);
97+
}
98+
99+
/**
100+
* @dev Caution: This value is estimated and can be incorrect due precision loss.
101+
* @notice Estimates the time an account set as locked time.
102+
* @param _mpMax Maximum multiplier points calculated from the current balance.
103+
* @param _currentBalance Current balance used to calculate the maximum multiplier points.
104+
*/
105+
function _estimateLockTime(uint256 _mpMax, uint256 _currentBalance) public pure returns (uint256 _lockTime) {
106+
return Math.mulDiv((_mpMax - _currentBalance) * 100, YEAR, _currentBalance * MP_APY, Math.Rounding.Ceil)
107+
- MAX_LOCKUP_PERIOD;
108+
}
109+
110+
/**
111+
* @dev Caution: This value is estimated and can be incorrect due precision loss.
112+
* @notice Calculates the remaining lock time available for a given `_mpMax` and `_currentBalance`
113+
* @param _mpMax Maximum multiplier points calculated from the current balance.
114+
* @param _currentBalance Current balance used to calculate the maximum multiplier points.
115+
*/
116+
function _remainingLockTimeAvailable(
117+
uint256 _mpMax,
118+
uint256 _currentBalance
119+
)
120+
public
121+
pure
122+
returns (uint256 _lockTime)
123+
{
124+
return Math.mulDiv((_currentBalance * MAX_MULTIPLIER_ABSOLUTE) - _mpMax, YEAR, _currentBalance);
125+
}
126+
127+
/**
128+
* @notice Calculates the lock time for a given bonus multiplier points and current balance.
129+
* @param _bonusMP bonus multiplier points intended to be generated
130+
* @param _currentBalance current balance
131+
*/
132+
function _calculateLockTime(uint256 _bonusMP, uint256 _currentBalance) public pure returns (uint256 _lockTime) {
133+
return Math.mulDiv(_bonusMP * 100, YEAR, _currentBalance * MP_APY);
50134
}
51135
}

contracts/StakeMath.sol

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// SPDX-License-Identifier: MIT-1.0
2+
pragma solidity ^0.8.26;
3+
4+
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
5+
import { MultiplierPointMath } from "./MultiplierPointMath.sol";
6+
7+
abstract contract StakeMath is MultiplierPointMath {
8+
/// @notice Minimal lockup time
9+
uint256 public constant MIN_LOCKUP_TIME = 1 weeks;
10+
11+
/**
12+
* @notice Calculates the bonus multiplier points earned when a balance Δa is increased an optionally locked for a
13+
* specified duration
14+
* @param _balance Account current balance
15+
* @param _maxMP Account current max multiplier points
16+
* @param _lockEndTime Account current lock end timestamp
17+
* @param _processTime Process current timestamp
18+
* @param _increasedAmount Increased amount of balance
19+
* @param _increasedLockSeconds Increased amount of seconds to lock
20+
* @return _deltaMpTotal Increased amount of total multiplier points
21+
* @return _newMaxMP Account new max multiplier points
22+
* @return _newLockEnd Account new lock end timestamp
23+
*/
24+
function _calculateStake(
25+
uint256 _balance,
26+
uint256 _maxMP,
27+
uint256 _lockEndTime,
28+
uint256 _processTime,
29+
uint256 _increasedAmount,
30+
uint256 _increasedLockSeconds
31+
)
32+
public
33+
pure
34+
returns (uint256 _deltaMpTotal, uint256 _newMaxMP, uint256 _newLockEnd)
35+
{
36+
uint256 newBalance = _balance + _increasedAmount;
37+
require(newBalance >= MIN_BALANCE, "StakeMath: balance too low");
38+
_newLockEnd = Math.max(_lockEndTime, _processTime) + _increasedLockSeconds;
39+
uint256 dt_lock = _newLockEnd - _processTime;
40+
require(dt_lock == 0 || dt_lock >= MIN_LOCKUP_TIME, "StakeMath: lockup time too low");
41+
require(dt_lock <= MAX_LOCKUP_PERIOD, "StakeMath: lockup time too high");
42+
43+
uint256 deltaMpBonus;
44+
if (dt_lock > 0) {
45+
deltaMpBonus = _calculateBonusMP(_increasedAmount, dt_lock);
46+
}
47+
48+
if (_balance > 0 && _increasedLockSeconds > 0) {
49+
deltaMpBonus += _calculateBonusMP(_balance, _increasedLockSeconds);
50+
}
51+
52+
_deltaMpTotal = _calculateInitialMP(_increasedAmount) + deltaMpBonus;
53+
_newMaxMP = _maxMP + _deltaMpTotal + _calculateAccuredMP(_balance, MAX_MULTIPLIER * YEAR);
54+
55+
require(
56+
_newMaxMP <= MAX_MULTIPLIER_ABSOLUTE * (_balance + _increasedAmount), "StakeMath: max multiplier exceeded"
57+
);
58+
}
59+
60+
/**
61+
* @notice Calculates the bonus multiplier points earned when a balance Δa is locked for a specified duration
62+
* @param _balance Account current balance
63+
* @param _maxMP Account current max multiplier points
64+
* @param _lockEndTime Account current lock end timestamp
65+
* @param _processTime Process current timestamp
66+
* @param _increasedLockSeconds Increased amount of seconds to lock
67+
* @return _deltaMpTotal Increased amount of total multiplier points
68+
* @return _newMaxMP Account new max multiplier points
69+
* @return _newLockEnd Account new lock end timestamp
70+
*/
71+
function calculateLock(
72+
uint256 _balance,
73+
uint256 _maxMP,
74+
uint256 _lockEndTime,
75+
uint256 _processTime,
76+
uint256 _increasedLockSeconds
77+
)
78+
public
79+
pure
80+
returns (uint256 _deltaMpTotal, uint256 _newMaxMP, uint256 _newLockEnd)
81+
{
82+
require(_balance > 0);
83+
require(_increasedLockSeconds > 0);
84+
85+
_newLockEnd = Math.max(_lockEndTime, _processTime) + _increasedLockSeconds;
86+
uint256 dt_lock = _newLockEnd - _processTime;
87+
require(dt_lock == 0 || dt_lock >= MIN_LOCKUP_TIME, "StakeMath: lockup time too low");
88+
require(dt_lock <= MAX_LOCKUP_PERIOD, "StakeMath: lockup time too high");
89+
90+
_deltaMpTotal += _calculateBonusMP(_balance, _increasedLockSeconds);
91+
_newMaxMP = _maxMP + _deltaMpTotal;
92+
93+
require(_newMaxMP <= MAX_MULTIPLIER_ABSOLUTE * (_balance), "StakeMath: max multiplier exceeded");
94+
}
95+
96+
/**
97+
*
98+
* @param _balance Account current balance
99+
* @param _lockEndTime Account current lock end timestamp
100+
* @param _processTime Process current timestamp
101+
* @param _totalMP Account current total multiplier points
102+
* @param _maxMP Account current max multiplier points
103+
* @param _reducedAmount Reduced amount of balance
104+
* @return _deltaMpTotal Increased amount of total multiplier points
105+
* @return _deltaMpMax Increased amount of max multiplier points
106+
*/
107+
function _calculateUnstake(
108+
uint256 _balance,
109+
uint256 _lockEndTime,
110+
uint256 _processTime,
111+
uint256 _totalMP,
112+
uint256 _maxMP,
113+
uint256 _reducedAmount
114+
)
115+
public
116+
pure
117+
returns (uint256 _deltaMpTotal, uint256 _deltaMpMax)
118+
{
119+
require(_lockEndTime <= _processTime, "StakeMath: lockup not ended");
120+
require(_balance >= _reducedAmount, "StakeMath: balance too low");
121+
uint256 newBalance = _balance - _reducedAmount;
122+
require(newBalance == 0 || newBalance >= MIN_BALANCE, "StakeMath: balance too low");
123+
_deltaMpTotal = _calculateReducedMP(_totalMP, _balance, _reducedAmount);
124+
_deltaMpMax = _calculateReducedMP(_maxMP, _balance, _reducedAmount);
125+
}
126+
127+
/**
128+
* @notice Calculates the accrued multiplier points for a given balance and seconds passed since last accrual
129+
* @param _balance Account current balance
130+
* @param _totalMP Account current total multiplier points
131+
* @param _maxMP Account current max multiplier points
132+
* @param _lastAccrualTime Account current last accrual timestamp
133+
* @param _processTime Process current timestamp
134+
* @return _deltaMpTotal Increased amount of total multiplier points
135+
*/
136+
function _calculateAccrual(
137+
uint256 _balance,
138+
uint256 _totalMP,
139+
uint256 _maxMP,
140+
uint256 _lastAccrualTime,
141+
uint256 _processTime
142+
)
143+
public
144+
pure
145+
returns (uint256 _deltaMpTotal)
146+
{
147+
uint256 dt = _processTime - _lastAccrualTime;
148+
require(dt >= ACCURE_RATE, "StakeMath: no enough time passed");
149+
if (_totalMP <= _maxMP) {
150+
_deltaMpTotal = Math.min(_calculateAccuredMP(_balance, dt), _maxMP - _totalMP);
151+
}
152+
}
153+
}

0 commit comments

Comments
 (0)