Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/project prices #56

Open
wants to merge 4 commits into
base: feature/base-currency-in-fc
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions contracts/JBPrices3_2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {JBOperatable} from './abstract/JBOperatable.sol';
import {PRBMath} from '@paulrberg/contracts/math/PRBMath.sol';
import {IJBPriceFeed} from './interfaces/IJBPriceFeed.sol';
import {IJBProjects} from './interfaces/IJBProjects.sol';
import {IJBOperatorStore} from './interfaces/IJBOperatorStore.sol';
import {IJBPrices3_2} from './interfaces/IJBPrices3_2.sol';
import {JBOperations2} from './libraries/JBOperations2.sol';

/// @notice Manages and normalizes price feeds.
contract JBPrices3_2 is Ownable, JBOperatable, IJBPrices3_2 {
//*********************************************************************//
// --------------------------- custom errors ------------------------- //
//*********************************************************************//
error PRICE_FEED_ALREADY_EXISTS();
error PRICE_FEED_NOT_FOUND();

//*********************************************************************//
// --------------------- internal stored constants ------------------- //
//*********************************************************************//

/// @notice The ID to store default values in.
uint256 public constant override DEFAULT_PROJECT_ID = 0;

//*********************************************************************//
// ---------------- public immutable stored properties --------------- //
//*********************************************************************//

/// @notice Mints ERC-721's that represent project ownership and transfers.
IJBProjects public immutable override projects;

//*********************************************************************//
// --------------------- public stored properties -------------------- //
//*********************************************************************//

/// @notice The available price feeds.
/// @dev The feed returns the number of `_currency` units that can be converted to 1 `_base` unit.
/// @custom:param _projectId The ID of the project for which the feed applies. Feeds stored in ID 0 are used by default.
/// @custom:param _currency The currency units the feed's resulting price is in terms of.
/// @custom:param _base The base currency unit being priced by the feed.
mapping(uint256 => mapping(uint256 => mapping(uint256 => IJBPriceFeed))) public override feedFor;

//*********************************************************************//
// -------------------------- public views --------------------------- //
//*********************************************************************//

/// @notice Gets the number of `_currency` units that can be converted to 1 `_base` unit.
/// @param _projectId The ID of the project relative to which the feed used to derive the price belongs. Feeds stored in ID 0 are used by default.
/// @param _currency The currency units the resulting price is in terms of.
/// @param _base The base currency unit being priced.
/// @param _decimals The number of decimals the returned fixed point price should include.
/// @return The price of the currency in terms of the base, as a fixed point number with the specified number of decimals.
function priceFor(
uint256 _projectId,
uint256 _currency,
uint256 _base,
uint256 _decimals
) public view override returns (uint256) {
// If the currency is the base, return 1 since they are priced the same. Include the desired number of decimals.
if (_currency == _base) return 10 ** _decimals;

// Get a reference to the feed.
IJBPriceFeed _feed = feedFor[_projectId][_currency][_base];

// If it exists, return the price.
if (_feed != IJBPriceFeed(address(0))) return _feed.currentPrice(_decimals);

// Get the inverse feed.
_feed = feedFor[_projectId][_base][_currency];

// If it exists, return the inverse price.
if (_feed != IJBPriceFeed(address(0)))
return PRBMath.mulDiv(10 ** _decimals, 10 ** _decimals, _feed.currentPrice(_decimals));

// Check in the 0 project if not found.
if (_projectId != 0)
return priceFor({_projectId: 0, _currency: _currency, _base: _base, _decimals: _decimals});

// No price feed available, revert.
revert PRICE_FEED_NOT_FOUND();
}

//*********************************************************************//
// ---------------------------- constructor -------------------------- //
//*********************************************************************//

/// @param _operatorStore A contract storing operator assignments.
/// @param _projects A contract which mints ERC-721's that represent project ownership and transfers.
/// @param _owner The address that will own the contract.
constructor(
IJBOperatorStore _operatorStore,
IJBProjects _projects,
address _owner
) JBOperatable(_operatorStore) {
projects = _projects;
// Transfer the ownership.
transferOwnership(_owner);
}

//*********************************************************************//
// ---------------------- external transactions ---------------------- //
//*********************************************************************//

/// @notice Add a price feed for a currency in terms of the provided base currency.
/// @dev Current feeds can't be modified, neither can feeds that have already been set by the default.
/// @param _currency The currency units the feed's resulting price is in terms of.
/// @param _base The base currency unit being priced by the feed.
/// @param _feed The price feed being added.
function addFeedFor(
uint256 _projectId,
uint256 _currency,
uint256 _base,
IJBPriceFeed _feed
)
external
override
requirePermissionAllowingOverride(
projects.ownerOf(_projectId),
_projectId,
JBOperations2.ADD_PRICE_FEED,
msg.sender == owner() && _projectId == 0
)
{
// Make sure there's no feed stored for the pair as defaults.
if (
feedFor[0][_currency][_base] != IJBPriceFeed(address(0)) ||
feedFor[0][_base][_currency] != IJBPriceFeed(address(0))
) {
revert PRICE_FEED_ALREADY_EXISTS();
}

// There can't already be a feed for the specified currency.
if (
feedFor[_projectId][_currency][_base] != IJBPriceFeed(address(0)) ||
feedFor[_projectId][_base][_currency] != IJBPriceFeed(address(0))
) revert PRICE_FEED_ALREADY_EXISTS();

// Store the feed.
feedFor[_projectId][_currency][_base] = _feed;

emit AddFeed(_projectId, _currency, _base, _feed);
}
}
29 changes: 21 additions & 8 deletions contracts/JBSingleTokenPaymentTerminalStore3_2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {IJBDirectory} from './interfaces/IJBDirectory.sol';
import {IJBFundingCycleDataSource3_1_1} from './interfaces/IJBFundingCycleDataSource3_1_1.sol';
import {IJBFundingCycleStore} from './interfaces/IJBFundingCycleStore.sol';
import {IJBPaymentTerminal} from './interfaces/IJBPaymentTerminal.sol';
import {IJBPrices} from './interfaces/IJBPrices.sol';
import {IJBPrices3_2} from './interfaces/IJBPrices3_2.sol';
import {IJBSingleTokenPaymentTerminal} from './interfaces/IJBSingleTokenPaymentTerminal.sol';
import {IJBSingleTokenPaymentTerminalStore3_2} from './interfaces/IJBSingleTokenPaymentTerminalStore3_2.sol';
import {JBConstants} from './libraries/JBConstants.sol';
Expand Down Expand Up @@ -65,7 +65,7 @@ contract JBSingleTokenPaymentTerminalStore3_1_1 is
IJBFundingCycleStore public immutable override fundingCycleStore;

