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/swap percent limit #23

Closed
wants to merge 2 commits into from
Closed
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
62 changes: 55 additions & 7 deletions contracts/JBBuybackDelegate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,16 @@ contract JBBuybackDelegate is ERC165, JBOperatable, IJBBuybackDelegate {
// --------------------- public constant properties ------------------ //
//*********************************************************************//

/// @notice The unit of the swap amount limit percentage.
uint256 public constant SWAP_PERCENT_LIMIT_DENOMINATOR = 10_000;

/// @notice The unit of the max slippage.
uint256 public constant SLIPPAGE_DENOMINATOR = 10_000;

/// @notice The minimum possible percent limit of swapping.
/// @dev This serves to avoid being able to bypass swapping with more than 10% of a payment.
uint256 public constant MIN_SWAP_PERCENT_LIMIT = 9_000;

/// @notice The minimum twap deviation allowed, out of MAX_SLIPPAGE.
/// @dev This serves to avoid operators settings values that force the bypassing the swap when a quote is not provided in payment metadata.
uint256 public constant MIN_TWAP_SLIPPAGE_TOLERANCE = 100;
Expand Down Expand Up @@ -166,8 +173,11 @@ contract JBBuybackDelegate is ERC165, JBOperatable, IJBBuybackDelegate {
if (_quoteExists) (_amountToSwapWith, _minimumSwapAmountOut) = abi.decode(_metadata, (uint256, uint256));
}

// Make sure the amount to swap with is at most the swap percent limit.
if (_amountToSwapWith != 0 && _amountToSwapWith > mulDiv(_totalPaid, _swapPercentLimit, SWAP_PERCENT_LIMIT_DENOMINATOR)) revert JuiceBuyback_SwapAmountOutOfBounds();

// If no amount was specified to swap with, default to the full amount of the payment.
if (_amountToSwapWith == 0) _amountToSwapWith = _totalPaid;
if (_amountToSwapWith == 0) _amountToSwapWith = mulDiv(_totalPaid, _swapPercentLimit, SWAP_PERCENT_LIMIT_DENOMINATOR);

// Find the default total number of tokens to mint as if no Buyback Delegate were installed, as a fixed point number with 18 decimals

Expand Down Expand Up @@ -216,14 +226,21 @@ contract JBBuybackDelegate is ERC165, JBOperatable, IJBBuybackDelegate {
/// @param _projectId The ID of the project for which the value applies.
/// @return _secondsAgo The period over which the TWAP is computed.
function twapWindowOf(uint256 _projectId) external view returns (uint32) {
return uint32(_twapParamsOf[_projectId]);
return uint256(uint32(_twapParamsOf[_projectId] << 96 >> 128));
}

/// @notice The TWAP max deviation acepted, out of SLIPPAGE_DENOMINATOR.
/// @param _projectId The ID of the project for which the value applies.
/// @return _delta the maximum deviation allowed between the token amount received and the TWAP quote.
function twapSlippageToleranceOf(uint256 _projectId) external view returns (uint256) {
return _twapParamsOf[_projectId] >> 128;
return uint256(uint32(_twapParamsOf[_projectId] >> 96));
}

/// @notice The limit of paid funds that can be allocated towards a swap, as a percent out of SWAP_PERCENT_LIMIT_DENOMINATOR.
/// @param _projectId The ID of the project for which the value applies.
/// @return The limit percentage of payments that can be used for swapping.
function swapPercentLimit(uint256 _projectId) external view returns (uint256) {
return uint256(uint96(_twapParamsOf[_projectId]));
}

/// @notice Generic redeem params, for interface completion.
Expand Down Expand Up @@ -350,9 +367,10 @@ contract JBBuybackDelegate is ERC165, JBOperatable, IJBBuybackDelegate {
/// @param _fee The fee that is used in the pool being set.
/// @param _twapWindow The period over which the TWAP is computed.
/// @param _twapSlippageTolerance The maximum deviation allowed between amount received and TWAP.
/// @param _swapPercentLimit The limit of paid funds that can be allocated towards a swap.
/// @param _terminalToken The terminal token that payments are made in.
/// @return newPool The pool that was created.
function setPoolFor(uint256 _projectId, uint24 _fee, uint32 _twapWindow, uint256 _twapSlippageTolerance, address _terminalToken)
function setPoolFor(uint256 _projectId, uint24 _fee, uint256 _twapWindow, uint256 _twapSlippageTolerance, uint256 _swapPercentLimit, address _terminalToken)
external
requirePermission(PROJECTS.ownerOf(_projectId), _projectId, JBBuybackDelegateOperations.CHANGE_POOL)
returns (IUniswapV3Pool newPool)
Expand All @@ -363,6 +381,9 @@ contract JBBuybackDelegate is ERC165, JBOperatable, IJBBuybackDelegate {
// Make sure the provided period is within sane bounds.
if (_twapWindow < MIN_TWAP_WINDOW || _twapWindow > MAX_TWAP_WINDOW) revert JuiceBuyback_InvalidTwapWindow();

// Make sure the provided delta is within sane bounds.
if (_swapPercentLimit < MIN_SWAP_PERCENT_LIMIT || _swapPercentLimit > SWAP_PERCENT_LIMIT_DENOMINATOR) revert JuiceBuyback_InvalidSwapPercentLimit();

// Keep a reference to the project's token.
address _projectToken = address(CONTROLLER.tokenStore().tokenOf(_projectId));

Expand Down Expand Up @@ -407,7 +428,11 @@ contract JBBuybackDelegate is ERC165, JBOperatable, IJBBuybackDelegate {
poolOf[_projectId][_terminalToken] = newPool;

// Store the twap period and max slipage.
_twapParamsOf[_projectId] = _twapSlippageTolerance << 128 | _twapWindow;
// _twapSlippageTolerance - occupies the topmost bits
// _twapWindow - occupies the next 32 bits
// _swapPercentLimit - occupies the remaining 32 bits
_twapParamsOf[_projectId] = _twapSlippageTolerance << 96 | _twapWindow << 64 | _swapPercentLimit;

projectTokenOf[_projectId] = address(_projectToken);

emit BuybackDelegate_TwapWindowChanged(_projectId, 0, _twapWindow, msg.sender);
Expand Down Expand Up @@ -435,7 +460,7 @@ contract JBBuybackDelegate is ERC165, JBOperatable, IJBBuybackDelegate {
uint256 _oldWindow = uint128(_twapParams);

// Store the new packed value of the TWAP params.
_twapParamsOf[_projectId] = uint256(_newWindow) | ((_twapParams >> 128) << 128);
_twapParamsOf[_projectId] = _twapParams >> 96 << 64 | _twapParams << 128 >> 128 | _newWindow << 64;

emit BuybackDelegate_TwapWindowChanged(_projectId, _oldWindow, _newWindow, msg.sender);
}
Expand All @@ -458,11 +483,34 @@ contract JBBuybackDelegate is ERC165, JBOperatable, IJBBuybackDelegate {
uint256 _oldSlippageTolerance = _twapParams >> 128;

// Store the new packed value of the TWAP params.
_twapParamsOf[_projectId] = _newSlippageTolerance << 128 | ((_twapParams << 128) >> 128);
_twapParamsOf[_projectId] = newTwapSlippageTolerance << 96 | _twapParams >> 96 << 96;

emit BuybackDelegate_TwapSlippageToleranceChanged(_projectId, _oldSlippageTolerance, _newSlippageTolerance, msg.sender);
}

/// @notice Set the limit of paid funds that can be allocated towards a swap, as a percent out of SWAP_PERCENT_LIMIT_DENOMINATOR.
/// @dev This can be called by the project owner or an address having the SET_POOL permission in JBOperatorStore.
/// @param _projectId The ID for which the new value applies.
/// @param _newSwapPercentLimit the new limit, out of SWAP_PERCENT_LIMIT_DENOMINATOR.
function setSwapPercentLimitOf(uint256 _projectId, uint256 _newSwapPercentLimit)
external
requirePermission(PROJECTS.ownerOf(_projectId), _projectId, JBBuybackDelegateOperations.SET_POOL_PARAMS)
{
// Make sure the provided delta is within sane bounds.
if (_newSwapPercentLimit < MIN_SWAP_PERCENT_LIMIT) revert JuiceBuyback_InvalidSwapPercentLimit();

// Keep a reference to the currently stored TWAP params.
uint256 _twapParams = _twapParamsOf[_projectId];

// Keep a reference to the old swap percent limit.
uint256 _oldSwapPercentLimit = _twapParams >> 128;

// Store the new packed value of the TWAP params.
_twapParamsOf[_projectId] = _twapParams << 96 >> 96 | newSwapPercentLimit;

emit BuybackDelegate_TwapPercentLimitChanged(_projectId, _oldSwapPercentLimit, _newSwapPercentLimit, msg.sender);
}

//*********************************************************************//
// ---------------------- internal functions ------------------------- //
//*********************************************************************//
Expand Down
5 changes: 5 additions & 0 deletions contracts/interfaces/IJBBuybackDelegate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ interface IJBBuybackDelegate is IJBPayDelegate3_1_1, IJBFundingCycleDataSource3_
error JuiceBuyback_NewSecondsAgoTooLow();
error JuiceBuyback_NoProjectToken();
error JuiceBuyback_PoolAlreadySet();
error JuiceBuyback_SwapAmountOutOfBounds();
error JuiceBuyback_TransferFailed();
error JuiceBuyback_InvalidSwapPercentLimit();
error JuiceBuyback_InvalidTwapSlippageTolerance();
error JuiceBuyback_InvalidTwapWindow();
error JuiceBuyback_Unauthorized();
Expand All @@ -36,6 +38,7 @@ interface IJBBuybackDelegate is IJBPayDelegate3_1_1, IJBFundingCycleDataSource3_
event BuybackDelegate_Mint(uint256 indexed projectId, uint256 amountIn, uint256 tokenCount, address caller);
event BuybackDelegate_TwapWindowChanged(uint256 indexed projectId, uint256 oldSecondsAgo, uint256 newSecondsAgo, address caller);
event BuybackDelegate_TwapSlippageToleranceChanged(uint256 indexed projectId, uint256 oldTwapDelta, uint256 newTwapDelta, address caller);
event BuybackDelegate_TwapPercentLimitChanged(uint256 indexed projectId, uint256 oldSwapPercentLimit, newSwapPercentLimit, address caller);
event BuybackDelegate_PoolAdded(uint256 indexed projectId, address indexed terminalToken, address newPool, address caller);

/////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -69,4 +72,6 @@ interface IJBBuybackDelegate is IJBPayDelegate3_1_1, IJBFundingCycleDataSource3_
function setTwapWindowOf(uint256 projectId, uint32 newWindow) external;

function setTwapSlippageToleranceOf(uint256 projectId, uint256 newSlippageTolerance) external;

function setSwapPercentLimitOf(uint256 _projectId, uint256 _newSwapPercentLimit) external;
}