Skip to content

Commit

Permalink
refactor: accounting for PR comments
Browse files Browse the repository at this point in the history
  • Loading branch information
kupermind committed Jul 24, 2024
1 parent bb35f9a commit 7035d04
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 126 deletions.
103 changes: 86 additions & 17 deletions contracts/BondCalculator.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

Check warning on line 2 in contracts/BondCalculator.sol

View workflow job for this annotation

GitHub Actions / build

Found more than One contract per file. 2 contracts found!

import {GenericBondCalculator} from "./GenericBondCalculator.sol";
import {mulDiv} from "@prb/math/src/Common.sol";
import {IVotingEscrow} from "./interfaces/IVotingEscrow.sol";
import "./interfaces/IUniswapV2Pair.sol";

Check warning on line 6 in contracts/BondCalculator.sol

View workflow job for this annotation

GitHub Actions / build

global import of path ./interfaces/IUniswapV2Pair.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

interface ITokenomics {
/// @dev Gets number of new units that were donated in the last epoch.
Expand Down Expand Up @@ -63,12 +64,16 @@ struct Product {
/// @author Aleksandr Kuperman - <[email protected]>
/// @author Andrey Lebedev - <[email protected]>
/// @author Mariapia Moscatiello - <[email protected]>
contract BondCalculator is GenericBondCalculator {
contract BondCalculator {
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;

Check warning on line 74 in contracts/BondCalculator.sol

View workflow job for this annotation

GitHub Actions / build

Immutable variables name are set to be in capitalized SNAKE_CASE
// Tokenomics contract address
address public immutable tokenomics;

Check warning on line 76 in contracts/BondCalculator.sol

View workflow job for this annotation

GitHub Actions / build

Immutable variables name are set to be in capitalized SNAKE_CASE
// veOLAS contract address
address public immutable ve;

Check warning on line 78 in contracts/BondCalculator.sol

View workflow job for this annotation

GitHub Actions / build

Immutable variables name are set to be in capitalized SNAKE_CASE

Expand All @@ -83,14 +88,14 @@ contract BondCalculator is GenericBondCalculator {
/// @param _tokenomics Tokenomics contract address.
/// @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)) {
constructor(address _olas, address _tokenomics, address _ve, DiscountParams memory _discountParams) {
// Check for at least one zero contract address
if (_olas == address(0) || _tokenomics == address(0) || _ve == address(0)) {
revert ZeroAddress();
}

olas = _olas;
tokenomics = _tokenomics;
ve = _ve;
owner = msg.sender;

Expand Down Expand Up @@ -153,17 +158,14 @@ contract BondCalculator is GenericBondCalculator {
}

/// @dev Calculated inverse discount factor based on bonding and account parameters.
/// @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.
/// @param account Account address.
/// @param bondVestingTime Bond vesting time.
/// @param productMaxVestingTime Product max vesting time.
/// @param productSupply Current product supply.
/// @param productPayout Current product payout.
/// @return idf Inverse discount factor in 18 decimals format.
function calculateIDF(bytes memory data) public view override 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));
function calculateIDF(address account, uint256 bondVestingTime, uint256 productMaxVestingTime, uint256 productSupply,
uint256 productPayout) public view returns (uint256 idf) {

// Get the copy of the discount params
DiscountParams memory localParams = discountParams;
Expand Down Expand Up @@ -226,6 +228,73 @@ contract BondCalculator is GenericBondCalculator {
idf = 1e18 + discountBooster;
}

/// @dev Calculates the amount of OLAS tokens based on the bonding calculator mechanism accounting for dynamic IDF.
/// @param account Account address.
/// @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.
/// @return amountOLAS Resulting amount of OLAS tokens.
function calculatePayoutOLAS(
address account,
uint256 tokenAmount,
uint256 priceLP,
uint256 bondVestingTime,
uint256 productMaxVestingTime,
uint256 productSupply,
uint256 productPayout
) external view 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)
// or 2* r0/sqrt(r0) * 10^18 => 87 bits + 60 bits = 147 bits (if LP is unbalanced);
// tokenAmount is of the order of sqrt(r0*r1) ~ 104 bits (if balanced) or sqrt(96) ~ 10 bits (if max unbalanced);
// overall: 64 + 53 + 104 = 221 < 256 - regular case if LP is balanced, and 64 + 147 + 10 = 221 < 256 if unbalanced
// mulDiv will correctly fit the total amount up to the value of max uint256, i.e., max of priceLP and max of tokenAmount,
// however their multiplication can not be bigger than the max of uint192
uint256 totalTokenValue = mulDiv(priceLP, tokenAmount, 1);
// Check for the cumulative LP tokens value limit
if (totalTokenValue > type(uint192).max) {
revert Overflow(totalTokenValue, type(uint192).max);
}

// Calculate the dynamic inverse discount factor
uint256 idf = calculateIDF(account, bondVestingTime, productMaxVestingTime, productSupply, productPayout);

// 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 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) {
IUniswapV2Pair pair = IUniswapV2Pair(token);
uint256 totalSupply = pair.totalSupply();
if (totalSupply > 0) {
address token0 = pair.token0();
address token1 = pair.token1();
uint256 reserve0;
uint256 reserve1;
// requires low gas
(reserve0, reserve1, ) = pair.getReserves();
// token0 != olas && token1 != olas, this should never happen
if (token0 == olas || token1 == olas) {
// If OLAS is in token0, assign its reserve to reserve1, otherwise the reserve1 is already correct
if (token0 == olas) {
reserve1 = reserve0;
}
// Calculate the LP price based on reserves and totalSupply ratio multiplied by 1e18
// Inspired by: https://github.com/curvefi/curve-contract/blob/master/contracts/pool-templates/base/SwapTemplateBase.vy#L262
priceLP = (reserve1 * 1e18) / totalSupply;
}
}
}

function getDiscountParams() external view returns (DiscountParams memory) {
return discountParams;
}
Expand Down
19 changes: 13 additions & 6 deletions contracts/Depository.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,22 @@ import {ITreasury} from "./interfaces/ITreasury.sol";

interface IBondCalculator {
/// @dev Calculates the amount of OLAS tokens based on the bonding calculator mechanism accounting for dynamic IDF.
/// @param account Account address.
/// @param tokenAmount LP token amount.
/// @param priceLP LP token price.
/// @param data Custom data that is used to calculate the IDF.
/// @param bondVestingTime Bond vesting time.
/// @param productMaxVestingTime Product max vesting time.
/// @param productSupply Current product supply.
/// @param productPayout Current product payout.
/// @return amountOLAS Resulting amount of OLAS tokens.
function calculatePayoutOLAS(
address account,
uint256 tokenAmount,
uint256 priceLP,
bytes memory data
uint256 bondVestingTime,
uint256 productMaxVestingTime,
uint256 productSupply,
uint256 productPayout
) external view returns (uint256 amountOLAS);

/// @dev Gets current reserves of OLAS / totalSupply of Uniswap V2-like LP tokens.
Expand Down Expand Up @@ -114,7 +122,7 @@ contract Depository is ERC721, IErrorsTokenomics {

// OLAS token address
address public immutable olas;

Check warning on line 124 in contracts/Depository.sol

View workflow job for this annotation

GitHub Actions / build

Immutable variables name are set to be in capitalized SNAKE_CASE
// Tkenomics contract address
// Tokenomics contract address
address public tokenomics;
// Treasury contract address
address public treasury;
Expand Down Expand Up @@ -370,9 +378,8 @@ contract Depository is ERC721, IErrorsTokenomics {

// Calculate the payout in OLAS tokens based on the LP pair with the inverse discount factor (IDF) calculation
// Note that payout cannot be zero since the price LP is non-zero, otherwise the product would not be created
payout = IBondCalculator(bondCalculator).calculatePayoutOLAS(tokenAmount, product.priceLP,
// Encode parameters required for the IDF calculation
abi.encode(msg.sender, bondVestingTime, productMaxVestingTime, supply, product.payout));
payout = IBondCalculator(bondCalculator).calculatePayoutOLAS(msg.sender, tokenAmount, product.priceLP,
bondVestingTime, productMaxVestingTime, supply, product.payout);

// Check for the sufficient supply
if (payout > supply) {
Expand Down
103 changes: 0 additions & 103 deletions contracts/GenericBondCalculator.sol

This file was deleted.

File renamed without changes.

0 comments on commit 7035d04

Please sign in to comment.