/// @notice The contract that exposes price feeds.
IJBPrices public immutable override prices;
IJBPrices3_2 public immutable override prices;

//*********************************************************************//
// --------------------- public stored properties -------------------- //
Expand Down Expand Up @@ -212,7 +212,11 @@ contract JBSingleTokenPaymentTerminalStore3_1_1 is
/// @param _directory A contract storing directories of terminals and controllers for each project.
/// @param _fundingCycleStore A contract storing all funding cycle configurations.
/// @param _prices A contract that exposes price feeds.
constructor(IJBDirectory _directory, IJBFundingCycleStore _fundingCycleStore, IJBPrices _prices) {
constructor(
IJBDirectory _directory,
IJBFundingCycleStore _fundingCycleStore,
IJBPrices3_2 _prices
) {
directory = _directory;
fundingCycleStore = _fundingCycleStore;
prices = _prices;
Expand Down Expand Up @@ -336,7 +340,7 @@ contract JBSingleTokenPaymentTerminalStore3_1_1 is
// The weight is always a fixed point mumber with 18 decimals. To ensure this, the ratio should use the same number of decimals as the `_amount`.
uint256 _weightRatio = _amount.currency == fundingCycle.baseCurrency()
? 10 ** _decimals
: prices.priceFor(_amount.currency, fundingCycle.baseCurrency(), _decimals);
: prices.priceFor(_projectId, _amount.currency, fundingCycle.baseCurrency(), _decimals);

// Find the number of tokens to mint, as a fixed point number with as many decimals as `weight` has.
tokenCount = PRBMath.mulDiv(_amount.value, _weight, _weightRatio);
Expand Down Expand Up @@ -545,7 +549,7 @@ contract JBSingleTokenPaymentTerminalStore3_1_1 is
: PRBMath.mulDiv(
_amount,
10 ** _MAX_FIXED_POINT_FIDELITY, // Use _MAX_FIXED_POINT_FIDELITY to keep as much of the `_amount.value`'s fidelity as possible when converting.
prices.priceFor(_currency, _balanceCurrency, _MAX_FIXED_POINT_FIDELITY)
prices.priceFor(_projectId, _currency, _balanceCurrency, _MAX_FIXED_POINT_FIDELITY)
);

// The amount being distributed must be available.
Expand Down Expand Up @@ -616,7 +620,7 @@ contract JBSingleTokenPaymentTerminalStore3_1_1 is
: PRBMath.mulDiv(
_amount,
10 ** _MAX_FIXED_POINT_FIDELITY, // Use _MAX_FIXED_POINT_FIDELITY to keep as much of the `_amount.value`'s fidelity as possible when converting.
prices.priceFor(_currency, _balanceCurrency, _MAX_FIXED_POINT_FIDELITY)
prices.priceFor(_projectId, _currency, _balanceCurrency, _MAX_FIXED_POINT_FIDELITY)
);

// The amount being distributed must be available in the overflow.
Expand Down Expand Up @@ -757,7 +761,12 @@ contract JBSingleTokenPaymentTerminalStore3_1_1 is
_distributionLimitRemaining = PRBMath.mulDiv(
_distributionLimitRemaining,
10 ** _MAX_FIXED_POINT_FIDELITY, // Use _MAX_FIXED_POINT_FIDELITY to keep as much of the `_amount.value`'s fidelity as possible when converting.
prices.priceFor(_distributionLimitCurrency, _balanceCurrency, _MAX_FIXED_POINT_FIDELITY)
prices.priceFor(
_projectId,
_distributionLimitCurrency,
_balanceCurrency,
_MAX_FIXED_POINT_FIDELITY
)
);

// Overflow is the balance of this project minus the amount that can still be distributed.
Expand Down Expand Up @@ -795,7 +804,11 @@ contract JBSingleTokenPaymentTerminalStore3_1_1 is
// Convert the ETH overflow to the specified currency if needed, maintaining a fixed point number with 18 decimals.
uint256 _totalOverflow18Decimal = _currency == JBCurrencies.ETH
? _ethOverflow
: PRBMath.mulDiv(_ethOverflow, 10 ** 18, prices.priceFor(JBCurrencies.ETH, _currency, 18));
: PRBMath.mulDiv(
_ethOverflow,
10 ** 18,
prices.priceFor(_projectId, JBCurrencies.ETH, _currency, 18)
);

// Adjust the decimals of the fixed point number if needed to match the target decimals.
return
Expand Down
32 changes: 14 additions & 18 deletions contracts/abstract/JBPayoutRedemptionPaymentTerminal3_2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import {IJBOperatable} from './../interfaces/IJBOperatable.sol';
import {IJBOperatorStore} from './../interfaces/IJBOperatorStore.sol';
import {IJBPaymentTerminal} from './../interfaces/IJBPaymentTerminal.sol';
import {IJBPayoutTerminal3_1} from './../interfaces/IJBPayoutTerminal3_1.sol';
import {IJBPrices} from './../interfaces/IJBPrices.sol';
import {IJBPrices3_2} from './../interfaces/IJBPrices3_2.sol';
import {IJBProjects} from './../interfaces/IJBProjects.sol';
import {IJBRedemptionTerminal} from './../interfaces/IJBRedemptionTerminal.sol';
import {IJBSingleTokenPaymentTerminalStore3_1_1} from './../interfaces/IJBSingleTokenPaymentTerminalStore3_1_1.sol';
import {IJBSingleTokenPaymentTerminalStore3_2} from './../interfaces/IJBSingleTokenPaymentTerminalStore3_2.sol';
import {IJBSplitAllocator} from './../interfaces/IJBSplitAllocator.sol';
import {JBConstants} from './../libraries/JBConstants.sol';
import {JBCurrencies} from './../libraries/JBCurrencies.sol';
Expand Down Expand Up @@ -95,7 +95,7 @@ abstract contract JBPayoutRedemptionPaymentTerminal3_2 is
IJBSplitsStore public immutable override splitsStore;

/// @notice The contract that exposes price feeds.
IJBPrices public immutable override prices;
IJBPrices3_2 public immutable override prices;

/// @notice The contract that stores and manages the terminal's data.
address public immutable override store;
Expand Down Expand Up @@ -131,7 +131,7 @@ abstract contract JBPayoutRedemptionPaymentTerminal3_2 is
uint256 _projectId
) external view virtual override returns (uint256) {
// Get this terminal's current overflow.
uint256 _overflow = IJBSingleTokenPaymentTerminalStore3_1_1(store).currentOverflowOf(
uint256 _overflow = IJBSingleTokenPaymentTerminalStore3_2(store).currentOverflowOf(
this,
_projectId
);
Expand All @@ -148,7 +148,7 @@ abstract contract JBPayoutRedemptionPaymentTerminal3_2 is
: PRBMath.mulDiv(
_adjustedOverflow,
10 ** decimals,
prices.priceFor(currency, JBCurrencies.ETH, decimals)
prices.priceFor(_projectId, currency, JBCurrencies.ETH, decimals)
);
}

Expand Down Expand Up @@ -212,7 +212,7 @@ abstract contract JBPayoutRedemptionPaymentTerminal3_2 is
IJBProjects _projects,
IJBDirectory _directory,
IJBSplitsStore _splitsStore,
IJBPrices _prices,
IJBPrices3_2 _prices,
address _store,
address _owner
)
Expand Down Expand Up @@ -411,7 +411,7 @@ abstract contract JBPayoutRedemptionPaymentTerminal3_2 is
if (!_to.acceptsToken(token, _projectId)) revert TERMINAL_TOKENS_INCOMPATIBLE();

// Record the migration in the store.
balance = IJBSingleTokenPaymentTerminalStore3_1_1(store).recordMigration(_projectId);
balance = IJBSingleTokenPaymentTerminalStore3_2(store).recordMigration(_projectId);

// Transfer the balance if needed.
if (balance != 0) {
Expand Down Expand Up @@ -646,7 +646,7 @@ abstract contract JBPayoutRedemptionPaymentTerminal3_2 is
reclaimAmount,
_delegateAllocations,
_memo
) = IJBSingleTokenPaymentTerminalStore3_1_1(store).recordRedemptionFor(
) = IJBSingleTokenPaymentTerminalStore3_2(store).recordRedemptionFor(
_holder,
_projectId,
_tokenCount,
Expand All @@ -656,8 +656,7 @@ abstract contract JBPayoutRedemptionPaymentTerminal3_2 is

// Set the reference to the fee discount to apply. No fee if the beneficiary is feeless or if the redemption rate is at its max.
_feeDiscount = isFeelessAddress[_beneficiary] ||
(_fundingCycle.redemptionRate() == JBConstants.MAX_REDEMPTION_RATE &&
_fundingCycle.ballotRedemptionRate() == JBConstants.MAX_REDEMPTION_RATE) ||
_fundingCycle.redemptionRate() == JBConstants.MAX_REDEMPTION_RATE ||
_feePercent == 0
? JBConstants.MAX_FEE_DISCOUNT
: _currentFeeDiscount(_projectId, JBFeeType.REDEMPTION);
Expand Down Expand Up @@ -806,7 +805,7 @@ abstract contract JBPayoutRedemptionPaymentTerminal3_2 is
(
JBFundingCycle memory _fundingCycle,
uint256 _distributedAmount
) = IJBSingleTokenPaymentTerminalStore3_1_1(store).recordDistributionFor(
) = IJBSingleTokenPaymentTerminalStore3_2(store).recordDistributionFor(
_projectId,
_amount,
_currency
Expand Down Expand Up @@ -924,7 +923,7 @@ abstract contract JBPayoutRedemptionPaymentTerminal3_2 is
(
JBFundingCycle memory _fundingCycle,
uint256 _distributedAmount
) = IJBSingleTokenPaymentTerminalStore3_1_1(store).recordUsedAllowanceOf(
) = IJBSingleTokenPaymentTerminalStore3_2(store).recordUsedAllowanceOf(
_projectId,
_amount,
_currency
Expand Down Expand Up @@ -1308,10 +1307,7 @@ abstract contract JBPayoutRedemptionPaymentTerminal3_2 is
if (_allowanceAmount != 0) _cancelTransferTo(_expectedDestination, _allowanceAmount);

// Add undistributed amount back to project's balance.
IJBSingleTokenPaymentTerminalStore3_1_1(store).recordAddedBalanceFor(
_projectId,
_depositAmount
);
IJBSingleTokenPaymentTerminalStore3_2(store).recordAddedBalanceFor(_projectId, _depositAmount);
}

/// @notice Contribute tokens to a project.
Expand Down Expand Up @@ -1355,7 +1351,7 @@ abstract contract JBPayoutRedemptionPaymentTerminal3_2 is
_tokenCount,
_delegateAllocations,
_memo
) = IJBSingleTokenPaymentTerminalStore3_1_1(store).recordPaymentFrom(
) = IJBSingleTokenPaymentTerminalStore3_2(store).recordPaymentFrom(
_payer,
_bundledAmount,
_projectId,
Expand Down Expand Up @@ -1463,7 +1459,7 @@ abstract contract JBPayoutRedemptionPaymentTerminal3_2 is
uint256 _refundedFees = _shouldRefundHeldFees ? _refundHeldFees(_projectId, _amount) : 0;

// Record the added funds with any refunded fees.
IJBSingleTokenPaymentTerminalStore3_1_1(store).recordAddedBalanceFor(
IJBSingleTokenPaymentTerminalStore3_2(store).recordAddedBalanceFor(
_projectId,
_amount + _refundedFees
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {IJBFeeHoldingTerminal} from './IJBFeeHoldingTerminal.sol';
import {IJBPayDelegate3_1_1} from './IJBPayDelegate3_1_1.sol';
import {IJBPaymentTerminal} from './IJBPaymentTerminal.sol';
import {IJBPayoutTerminal3_1} from './IJBPayoutTerminal3_1.sol';
import {IJBPrices} from './IJBPrices.sol';
import {IJBPrices3_2} from './IJBPrices3_2.sol';
import {IJBProjects} from './IJBProjects.sol';
import {IJBRedemptionDelegate3_1_1} from './IJBRedemptionDelegate3_1_1.sol';
import {IJBRedemptionTerminal} from './IJBRedemptionTerminal.sol';
Expand Down Expand Up @@ -170,7 +170,7 @@ interface IJBPayoutRedemptionPaymentTerminal3_2 is

function directory() external view returns (IJBDirectory);

function prices() external view returns (IJBPrices);
function prices() external view returns (IJBPrices3_2);

function store() external view returns (address);

Expand Down
Loading
Loading