-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor and test: making bond calculator inherit the generic one, co…
…rrecting tests
- Loading branch information
Showing
6 changed files
with
135 additions
and
126 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
pragma solidity ^0.8.25; | ||
|
||
import {mulDiv} from "@prb/math/src/Common.sol"; | ||
import {GenericBondCalculator} from "./GenericBondCalculator.sol"; | ||
import {IVotingEscrow} from "./interfaces/IVotingEscrow.sol"; | ||
|
||
interface ITokenomics { | ||
|
@@ -23,6 +24,9 @@ error Overflow(uint256 provided, uint256 max); | |
/// @dev Provided zero address. | ||
error ZeroAddress(); | ||
|
||
/// @dev Provided zero value. | ||
error ZeroValue(); | ||
|
||
// Struct for discount factor params | ||
// The size of the struct is 96 + 64 + 64 = 224 (1 slot) | ||
struct DiscountParams { | ||
|
@@ -56,43 +60,45 @@ struct Product { | |
uint32 vesting; | ||
} | ||
|
||
/// @title GenericBondSwap - Smart contract for generic bond calculation mechanisms in exchange for OLAS tokens. | ||
/// @dev The bond calculation mechanism is based on the UniswapV2Pair contract. | ||
/// @author AL | ||
/// @title BondCalculator - Smart contract for bond calculation payout in exchange for OLAS tokens based on dynamic IDF. | ||
/// @author Aleksandr Kuperman - <[email protected]> | ||
contract GenericBondCalculator { | ||
/// @author Andrey Lebedev - <[email protected]> | ||
/// @author Mariapia Moscatiello - <[email protected]> | ||
contract BondCalculator is GenericBondCalculator { | ||
event OwnerUpdated(address indexed owner); | ||
event DiscountParamsUpdated(DiscountParams newDiscountParams); | ||
|
||
// Maximum sum of discount factor weights | ||
uint256 public constant MAX_SUM_WEIGHTS = 10_000; | ||
// OLAS contract address | ||
address public immutable olas; | ||
// veOLAS contract address | ||
address public immutable ve; | ||
// Tokenomics contract address | ||
address public immutable tokenomics; | ||
|
||
// Contract owner | ||
address public owner; | ||
// Discount params | ||
DiscountParams public discountParams; | ||
|
||
|
||
/// @dev Generic Bond Calcolator constructor | ||
/// @dev Bond Calculator constructor. | ||
/// @param _olas OLAS contract address. | ||
/// @param _tokenomics Tokenomics contract address. | ||
constructor(address _olas, address _ve, address _tokenomics, DiscountParams memory _discountParams) { | ||
// Check for at least one zero contract address | ||
if (_olas == address(0) || _ve == address(0) || _tokenomics == address(0)) { | ||
/// @param _ve veOLAS contract address. | ||
/// @param _discountParams Discount factor parameters. | ||
constructor(address _olas, address _tokenomics, address _ve, DiscountParams memory _discountParams) | ||
GenericBondCalculator(_olas, _tokenomics) | ||
{ | ||
// Check for zero address | ||
if (_ve == address(0)) { | ||
revert ZeroAddress(); | ||
} | ||
|
||
olas = _olas; | ||
ve = _ve; | ||
tokenomics = _tokenomics; | ||
owner = msg.sender; | ||
|
||
|
||
// Check for zero values | ||
if (_discountParams.targetNewUnits == 0 || _discountParams.targetVotingPower == 0) { | ||
revert ZeroValue(); | ||
} | ||
// Check the sum of factors that cannot exceed the value of 10_000 (100% with a 0.01% step) | ||
uint256 sumWeights; | ||
for (uint256 i = 0; i < _discountParams.weightFactors.length; ++i) { | ||
|
@@ -129,6 +135,10 @@ contract GenericBondCalculator { | |
revert OwnerOnly(msg.sender, owner); | ||
} | ||
|
||
// Check for zero values | ||
if (newDiscountParams.targetNewUnits == 0 || newDiscountParams.targetVotingPower == 0) { | ||
revert ZeroValue(); | ||
} | ||
// Check the sum of factors that cannot exceed the value of 10_000 (100% with a 0.01% step) | ||
uint256 sumWeights; | ||
for (uint256 i = 0; i < newDiscountParams.weightFactors.length; ++i) { | ||
|
@@ -143,26 +153,16 @@ contract GenericBondCalculator { | |
emit DiscountParamsUpdated(newDiscountParams); | ||
} | ||
|
||
/// @dev Calculates the amount of OLAS tokens based on the bonding calculator mechanism. | ||
/// @notice Currently there is only one implementation of a bond calculation mechanism based on the UniswapV2 LP. | ||
/// @notice IDF has a 10^18 multiplier and priceLP has the same as well, so the result must be divided by 10^36. | ||
/// @dev Calculates the amount of OLAS tokens based on the bonding calculator mechanism accounting for dynamic IDF. | ||
/// @param tokenAmount LP token amount. | ||
/// @param priceLP LP token price. | ||
/// @param bondVestingTime Bond vesting time. | ||
/// @param productMaxVestingTime Product max vesting time. | ||
/// @param productSupply Current product supply. | ||
/// @param productPayout Current product payout. | ||
/// @param data Custom data that is used to calculate the IDF. | ||
/// @return amountOLAS Resulting amount of OLAS tokens. | ||
/// #if_succeeds {:msg "LP price limit"} priceLP * tokenAmount <= type(uint192).max; | ||
function calculatePayoutOLAS( | ||
address account, | ||
uint256 tokenAmount, | ||
uint256 priceLP, | ||
uint256 bondVestingTime, | ||
uint256 productMaxVestingTime, | ||
uint256 productSupply, | ||
uint256 productPayout | ||
) external view returns (uint256 amountOLAS) { | ||
bytes memory data | ||
) external view override returns (uint256 amountOLAS) { | ||
// The result is divided by additional 1e18, since it was multiplied by in the current LP price calculation | ||
// The resulting amountDF can not overflow by the following calculations: idf = 64 bits; | ||
// priceLP = 2 * r0/L * 10^18 = 2*r0*10^18/sqrt(r0*r1) ~= 61 + 96 - sqrt(96 * 112) ~= 53 bits (if LP is balanced) | ||
|
@@ -178,34 +178,33 @@ contract GenericBondCalculator { | |
} | ||
|
||
// Calculate the dynamic inverse discount factor | ||
uint256 idf = calculateIDF(account, bondVestingTime, productMaxVestingTime, productSupply, productPayout); | ||
uint256 idf = calculateIDF(data); | ||
|
||
// Amount with the discount factor is IDF * priceLP * tokenAmount / 1e36 | ||
// At this point of time IDF is bound by the max of uint64, and totalTokenValue is no bigger than the max of uint192 | ||
amountOLAS = (idf * totalTokenValue) / 1e36; | ||
} | ||
|
||
/// @dev Calculated inverse discount factor based on bonding and account parameters. | ||
/// @param account Bonding account address. | ||
/// @param bondVestingTime Bonding desired vesting time. | ||
/// @param productMaxVestingTime Product max vesting time. | ||
/// @param productSupply Current product supply. | ||
/// @param productPayout Current product payout. | ||
/// @param data Custom data that is used to calculate the IDF: | ||
/// - account Account address. | ||
/// - bondVestingTime Bond vesting time. | ||
/// - productMaxVestingTime Product max vesting time. | ||
/// - productSupply Current product supply. | ||
/// - productPayout Current product payout. | ||
/// @return idf Inverse discount factor in 18 decimals format. | ||
function calculateIDF( | ||
address account, | ||
uint256 bondVestingTime, | ||
uint256 productMaxVestingTime, | ||
uint256 productSupply, | ||
uint256 productPayout | ||
) public view returns (uint256 idf) { | ||
function calculateIDF(bytes memory data) public view virtual returns (uint256 idf) { | ||
// Decode the required data | ||
(address account, uint256 bondVestingTime, uint256 productMaxVestingTime, uint256 productSupply, | ||
uint256 productPayout) = abi.decode(data, (address, uint256, uint256, uint256, uint256)); | ||
|
||
// Get the copy of the discount params | ||
DiscountParams memory localParams = discountParams; | ||
uint256 discountBooster; | ||
|
||
// First discount booster: booster = k1 * NumNewUnits(previous epoch) / TargetNewUnits(previous epoch) | ||
// Check the number of new units coming from tokenomics vs the target number of new units | ||
if (localParams.targetNewUnits > 0) { | ||
if (localParams.weightFactors[0] > 0) { | ||
uint256 numNewUnits = ITokenomics(tokenomics).getLastEpochNumNewUnits(); | ||
|
||
// If the number of new units exceeds the target, bound by the target number | ||
|
@@ -217,16 +216,20 @@ contract GenericBondCalculator { | |
|
||
// Second discount booster: booster += k2 * bondVestingTime / productMaxVestingTime | ||
// Add vesting time discount booster | ||
discountBooster += (localParams.weightFactors[1] * bondVestingTime * 1e18) / productMaxVestingTime; | ||
if (localParams.weightFactors[1] > 0) { | ||
discountBooster += (localParams.weightFactors[1] * bondVestingTime * 1e18) / productMaxVestingTime; | ||
} | ||
|
||
// Third discount booster: booster += k3 * (1 - productPayout(at bonding time) / productSupply) | ||
// Add product supply discount booster | ||
productSupply = productSupply + productPayout; | ||
discountBooster += localParams.weightFactors[2] * (1e18 - ((productPayout * 1e18) / productSupply)); | ||
if (localParams.weightFactors[2] > 0) { | ||
productSupply = productSupply + productPayout; | ||
discountBooster += localParams.weightFactors[2] * (1e18 - ((productPayout * 1e18) / productSupply)); | ||
} | ||
|
||
// Fourth discount booster: booster += k4 * getVotes(bonding account) / targetVotingPower | ||
// Check the veOLAS balance of a bonding account | ||
if (localParams.targetVotingPower > 0) { | ||
if (localParams.weightFactors[3] > 0) { | ||
uint256 vPower = IVotingEscrow(ve).getVotes(account); | ||
|
||
// If the number of new units exceeds the target, bound by the target number | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,7 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.18; | ||
pragma solidity ^0.8.25; | ||
|
||
import {mulDiv} from "@prb/math/src/Common.sol"; | ||
import "./interfaces/ITokenomics.sol"; | ||
import "./interfaces/IUniswapV2Pair.sol"; | ||
|
||
/// @dev Value overflow. | ||
|
@@ -13,17 +12,17 @@ error Overflow(uint256 provided, uint256 max); | |
/// @dev Provided zero address. | ||
error ZeroAddress(); | ||
|
||
/// @title GenericBondSwap - Smart contract for generic bond calculation mechanisms in exchange for OLAS tokens. | ||
/// @dev The bond calculation mechanism is based on the UniswapV2Pair contract. | ||
/// @author AL | ||
/// @title GenericBondCalculator - Smart contract for generic bond calculation mechanisms in exchange for OLAS tokens. | ||
/// @author Aleksandr Kuperman - <[email protected]> | ||
/// @author Andrey Lebedev - <[email protected]> | ||
/// @author Mariapia Moscatiello - <[email protected]> | ||
contract GenericBondCalculator { | ||
// OLAS contract address | ||
address public immutable olas; | ||
// Tokenomics contract address | ||
address public immutable tokenomics; | ||
|
||
/// @dev Generic Bond Calcolator constructor | ||
/// @dev Generic Bond Calculator constructor | ||
/// @param _olas OLAS contract address. | ||
/// @param _tokenomics Tokenomics contract address. | ||
constructor(address _olas, address _tokenomics) { | ||
|
@@ -43,7 +42,7 @@ contract GenericBondCalculator { | |
/// @param priceLP LP token price. | ||
/// @return amountOLAS Resulting amount of OLAS tokens. | ||
/// #if_succeeds {:msg "LP price limit"} priceLP * tokenAmount <= type(uint192).max; | ||
function calculatePayoutOLAS(uint256 tokenAmount, uint256 priceLP) external view | ||
function calculatePayoutOLAS(uint256 tokenAmount, uint256 priceLP, bytes memory) external view virtual | ||
returns (uint256 amountOLAS) | ||
{ | ||
// The result is divided by additional 1e18, since it was multiplied by in the current LP price calculation | ||
|
@@ -60,15 +59,15 @@ contract GenericBondCalculator { | |
revert Overflow(totalTokenValue, type(uint192).max); | ||
} | ||
// Amount with the discount factor is IDF * priceLP * tokenAmount / 1e36 | ||
// At this point of time IDF is bound by the max of uint64, and totalTokenValue is no bigger than the max of uint192 | ||
amountOLAS = ITokenomics(tokenomics).getLastIDF() * totalTokenValue / 1e36; | ||
// Note IDF in Tokenomics is deprecated, and can be assumed as equal to 1e18 by default | ||
amountOLAS = totalTokenValue / 1e18; | ||
} | ||
|
||
/// @dev Gets current reserves of OLAS / totalSupply of LP tokens. | ||
/// @dev Gets current reserves of OLAS / totalSupply of Uniswap V2-like LP tokens. | ||
/// @notice The price LP calculation is based on the UniswapV2Pair contract. | ||
/// @param token Token address. | ||
/// @return priceLP Resulting reserveX / totalSupply ratio with 18 decimals. | ||
function getCurrentPriceLP(address token) external view returns (uint256 priceLP) | ||
{ | ||
function getCurrentPriceLP(address token) external view virtual returns (uint256 priceLP) { | ||
IUniswapV2Pair pair = IUniswapV2Pair(token); | ||
uint256 totalSupply = pair.totalSupply(); | ||
if (totalSupply > 0) { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.