1
1
// SPDX-License-Identifier: MIT
2
2
pragma solidity ^ 0.8.25 ;
3
3
4
- import {GenericBondCalculator } from "./GenericBondCalculator .sol " ;
4
+ import {mulDiv } from "@prb/math/src/Common .sol " ;
5
5
import {IVotingEscrow} from "./interfaces/IVotingEscrow.sol " ;
6
+ import "./interfaces/IUniswapV2Pair.sol " ;
6
7
7
8
interface ITokenomics {
8
9
/// @dev Gets number of new units that were donated in the last epoch.
@@ -63,12 +64,16 @@ struct Product {
63
64
/// @author Aleksandr Kuperman - <[email protected] >
64
65
/// @author Andrey Lebedev - <[email protected] >
65
66
/// @author Mariapia Moscatiello - <[email protected] >
66
- contract BondCalculator is GenericBondCalculator {
67
+ contract BondCalculator {
67
68
event OwnerUpdated (address indexed owner );
68
69
event DiscountParamsUpdated (DiscountParams newDiscountParams );
69
70
70
71
// Maximum sum of discount factor weights
71
72
uint256 public constant MAX_SUM_WEIGHTS = 10_000 ;
73
+ // OLAS contract address
74
+ address public immutable olas;
75
+ // Tokenomics contract address
76
+ address public immutable tokenomics;
72
77
// veOLAS contract address
73
78
address public immutable ve;
74
79
@@ -83,14 +88,14 @@ contract BondCalculator is GenericBondCalculator {
83
88
/// @param _tokenomics Tokenomics contract address.
84
89
/// @param _ve veOLAS contract address.
85
90
/// @param _discountParams Discount factor parameters.
86
- constructor (address _olas , address _tokenomics , address _ve , DiscountParams memory _discountParams )
87
- GenericBondCalculator (_olas, _tokenomics)
88
- {
89
- // Check for zero address
90
- if (_ve == address (0 )) {
91
+ constructor (address _olas , address _tokenomics , address _ve , DiscountParams memory _discountParams ) {
92
+ // Check for at least one zero contract address
93
+ if (_olas == address (0 ) || _tokenomics == address (0 ) || _ve == address (0 )) {
91
94
revert ZeroAddress ();
92
95
}
93
96
97
+ olas = _olas;
98
+ tokenomics = _tokenomics;
94
99
ve = _ve;
95
100
owner = msg .sender ;
96
101
@@ -153,17 +158,14 @@ contract BondCalculator is GenericBondCalculator {
153
158
}
154
159
155
160
/// @dev Calculated inverse discount factor based on bonding and account parameters.
156
- /// @param data Custom data that is used to calculate the IDF:
157
- /// - account Account address.
158
- /// - bondVestingTime Bond vesting time.
159
- /// - productMaxVestingTime Product max vesting time.
160
- /// - productSupply Current product supply.
161
- /// - productPayout Current product payout.
161
+ /// @param account Account address.
162
+ /// @param bondVestingTime Bond vesting time.
163
+ /// @param productMaxVestingTime Product max vesting time.
164
+ /// @param productSupply Current product supply.
165
+ /// @param productPayout Current product payout.
162
166
/// @return idf Inverse discount factor in 18 decimals format.
163
- function calculateIDF (bytes memory data ) public view override returns (uint256 idf ) {
164
- // Decode the required data
165
- (address account , uint256 bondVestingTime , uint256 productMaxVestingTime , uint256 productSupply ,
166
- uint256 productPayout ) = abi.decode (data, (address , uint256 , uint256 , uint256 , uint256 ));
167
+ function calculateIDF (address account , uint256 bondVestingTime , uint256 productMaxVestingTime , uint256 productSupply ,
168
+ uint256 productPayout ) public view returns (uint256 idf ) {
167
169
168
170
// Get the copy of the discount params
169
171
DiscountParams memory localParams = discountParams;
@@ -226,6 +228,73 @@ contract BondCalculator is GenericBondCalculator {
226
228
idf = 1e18 + discountBooster;
227
229
}
228
230
231
+ /// @dev Calculates the amount of OLAS tokens based on the bonding calculator mechanism accounting for dynamic IDF.
232
+ /// @param account Account address.
233
+ /// @param tokenAmount LP token amount.
234
+ /// @param priceLP LP token price.
235
+ /// @param bondVestingTime Bond vesting time.
236
+ /// @param productMaxVestingTime Product max vesting time.
237
+ /// @param productSupply Current product supply.
238
+ /// @param productPayout Current product payout.
239
+ /// @return amountOLAS Resulting amount of OLAS tokens.
240
+ function calculatePayoutOLAS (
241
+ address account ,
242
+ uint256 tokenAmount ,
243
+ uint256 priceLP ,
244
+ uint256 bondVestingTime ,
245
+ uint256 productMaxVestingTime ,
246
+ uint256 productSupply ,
247
+ uint256 productPayout
248
+ ) external view returns (uint256 amountOLAS ) {
249
+ // The result is divided by additional 1e18, since it was multiplied by in the current LP price calculation
250
+ // The resulting amountDF can not overflow by the following calculations: idf = 64 bits;
251
+ // priceLP = 2 * r0/L * 10^18 = 2*r0*10^18/sqrt(r0*r1) ~= 61 + 96 - sqrt(96 * 112) ~= 53 bits (if LP is balanced)
252
+ // or 2* r0/sqrt(r0) * 10^18 => 87 bits + 60 bits = 147 bits (if LP is unbalanced);
253
+ // tokenAmount is of the order of sqrt(r0*r1) ~ 104 bits (if balanced) or sqrt(96) ~ 10 bits (if max unbalanced);
254
+ // overall: 64 + 53 + 104 = 221 < 256 - regular case if LP is balanced, and 64 + 147 + 10 = 221 < 256 if unbalanced
255
+ // mulDiv will correctly fit the total amount up to the value of max uint256, i.e., max of priceLP and max of tokenAmount,
256
+ // however their multiplication can not be bigger than the max of uint192
257
+ uint256 totalTokenValue = mulDiv (priceLP, tokenAmount, 1 );
258
+ // Check for the cumulative LP tokens value limit
259
+ if (totalTokenValue > type (uint192 ).max) {
260
+ revert Overflow (totalTokenValue, type (uint192 ).max);
261
+ }
262
+
263
+ // Calculate the dynamic inverse discount factor
264
+ uint256 idf = calculateIDF (account, bondVestingTime, productMaxVestingTime, productSupply, productPayout);
265
+
266
+ // Amount with the discount factor is IDF * priceLP * tokenAmount / 1e36
267
+ // At this point of time IDF is bound by the max of uint64, and totalTokenValue is no bigger than the max of uint192
268
+ amountOLAS = (idf * totalTokenValue) / 1e36 ;
269
+ }
270
+
271
+ /// @dev Gets current reserves of OLAS / totalSupply of Uniswap V2-like LP tokens.
272
+ /// @notice The price LP calculation is based on the UniswapV2Pair contract.
273
+ /// @param token Token address.
274
+ /// @return priceLP Resulting reserveX / totalSupply ratio with 18 decimals.
275
+ function getCurrentPriceLP (address token ) external view returns (uint256 priceLP ) {
276
+ IUniswapV2Pair pair = IUniswapV2Pair (token);
277
+ uint256 totalSupply = pair.totalSupply ();
278
+ if (totalSupply > 0 ) {
279
+ address token0 = pair.token0 ();
280
+ address token1 = pair.token1 ();
281
+ uint256 reserve0;
282
+ uint256 reserve1;
283
+ // requires low gas
284
+ (reserve0, reserve1, ) = pair.getReserves ();
285
+ // token0 != olas && token1 != olas, this should never happen
286
+ if (token0 == olas || token1 == olas) {
287
+ // If OLAS is in token0, assign its reserve to reserve1, otherwise the reserve1 is already correct
288
+ if (token0 == olas) {
289
+ reserve1 = reserve0;
290
+ }
291
+ // Calculate the LP price based on reserves and totalSupply ratio multiplied by 1e18
292
+ // Inspired by: https://github.com/curvefi/curve-contract/blob/master/contracts/pool-templates/base/SwapTemplateBase.vy#L262
293
+ priceLP = (reserve1 * 1e18 ) / totalSupply;
294
+ }
295
+ }
296
+ }
297
+
229
298
function getDiscountParams () external view returns (DiscountParams memory ) {
230
299
return discountParams;
231
300
}
0 commit comments