diff --git a/.vscode/settings.json b/.vscode/settings.json index fcb721a..08f8a97 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "solidity.compileUsingRemoteVersion": "v0.8.16+commit.07a7930e" + "solidity.compileUsingRemoteVersion": "v0.8.20+commit.a1b79de6" } diff --git a/README.md b/README.md index f1076ad..7ec807e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Provides a datasource and delegate which maximise the project token received by the contributor when they call `pay` on the terminal. In order to do so, the delegate will either mint new tokens ("vanilla" path, bypassing the delegate) or swap existing token in an Uniswap V3 pool ("buyback" path), depending on the best quote available at the time of the call. -This first iteration is only compatible with ETH terminals. +This first iteration is only compatible with 18-decimals token terminals. ## Design ### Flow @@ -29,4 +29,9 @@ Maximizing the project token received by the contributor while leveling the fund ## Risk & trade-offs - This delegate is, for now, only compatible with ETH as terminal token. - This delegate relies on the liquidity available in an Uniswap V3. If LP migrate to a new pool or another DEX, this delegate would need to be redeployed. - - A low liquidity might, if the max slippage isn't set properly, lead to an actual amount of token received lower than expected. \ No newline at end of file + - A low liquidity might, if the max slippage isn't set properly, lead to an actual amount of token received lower than expected. + +## Future work +- Non-18 decimals token should use a non fixed decimals (ln167, ln199, ln284 - `_data.amount.decimals`) and the tests should then use this same decimals (or the terminal decimal for forked tests, `IJBSingleTokenPaymentTerminal(address(jbEthPaymentTerminal)).decimals()`) +- Invariant are only partially tested (total supply hold in mint case, pool needfs additional tooling as we rely on hardcoded pool hash for create2) +- A first version was designed to be used as a BBD for an unique project (project token, pool, etc being immutables), in order to keep the gas cost as low as possible. This might be resumed and further tested if a need arises. \ No newline at end of file diff --git a/contracts/JBBuybackDelegate.sol b/contracts/JBBuybackDelegate.sol index d4da28f..1e783ce 100644 --- a/contracts/JBBuybackDelegate.sol +++ b/contracts/JBBuybackDelegate.sol @@ -1,504 +1,558 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.16; +pragma solidity ^0.8.20; -import {IJBController3_1} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBController3_1.sol"; +import {IJBPaymentTerminal} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPaymentTerminal.sol"; import {IJBPayoutRedemptionPaymentTerminal3_1_1} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPayoutRedemptionPaymentTerminal3_1_1.sol"; +import {JBDidPayData3_1_1} from "@jbx-protocol/juice-contracts-v3/contracts/structs/JBDidPayData3_1_1.sol"; +import {JBOperatable} from "@jbx-protocol/juice-contracts-v3/contracts/abstract/JBOperatable.sol"; import {IJBDirectory} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBDirectory.sol"; +import {IJBController3_1} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBController3_1.sol"; +import {IJBProjects} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBController3_1.sol"; +import {IJBOperatable} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBOperatable.sol"; import {IJBFundingCycleDataSource3_1_1} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleDataSource3_1_1.sol"; import {IJBPayDelegate3_1_1} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPayDelegate3_1_1.sol"; -import {IJBPaymentTerminal} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPaymentTerminal.sol"; - -import {JBTokens} from "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBTokens.sol"; - -import {JBDidPayData3_1_1} from "@jbx-protocol/juice-contracts-v3/contracts/structs/JBDidPayData3_1_1.sol"; -import {JBPayParamsData} from "@jbx-protocol/juice-contracts-v3/contracts/structs/JBPayParamsData.sol"; -import {JBRedeemParamsData} from "@jbx-protocol/juice-contracts-v3/contracts/structs/JBRedeemParamsData.sol"; import {JBPayDelegateAllocation3_1_1} from "@jbx-protocol/juice-contracts-v3/contracts/structs/JBPayDelegateAllocation3_1_1.sol"; +import {JBPayParamsData} from "@jbx-protocol/juice-contracts-v3/contracts/structs/JBPayParamsData.sol"; +import {JBRedeemParamsData} from "@jbx-protocol/juice-contracts-v3/contracts/structs/JBRedeemParamsData.sol"; import {JBRedemptionDelegateAllocation3_1_1} from "@jbx-protocol/juice-contracts-v3/contracts/structs/JBRedemptionDelegateAllocation3_1_1.sol"; - -import {JBDelegateMetadataHelper} from "@jbx-protocol/juice-delegate-metadata-lib/src/JBDelegateMetadataHelper.sol"; - +import {JBTokens} from "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBTokens.sol"; +import {JBDelegateMetadataLib} from "@jbx-protocol/juice-delegate-metadata-lib/src/JBDelegateMetadataLib.sol"; import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - -import {mulDiv18} from "@prb/math/src/Common.sol"; - -import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; -import {IUniswapV3SwapCallback} from "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol"; +import {mulDiv} from "@prb/math/src/Common.sol"; import {TickMath} from "@uniswap/v3-core/contracts/libraries/TickMath.sol"; - import {OracleLibrary} from "@uniswap/v3-periphery/contracts/libraries/OracleLibrary.sol"; - +import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; +import {JBBuybackDelegateOperations} from "./libraries/JBBuybackDelegateOperations.sol"; +import {IJBBuybackDelegate} from "./interfaces/IJBBuybackDelegate.sol"; import {IWETH9} from "./interfaces/external/IWETH9.sol"; -/** - * @custom:benediction DEVS BENEDICAT ET PROTEGAT CONTRACTVS MEAM - * - * @title Buyback Delegate compatible with any jb terminal that supports ETH payments - * - * @notice Datasource and delegate allowing pay beneficiary to get the highest amount - * of project tokens between minting using the project weigh and swapping in a - * given Uniswap V3 pool - * - * @dev This only supports ETH terminal. The pool is fixed, if a new pool offers deeper - * liquidity, this delegate needs to be redeployed. - */ - -contract JBBuybackDelegate is - Ownable, - ERC165, - JBDelegateMetadataHelper, - IJBFundingCycleDataSource3_1_1, - IJBPayDelegate3_1_1, - IUniswapV3SwapCallback -{ +/// @custom:benediction DEVS BENEDICAT ET PROTEGAT CONTRACTVS MEAM +/// @title JBBuybackDelegate +/// @notice Generic Buyback Delegate compatible with any Juicebox payment terminal and any project token that can be pooled. +/// @notice Functions as a Data Source and Delegate allowing beneficiaries of payments to get the highest amount +/// of a project's token between minting using the project weight and swapping in a given Uniswap V3 pool. +contract JBBuybackDelegate is ERC165, JBOperatable, IJBBuybackDelegate { //*********************************************************************// - // --------------------------- custom errors ------------------------- // + // --------------------- internal stored properties ------------------ // //*********************************************************************// - error JuiceBuyback_Unauthorized(); - error JuiceBuyback_MaximumSlippage(); - error JuiceBuyback_NewSecondsAgoTooLow(); - error JuiceBuyback_TransferFailed(); + /// @notice The TWAP max deviation acepted and timeframe to use for the pool twap, packed in a uint256. + /// @custom:param _projectId The ID of the project to which the TWAP params apply. + mapping(uint256 _projectId => uint256) internal _twapParamsOf; //*********************************************************************// - // ----------------------------- events ----------------------------- // + // --------------------- public constant properties ------------------ // //*********************************************************************// - event BuybackDelegate_Swap(uint256 projectId, uint256 amountEth, uint256 amountOut); - event BuybackDelegate_Mint(uint256 projectId); - event BuybackDelegate_SecondsAgoIncrease(uint256 oldSecondsAgo, uint256 newSecondsAgo); - event BuybackDelegate_TwapDeltaChanged(uint256 oldTwapDelta, uint256 newTwapDelta); - event BuybackDelegate_PendingSweep(address indexed beneficiary, uint256 amount); + /// @notice The unit of the max slippage. + uint256 public constant SLIPPAGE_DENOMINATOR = 10_000; - //*********************************************************************// - // --------------------- private constant properties ----------------- // - //*********************************************************************// + /// @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; - /** - * @notice Address project token < address terminal token ? - */ - bool immutable PROJECT_TOKEN_IS_TOKEN0; + /// @notice The maximum 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 MAX_TWAP_SLIPPAGE_TOLERANCE = 9000; - /** - * @notice The unit of the max slippage (expressed in 1/10000th) - */ - uint256 constant SLIPPAGE_DENOMINATOR = 10000; + /// @notice The smallest TWAP period allowed, in seconds. + /// @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_WINDOW = 2 minutes; + + /// @notice The largest TWAP period allowed, in seconds. + /// @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 MAX_TWAP_WINDOW = 2 days; //*********************************************************************// - // --------------------- public constant properties ------------------ // + // -------------------- public immutable properties ------------------ // //*********************************************************************// - /** - * @notice The project token address - * - * @dev In this context, this is the tokenOut - */ - IERC20 public immutable PROJECT_TOKEN; - - /** - * @notice The uniswap pool corresponding to the project token-other token market - * (this should be carefully chosen liquidity wise) - */ - IUniswapV3Pool public immutable POOL; - - /** - * @notice The JB Directory - */ + /// @notice The uniswap v3 factory used to reference pools from. + address public immutable UNISWAP_V3_FACTORY; + + /// @notice The directory of terminals and controllers. IJBDirectory public immutable DIRECTORY; - /** - * @notice The project controller - */ + /// @notice The controller used to mint and burn tokens from. IJBController3_1 public immutable CONTROLLER; - /** - * @notice The WETH contract - */ - IWETH9 public immutable WETH; + /// @notice The project registry. + IJBProjects public immutable PROJECTS; - /** - * @notice The 4bytes ID of this delegate, used for metadata parsing - */ + /// @notice The WETH contract. + IWETH9 public immutable WETH; - bytes4 public immutable delegateId; + /// @notice The 4bytes ID of this delegate, used for metadata parsing. + bytes4 public immutable DELEGATE_ID; //*********************************************************************// // --------------------- public stored properties -------------------- // //*********************************************************************// - // the timeframe to use for the pool twap (from secondAgo to now) - uint32 public secondsAgo; - - // the twap max deviation acepted (in 10_000th) - uint256 public twapDelta; + /// @notice The uniswap pool corresponding to the project token <-> terminal token pair. + /// @custom:param _projectId The ID of the project to which the pool applies. + /// @custom:param _terminalToken The address of the token being used to make payments in. + mapping(uint256 _projectId => mapping(address _terminalToken => IUniswapV3Pool)) public poolOf; - // any ETH left-over in this contract (from swap in the end of liquidity range) - mapping(address => uint256) public sweepBalanceOf; - - // running cumulative sum of ETH left-over - uint256 public sweepBalance; + /// @notice Each project's token. + /// @custom:param _projectId The ID of the project to which the token belongs. + mapping(uint256 _projectId => address) public projectTokenOf; //*********************************************************************// - // ---------------------------- Constructor -------------------------- // + // ---------------------------- constructor -------------------------- // //*********************************************************************// - /** - * @dev No other logic besides initializing the immutables - */ + /// @param _weth The WETH contract. + /// @param _factory The uniswap v3 factory used to reference pools from. + /// @param _directory The directory of terminals and controllers. + /// @param _controller The controller used to mint and burn tokens from. + /// @param _delegateId The 4bytes ID of this delegate, used for metadata parsing. constructor( - IERC20 _projectToken, IWETH9 _weth, address _factory, - uint24 _fee, - uint32 _secondsAgo, - uint256 _twapDelta, IJBDirectory _directory, IJBController3_1 _controller, bytes4 _delegateId - ) { - PROJECT_TOKEN = _projectToken; + ) JBOperatable(IJBOperatable(address(_controller)).operatorStore()) { WETH = _weth; DIRECTORY = _directory; CONTROLLER = _controller; - PROJECT_TOKEN_IS_TOKEN0 = address(_projectToken) < address(_weth); - POOL = IUniswapV3Pool( - address( - uint160( - uint256( - keccak256( - abi.encodePacked( - hex"ff", - _factory, - keccak256( - abi.encode( - PROJECT_TOKEN_IS_TOKEN0 ? _projectToken : _weth, - PROJECT_TOKEN_IS_TOKEN0 ? _weth : _projectToken, - _fee - ) - ), - bytes32(0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54) - ) - ) - ) - ) - ) - ); - - secondsAgo = _secondsAgo; - twapDelta = _twapDelta; - delegateId = _delegateId; + UNISWAP_V3_FACTORY = _factory; + DELEGATE_ID = _delegateId; + PROJECTS = _controller.projects(); } //*********************************************************************// - // ---------------------- external functions ------------------------- // + // ------------------------- external views -------------------------- // //*********************************************************************// - /** - * @notice The datasource implementation - * - * @param _data the data passed to the data source in terminal.pay(..). _data.metadata need to have the Uniswap quote - * this quote should be set as 0 if the user wants to use the vanilla minting path - * @return weight the weight to use (the one passed if not max reserved rate, 0 if swapping or the one corresponding - * to the reserved token to mint if minting) - * @return memo the original memo passed - * @return delegateAllocations The amount to send to delegates instead of adding to the local balance. - */ + /// @notice The DataSource implementation that determines if a swap path and/or a mint path should be taken. + /// @param _data The data passed to the data source in terminal.pay(..). _data.metadata can have a Uniswap quote and specify how much of the payment should be used to swap, otherwise a quote will be determined from a TWAP and use the full amount paid in. + /// @return weight The weight to use, which is the original weight passed in if no swap path is taken, 0 if only the swap path is taken, and an adjusted weight if the both the swap and mint paths are taken. + /// @return memo the original memo passed + /// @return delegateAllocations The amount to send to delegates instead of adding to the local balance. This is empty if only the mint path is taken. function payParams(JBPayParamsData calldata _data) external view override returns (uint256 weight, string memory memo, JBPayDelegateAllocation3_1_1[] memory delegateAllocations) { - // Find the total number of tokens to mint, as a fixed point number with 18 decimals - uint256 _tokenCount = mulDiv18(_data.amount.value, _data.weight); + // Keep a reference to the payment total + uint256 _totalPaid = _data.amount.value; + + // Keep a reference to the weight + uint256 _weight = _data.weight; - // Get a quote based on either the uni SDK quote or a twap from the pool - uint256 _swapAmountOut; + // Keep a reference to the minimum number of tokens expected to be swapped for. + uint256 _minimumSwapAmountOut; - (bool _validQuote, bytes memory _metadata) = getMetadata(delegateId, _data.metadata); + // Keep a reference to the amount from the payment to allocate towards a swap. + uint256 _amountToSwapWith; - uint256 _quote; - uint256 _slippage; - if (_validQuote) (_quote, _slippage) = abi.decode(_metadata, (uint256, uint256)); + // Keep a reference to a flag indicating if the quote passed into the metadata exists. + bool _quoteExists; - if (_quote != 0) { - // Unpack the quote from the pool, given by the frontend - this one takes precedence on the twap - // as it should be closer to the current pool state, if not, use the twap - _swapAmountOut = _quote - ((_quote * _slippage) / SLIPPAGE_DENOMINATOR); - } else { - _swapAmountOut = _getQuote(_data.amount.value); + // Scoped section to prevent Stack Too Deep. + { + bytes memory _metadata; + + // Unpack the quote from the pool, given by the frontend. + (_quoteExists, _metadata) = JBDelegateMetadataLib.getMetadata(DELEGATE_ID, _data.metadata); + if (_quoteExists) (_minimumSwapAmountOut, _amountToSwapWith) = abi.decode(_metadata, (uint256, uint256)); } - // If the minimum amount received from swapping is greather than received when minting, use the swap pathway - if (_tokenCount < _swapAmountOut) { - // Return this delegate as the one to use, along the quote and reserved rate, and do not mint from the terminal + // If no amount was specified to swap with, default to the full amount of the payment. + if (_amountToSwapWith == 0) _amountToSwapWith = _totalPaid; + + // Find the default total number of tokens to mint as if no Buyback Delegate were installed, as a fixed point number with 18 decimals + + uint256 _tokenCountWithoutDelegate = mulDiv(_amountToSwapWith, _weight, 10 ** _data.amount.decimals); + + // Keep a reference to the project's token. + address _projectToken = projectTokenOf[_data.projectId]; + + // Keep a reference to the token being used by the terminal that is calling this delegate. Use weth is ETH. + address _terminalToken = _data.amount.token == JBTokens.ETH ? address(WETH) : _data.amount.token; + + // If a minimum amount of tokens to swap for wasn't specified, resolve a value as good as possible using a TWAP. + if (_minimumSwapAmountOut == 0) { + _minimumSwapAmountOut = _getQuote(_data.projectId, _projectToken, _amountToSwapWith, _terminalToken); + } + + // If the minimum amount received from swapping is greather than received when minting, use the swap path. + if (_tokenCountWithoutDelegate < _minimumSwapAmountOut) { + // Make sure the amount to swap with is at most the full amount being paid. + if (_amountToSwapWith > _totalPaid) revert JuiceBuyback_InsufficientPayAmount(); + + // Keep a reference to a flag indicating if the pool will reference the project token as the first in the pair. + bool _projectTokenIs0 = address(_projectToken) < _terminalToken; + + // Return this delegate as the one to use, while forwarding the amount to swap with. Speficy metadata that allows the swap to be executed. delegateAllocations = new JBPayDelegateAllocation3_1_1[](1); delegateAllocations[0] = JBPayDelegateAllocation3_1_1({ delegate: IJBPayDelegate3_1_1(this), - amount: _data.amount.value, - metadata: abi.encode(_tokenCount, _swapAmountOut) + amount: _amountToSwapWith, + metadata: abi.encode(_quoteExists, _projectTokenIs0, _minimumSwapAmountOut, _totalPaid == _amountToSwapWith ? 0 : _totalPaid - _amountToSwapWith, _weight) }); - return (0, _data.memo, delegateAllocations); + // All the mint will be done in didPay, return 0 as weight to avoid minting via the terminal + return ( + 0, + _data.memo, + delegateAllocations + ); } - // If minting, do not use this as delegate (delegateAllocations is left uninitialised) + // If only minting, delegateAllocations is left uninitialised and the full weight is returned for the terminal to mint. return (_data.weight, _data.memo, delegateAllocations); } - /** - * @notice Delegate to either swap to the beneficiary or mint to the beneficiary - * - * @dev This delegate is called only if the quote for the swap is bigger than the lowest received when minting. - * If the swap reverts (slippage, liquidity, etc), the delegate will then mint the same amount of token as - * if the delegate was not used. - * If the beneficiary requests non claimed token, the swap is not used (as it is, per definition, claimed token) - * - * @param _data the delegate data passed by the terminal - */ + /// @notice The timeframe to use for the pool TWAP. + /// @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]); + } + + /// @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; + } + + /// @notice Generic redeem params, for interface completion. + /// @dev This is a passthrough of the redemption parameters + /// @param _data The redeem data passed by the terminal. + function redeemParams(JBRedeemParamsData calldata _data) + external + pure + override + returns (uint256, string memory, JBRedemptionDelegateAllocation3_1_1[] memory delegateAllocations) + { + return (_data.reclaimAmount.value, _data.memo, delegateAllocations); + } + + //*********************************************************************// + // -------------------------- public views --------------------------- // + //*********************************************************************// + + function supportsInterface(bytes4 _interfaceId) public view override(ERC165, IERC165) returns (bool) { + return _interfaceId == type(IJBFundingCycleDataSource3_1_1).interfaceId + || _interfaceId == type(IJBPayDelegate3_1_1).interfaceId + || _interfaceId == type(IJBBuybackDelegate).interfaceId + || super.supportsInterface(_interfaceId); + } + + //*********************************************************************// + // ---------------------- external transactions ---------------------- // + //*********************************************************************// + + /// @notice Delegate used to swap a provided amount to the beneficiary, using any leftover amount to mint. + /// @dev This delegate is called only if the quote for the swap is bigger than the quote when minting. + /// If the swap reverts (slippage, liquidity, etc), the delegate will then mint the same amount of token as if the delegate was not used. + /// @param _data The delegate data passed by the terminal. function didPay(JBDidPayData3_1_1 calldata _data) external payable override { - // Access control as minting is authorized to this delegate + // Make sure only a payment terminal belonging to the project can access this functionality. if (!DIRECTORY.isTerminalOf(_data.projectId, IJBPaymentTerminal(msg.sender))) { revert JuiceBuyback_Unauthorized(); } - (uint256 _tokenCount, uint256 _swapMinAmountOut) = abi.decode(_data.dataSourceMetadata, (uint256, uint256)); - - // Try swapping - uint256 _amountReceived = _swap(_data, _swapMinAmountOut); + // Parse the metadata passed in from the data source. + ( + bool _quoteExists, + bool _projectTokenIs0, + uint256 _minimumSwapAmountOut, + uint256 _amountToMintWith, + uint256 _weight + ) = abi.decode(_data.dataSourceMetadata, (bool, bool, uint256, uint256, uint256)); + + // Get a reference to the amount of tokens that was swapped for. + uint256 _exactSwapAmountOut = _swap(_data, _projectTokenIs0); + + // Make sure the slippage is tolerable if passed in via an explicit quote. + if (_quoteExists && _exactSwapAmountOut < _minimumSwapAmountOut) revert JuiceBuyback_MaximumSlippage(); + + // Get a reference to any amount of tokens paid in remaining in this contract. + uint256 _terminalTokenInThisContract = _data.forwardedAmount.token == JBTokens.ETH + ? address(this).balance + : IERC20(_data.forwardedAmount.token).balanceOf(address(this)); + + // Use any leftover amount of tokens paid in remaining to mint. + // Keep a reference to the number of tokens being minted. + uint256 _partialMintTokenCount; + if (_terminalTokenInThisContract != 0) { + _partialMintTokenCount = mulDiv(_terminalTokenInThisContract, _weight, 10 ** _data.amount.decimals); + + // If the token paid in wasn't ETH, give the terminal permission to pull them back into its balance. + if (_data.forwardedAmount.token != JBTokens.ETH) { + IERC20(_data.forwardedAmount.token).approve(msg.sender, _terminalTokenInThisContract); + } + + // Add the paid amount back to the project's terminal balance. + IJBPayoutRedemptionPaymentTerminal3_1_1(msg.sender).addToBalanceOf{ + value: _data.forwardedAmount.token == JBTokens.ETH ? _terminalTokenInThisContract : 0 + }(_data.projectId, _terminalTokenInThisContract, _data.forwardedAmount.token, "", ""); + + emit BuybackDelegate_Mint(_data.projectId, _terminalTokenInThisContract, _partialMintTokenCount, msg.sender); + } + + // Add amount to mint to leftover mint amount (avoiding stack too deep here) + _partialMintTokenCount += mulDiv(_amountToMintWith, _weight, 10 ** _data.amount.decimals); + + // Mint the whole amount of tokens again together with the (optional partial mint), such that the correct portion of reserved tokens get taken into account. + CONTROLLER.mintTokensOf({ + projectId: _data.projectId, + tokenCount: _exactSwapAmountOut + _partialMintTokenCount, + beneficiary: address(_data.beneficiary), + memo: _data.memo, + preferClaimedTokens: _data.preferClaimedTokens, + useReservedRate: true + }); + } - // If swap failed, mint instead, with the original weight + add to balance the token in - if (_amountReceived == 0) _mint(_data, _tokenCount); + /// @notice The Uniswap V3 pool callback where the token transfer is expected to happen. + /// @param _amount0Delta The amount of token 0 being used for the swap. + /// @param _amount1Delta The amount of token 1 being used for the swap. + /// @param _data Data passed in by the swap operation. + function uniswapV3SwapCallback(int256 _amount0Delta, int256 _amount1Delta, bytes calldata _data) + external + override + { + // Unpack the data passed in through the swap hook. + (uint256 _projectId, address _terminalToken) = abi.decode(_data, (uint256, address)); - // Track any new eth left-over - if (address(this).balance > 0 && address(this).balance != sweepBalance) { - sweepBalanceOf[_data.beneficiary] += address(this).balance - sweepBalance; + // Get the terminal token, using WETH if the token paid in is ETH. + address _terminalTokenWithWETH = _terminalToken == JBTokens.ETH ? address(WETH) : _terminalToken; - emit BuybackDelegate_PendingSweep(_data.beneficiary, address(this).balance - sweepBalance); + // Make sure this call is being made from within the swap execution. + if (msg.sender != address(poolOf[_projectId][_terminalTokenWithWETH])) revert JuiceBuyback_Unauthorized(); - sweepBalance = address(this).balance; - } - } + // Keep a reference to the amount of tokens that should be sent to fulfill the swap (the positive delta) + uint256 _amountToSendToPool = _amount0Delta < 0 ? uint256(_amount1Delta) : uint256(_amount0Delta); - /** - * @notice The Uniswap V3 pool callback (where token transfer should happens) - * - * @dev Slippage controle is achieved here - */ - function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external override { - // Check if this is really a callback - if (msg.sender != address(POOL)) revert JuiceBuyback_Unauthorized(); - - // Unpack the data - uint256 _minimumAmountReceived = abi.decode(data, (uint256)); - - // delta is in regard of the pool balance (positive = pool need to receive) - uint256 _amountToSendToPool = PROJECT_TOKEN_IS_TOKEN0 ? uint256(amount1Delta) : uint256(amount0Delta); - uint256 _amountReceivedForBeneficiary = - PROJECT_TOKEN_IS_TOKEN0 ? uint256(-amount0Delta) : uint256(-amount1Delta); - - // Revert if slippage is too high - if (_amountReceivedForBeneficiary < _minimumAmountReceived) { - revert JuiceBuyback_MaximumSlippage(); - } + // Wrap ETH into WETH if relevant (do not rely on ETH delegate balance to support pure WETH terminals) + if (_terminalToken == JBTokens.ETH) WETH.deposit{value: _amountToSendToPool}(); - // Wrap and transfer the WETH to the pool - WETH.deposit{value: _amountToSendToPool}(); - WETH.transfer(address(POOL), _amountToSendToPool); + // Transfer the token to the pool. + IERC20(_terminalTokenWithWETH).transfer(msg.sender, _amountToSendToPool); } - /** - * @notice Generic redeem params, for interface completion - * - * @dev This is a passthrough of the redemption parameters - * - * @param _data the redeem data passed by the terminal - */ - function redeemParams(JBRedeemParamsData calldata _data) + /// @notice Add a pool for a given project. This pool the becomes the default for a given token project <--> terminal token pair. + /// @dev Uses create2 for callback auth and allows adding a pool not deployed yet. + /// This can be called by the project owner or an address having the SET_POOL permission in JBOperatorStore + /// @param _projectId The ID of the project having its pool set. + /// @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 _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) external - pure - override - returns ( - uint256 reclaimAmount, - string memory memo, - JBRedemptionDelegateAllocation3_1_1[] memory delegateAllocations - ) + requirePermission(PROJECTS.ownerOf(_projectId), _projectId, JBBuybackDelegateOperations.CHANGE_POOL) + returns (IUniswapV3Pool newPool) { - return (_data.reclaimAmount.value, _data.memo, delegateAllocations); - } + // Make sure the provided delta is within sane bounds. + if (_twapSlippageTolerance < MIN_TWAP_SLIPPAGE_TOLERANCE || _twapSlippageTolerance > MAX_TWAP_SLIPPAGE_TOLERANCE) revert JuiceBuyback_InvalidTwapSlippageTolerance(); - /** - * @notice Increase the period over which the twap is computed - * - * @param _newSecondsAgo the new period - */ - function increaseSecondsAgo(uint32 _newSecondsAgo) external onlyOwner { - uint32 _oldSecondsAgo = secondsAgo; + // Make sure the provided period is within sane bounds. + if (_twapWindow < MIN_TWAP_WINDOW || _twapWindow > MAX_TWAP_WINDOW) revert JuiceBuyback_InvalidTwapWindow(); - if (_newSecondsAgo <= _oldSecondsAgo) revert JuiceBuyback_NewSecondsAgoTooLow(); + // Keep a reference to the project's token. + address _projectToken = address(CONTROLLER.tokenStore().tokenOf(_projectId)); - secondsAgo = _newSecondsAgo; + // Make sure the project has issued a token. + if (_projectToken == address(0)) revert JuiceBuyback_NoProjectToken(); - emit BuybackDelegate_SecondsAgoIncrease(_oldSecondsAgo, _newSecondsAgo); - } + // If the terminal token specified in ETH, use WETH instead. + if (_terminalToken == JBTokens.ETH) _terminalToken = address(WETH); + + // Keep a reference to a flag indicating if the pool will reference the project token as the first in the pair. + bool _projectTokenIs0 = address(_projectToken) < _terminalToken; + + // Compute the corresponding pool's address, which is a function of both tokens and the specified fee. + newPool = IUniswapV3Pool( + address( + uint160( + uint256( + keccak256( + abi.encodePacked( + hex"ff", + UNISWAP_V3_FACTORY, + keccak256( + abi.encode( + _projectTokenIs0 ? _projectToken : _terminalToken, + _projectTokenIs0 ? _terminalToken : _projectToken, + _fee + ) + ), + // POOL_INIT_CODE_HASH from https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/PoolAddress.sol + bytes32(0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54) + ) + ) + ) + ) + ) + ); + + // Make sure this pool has yet to be specified in this delegate. + if (poolOf[_projectId][_terminalToken] == newPool) revert JuiceBuyback_PoolAlreadySet(); - /** - * @notice Set the maximum deviation allowed between amount received and twap - * - * @param _newDelta the new delta, in 10_000th - */ - function setTwapDelta(uint256 _newDelta) external onlyOwner { - uint256 _oldDelta = twapDelta; + // Store the pool. + poolOf[_projectId][_terminalToken] = newPool; - twapDelta = _newDelta; + // Store the twap period and max slipage. + _twapParamsOf[_projectId] = _twapSlippageTolerance << 128 | _twapWindow; + projectTokenOf[_projectId] = address(_projectToken); - emit BuybackDelegate_TwapDeltaChanged(_oldDelta, _newDelta); + emit BuybackDelegate_TwapWindowChanged(_projectId, 0, _twapWindow, msg.sender); + emit BuybackDelegate_TwapSlippageToleranceChanged(_projectId, 0, _twapSlippageTolerance, msg.sender); + emit BuybackDelegate_PoolAdded(_projectId, _terminalToken, address(newPool), msg.sender); } - /** - * @notice Sweep the eth left-over in this contract - */ - function sweep(address _beneficiary) external { - // The beneficiary ETH balance in this contract leftover - uint256 _balance = sweepBalanceOf[_beneficiary]; + /// @notice Increase the period over which the TWAP is computed. + /// @dev This can be called by the project owner or an address having the SET_TWAP_PERIOD permission in JBOperatorStore. + /// @param _projectId The ID for which the new value applies. + /// @param _newWindow The new TWAP period. + function setTwapWindowOf(uint256 _projectId, uint32 _newWindow) + external + requirePermission(PROJECTS.ownerOf(_projectId), _projectId, JBBuybackDelegateOperations.SET_POOL_PARAMS) + { + // Make sure the provided period is within sane bounds. + if (_newWindow < MIN_TWAP_WINDOW || _newWindow > MAX_TWAP_WINDOW) { + revert JuiceBuyback_InvalidTwapWindow(); + } + + // Keep a reference to the currently stored TWAP params. + uint256 _twapParams = _twapParamsOf[_projectId]; + + // Keep a reference to the old window value. + uint256 _oldWindow = uint128(_twapParams); - // If no balance, don't do anything - if (_balance == 0) return; + // Store the new packed value of the TWAP params. + _twapParamsOf[_projectId] = uint256(_newWindow) | ((_twapParams >> 128) << 128); - // Reset beneficiary balance - sweepBalanceOf[_beneficiary] = 0; + emit BuybackDelegate_TwapWindowChanged(_projectId, _oldWindow, _newWindow, msg.sender); + } - // Keep the contract balance up to date - sweepBalance = address(this).balance - _balance; + /// @notice Set the maximum deviation allowed between amount received and TWAP. + /// @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 _newSlippageTolerance the new delta, out of SLIPPAGE_DENOMINATOR. + function setTwapSlippageToleranceOf(uint256 _projectId, uint256 _newSlippageTolerance) + external + requirePermission(PROJECTS.ownerOf(_projectId), _projectId, JBBuybackDelegateOperations.SET_POOL_PARAMS) + { + // Make sure the provided delta is within sane bounds. + if (_newSlippageTolerance < MIN_TWAP_SLIPPAGE_TOLERANCE || _newSlippageTolerance > MAX_TWAP_SLIPPAGE_TOLERANCE) revert JuiceBuyback_InvalidTwapSlippageTolerance(); - // Send the eth to the beneficiary - (bool _success,) = payable(_beneficiary).call{value: _balance}(""); - if (!_success) revert JuiceBuyback_TransferFailed(); + // Keep a reference to the currently stored TWAP params. + uint256 _twapParams = _twapParamsOf[_projectId]; - emit BuybackDelegate_PendingSweep(_beneficiary, 0); + // Keep a reference to the old slippage value. + uint256 _oldSlippageTolerance = _twapParams >> 128; + + // Store the new packed value of the TWAP params. + _twapParamsOf[_projectId] = _newSlippageTolerance << 128 | ((_twapParams << 128) >> 128); + + emit BuybackDelegate_TwapSlippageToleranceChanged(_projectId, _oldSlippageTolerance, _newSlippageTolerance, msg.sender); } //*********************************************************************// // ---------------------- internal functions ------------------------- // //*********************************************************************// - /** - * @notice Get a quote based on twap over a secondsAgo period, taking into account a twapDelta max deviation - * - * @param _amountIn the amount to swap - * - * @return _amountOut the minimum amount received according to the twap - */ - function _getQuote(uint256 _amountIn) internal view returns (uint256 _amountOut) { - // If non-existing or non-initialized pool, quote 0 - try POOL.slot0() returns (uint160, int24, uint16, uint16, uint16, uint8, bool unlocked) { - // non initialized? + /// @notice Get a quote based on TWAP over a secondsAgo period, taking into account a twapDelta max deviation. + /// @param _projectId The ID of the project for which the swap is being made. + /// @param _projectToken The project's token being swapped for. + /// @param _amountIn The amount being used to swap. + /// @param _terminalToken The token paid in being used to swap. + /// @return amountOut the minimum amount received according to the TWAP. + function _getQuote(uint256 _projectId, address _projectToken, uint256 _amountIn, address _terminalToken) + internal + view + returns (uint256 amountOut) + { + // Get a reference to the pool that'll be used to make the swap. + IUniswapV3Pool _pool = poolOf[_projectId][address(_terminalToken)]; + + // Make sure the pool exists. + try _pool.slot0() returns (uint160, int24, uint16, uint16, uint16, uint8, bool unlocked) { + // If the pool hasn't been initialized, return an empty quote. if (!unlocked) return 0; } catch { - // invalid address or not deployed yet? + // If the address is invalid or if the pool has not yet been deployed, return an empty quote. return 0; } - // Get the twap tick - (int24 arithmeticMeanTick,) = OracleLibrary.consult(address(POOL), secondsAgo); + // Unpack the TWAP params and get a reference to the period and slippage. + uint256 _twapParams = _twapParamsOf[_projectId]; + uint32 _quotePeriod = uint32(_twapParams); + uint256 _maxDelta = _twapParams >> 128; + + // Keep a reference to the TWAP tick. + (int24 arithmeticMeanTick,) = OracleLibrary.consult(address(_pool), _quotePeriod); - // Get a quote based on this twap tick - _amountOut = - OracleLibrary.getQuoteAtTick(arithmeticMeanTick, uint128(_amountIn), address(WETH), address(PROJECT_TOKEN)); + // Get a quote based on this TWAP tick. + amountOut = OracleLibrary.getQuoteAtTick({ + tick: arithmeticMeanTick, + baseAmount: uint128(_amountIn), + baseToken: _terminalToken, + quoteToken: address(_projectToken) + }); - // Return the lowest twap accepted - _amountOut -= (_amountOut * twapDelta) / SLIPPAGE_DENOMINATOR; + // Return the lowest TWAP tolerable. + amountOut -= (amountOut * _maxDelta) / SLIPPAGE_DENOMINATOR; } - /** - * @notice Swap the terminal token to receive the project toke_beforeTransferTon - * - * @dev This delegate first receive the whole amount of project token, - * then send the non-reserved token to the beneficiary, - * then burn the rest of this delegate balance (ie the amount of reserved token), - * then mint the same amount as received (this will add the reserved token, following the fc rate) - * then burn the difference (ie this delegate balance) - * -> End result is having the correct balances (beneficiary and reserve), according to the reserve rate - * - * @param _data the didPayData passed by the terminal - * @param _minimumReceivedFromSwap the minimum amount received, to prevent slippage - */ - function _swap(JBDidPayData3_1_1 calldata _data, uint256 _minimumReceivedFromSwap) - internal - returns (uint256 _amountReceived) - { - // Pass the token and min amount to receive as extra data - try POOL.swap({ + /// @notice Swap the terminal token to receive the project token. + /// @param _data The didPayData passed by the terminal. + /// @param _projectTokenIs0 A flag indicating if the pool will reference the project token as the first in the pair. + /// @return amountReceived The amount of tokens received from the swap. + function _swap( + JBDidPayData3_1_1 calldata _data, + bool _projectTokenIs0 + ) internal returns (uint256 amountReceived) { + // The amount of tokens that are being used with which to make the swap. + uint256 _amountToSwapWith = _data.forwardedAmount.value; + + // Get the terminal token, using WETH if the token paid in is ETH. + address _terminalTokenWithWETH = _data.forwardedAmount.token == JBTokens.ETH ? address(WETH) : _data.forwardedAmount.token; + + // Get a reference to the pool that'll be used to make the swap. + IUniswapV3Pool _pool = poolOf[_data.projectId][_terminalTokenWithWETH]; + + // Try swapping. + try _pool.swap({ recipient: address(this), - zeroForOne: !PROJECT_TOKEN_IS_TOKEN0, - amountSpecified: int256(_data.amount.value), - sqrtPriceLimitX96: PROJECT_TOKEN_IS_TOKEN0 ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1, - data: abi.encode(_minimumReceivedFromSwap) + zeroForOne: !_projectTokenIs0, + amountSpecified: int256(_amountToSwapWith), + sqrtPriceLimitX96: _projectTokenIs0 ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1, + data: abi.encode(_data.projectId, _data.forwardedAmount.token) }) returns (int256 amount0, int256 amount1) { - // Swap succeded, take note of the amount of PROJECT_TOKEN received (negative as it is an exact input) - _amountReceived = uint256(-(PROJECT_TOKEN_IS_TOKEN0 ? amount0 : amount1)); + // If the swap succeded, take note of the amount of tokens received. This will return as negative since it is an exact input. + amountReceived = uint256(-(_projectTokenIs0 ? amount0 : amount1)); } catch { - // implies _amountReceived = 0 -> will later mint when back in didPay - return _amountReceived; + // If the swap failed, return. + return 0; } + // Burn the whole amount received. CONTROLLER.burnTokensOf({ holder: address(this), projectId: _data.projectId, - tokenCount: _amountReceived, + tokenCount: amountReceived, memo: "", preferClaimedTokens: true }); - CONTROLLER.mintTokensOf({ - projectId: _data.projectId, - tokenCount: _amountReceived, - beneficiary: address(_data.beneficiary), - memo: _data.memo, - preferClaimedTokens: _data.preferClaimedTokens, - useReservedRate: true - }); - - emit BuybackDelegate_Swap(_data.projectId, _data.amount.value, _amountReceived); - } + // We return the amount we received/burned and we will mint them to the user later - /** - * @notice Mint the token out, sending back the token in the terminal - * - * @param _data the didPayData passed by the terminal - * @param _amount the amount of token out to mint - */ - function _mint(JBDidPayData3_1_1 calldata _data, uint256 _amount) internal { - // Mint to the beneficiary with the fc reserve rate - CONTROLLER.mintTokensOf({ - projectId: _data.projectId, - tokenCount: _amount, - beneficiary: _data.beneficiary, - memo: _data.memo, - preferClaimedTokens: _data.preferClaimedTokens, - useReservedRate: true - }); - - // Send the eth back to the terminal balance - IJBPayoutRedemptionPaymentTerminal3_1_1(msg.sender).addToBalanceOf{value: _data.amount.value}( - _data.projectId, _data.amount.value, JBTokens.ETH, "", "" - ); - - emit BuybackDelegate_Mint(_data.projectId); - } - - //*********************************************************************// - // ---------------------- peripheral functions ----------------------- // - //*********************************************************************// - - function supportsInterface(bytes4 _interfaceId) public view override(ERC165, IERC165) returns (bool) { - return _interfaceId == type(IJBFundingCycleDataSource3_1_1).interfaceId - || _interfaceId == type(IJBPayDelegate3_1_1).interfaceId || super.supportsInterface(_interfaceId); + emit BuybackDelegate_Swap(_data.projectId, _amountToSwapWith, _pool, amountReceived, msg.sender); } } diff --git a/contracts/JBGenericBuybackDelegate.sol b/contracts/JBGenericBuybackDelegate.sol deleted file mode 100644 index 4c6a780..0000000 --- a/contracts/JBGenericBuybackDelegate.sol +++ /dev/null @@ -1,644 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IJBPaymentTerminal} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPaymentTerminal.sol"; -import {IJBPayoutRedemptionPaymentTerminal3_1_1 } from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPayoutRedemptionPaymentTerminal3_1_1.sol"; -import {IJBSingleTokenPaymentTerminal} from "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBSingleTokenPaymentTerminal.sol"; -import {JBDidPayData3_1_1} from "@jbx-protocol/juice-contracts-v3/contracts/structs/JBDidPayData3_1_1.sol"; -import {IJBOperatable, JBOperatable} from "@jbx-protocol/juice-contracts-v3/contracts/abstract/JBOperatable.sol"; -import {JBPayDelegateAllocation3_1_1} from "@jbx-protocol/juice-contracts-v3/contracts/structs/JBPayDelegateAllocation3_1_1.sol"; -import {JBPayParamsData} from "@jbx-protocol/juice-contracts-v3/contracts/structs/JBPayParamsData.sol"; -import {JBRedeemParamsData} from "@jbx-protocol/juice-contracts-v3/contracts/structs/JBRedeemParamsData.sol"; -import {JBRedemptionDelegateAllocation3_1_1} from "@jbx-protocol/juice-contracts-v3/contracts/structs/JBRedemptionDelegateAllocation3_1_1.sol"; -import {JBTokens} from "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBTokens.sol"; - -import {JBDelegateMetadataLib} from "@jbx-protocol/juice-delegate-metadata-lib/src/JBDelegateMetadataLib.sol"; - -import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; -import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; - -import {mulDiv18} from "@prb/math/src/Common.sol"; - -import {TickMath} from "@uniswap/v3-core/contracts/libraries/TickMath.sol"; -import {OracleLibrary} from "@uniswap/v3-periphery/contracts/libraries/OracleLibrary.sol"; - -import {JBBuybackDelegateOperations} from "./libraries/JBBuybackDelegateOperations.sol"; -import /** {*} from */ "./interfaces/IJBGenericBuybackDelegate.sol"; - -/** - * @custom:benediction DEVS BENEDICAT ET PROTEGAT CONTRACTVS MEAM - * - * @title Generic Buyback Delegate compatible with any jb terminal, any project token (except fee on transfer) - * - * @notice Datasource and delegate allowing pay beneficiary to get the highest amount - * of project tokens between minting using the project weigh and swapping in a - * given Uniswap V3 pool - * - * @dev This supports any terminal and token, as well as any number of projects using it. - */ - -contract JBGenericBuybackDelegate is - ERC165, - JBOperatable, - IJBGenericBuybackDelegate -{ - - //*********************************************************************// - // --------------------- public constant properties ----------------- // - //*********************************************************************// - /** - * @notice The unit of the max slippage (expressed in 1/10000th) - */ - uint256 public constant SLIPPAGE_DENOMINATOR = 10000; - - /** - * @notice The minimum twap deviation allowed (0.1%, in 1/10000th) - * - * @dev This is to avoid bypassing the swap when a quote is not provided - * (ie in fees/automated pay) - */ - uint256 public constant MIN_TWAP_DELTA = 100; - - /** - * @notice The minimmaximum twap deviation allowed (9%, in 1/10000th) - * - * @dev This is to avoid bypassing the swap when a quote is not provided - * (ie in fees/automated pay) - */ - uint256 public constant MAX_TWAP_DELTA = 9000; - - /** - * @notice The smallest TWAP period allowed, in seconds. - * - * @dev This is to avoid having a too short twap, prone to pool manipulation - */ - uint256 public constant MIN_SECONDS_AGO = 2 minutes; - - /** - * @notice The biggest TWAP period allowed, in seconds. - * - * @dev This is to avoid having a too long twap, bypassing the swap - */ - uint256 public constant MAX_SECONDS_AGO = 2 days; - - /** - * @notice The uniswap v3 factory - */ - address public immutable UNISWAP_V3_FACTORY; - - /** - * @notice The JB Directory - */ - IJBDirectory public immutable DIRECTORY; - - /** - * @notice The project controller - */ - IJBController3_1 public immutable CONTROLLER; - - /** - * @notice The project registry - */ - IJBProjects public immutable PROJECTS; - - /** - * @notice The WETH contract - */ - IWETH9 public immutable WETH; - - /** - * @notice The 4bytes ID of this delegate, used for metadata parsing - */ - bytes4 public immutable delegateId; - - //*********************************************************************// - // --------------------- public stored properties -------------------- // - //*********************************************************************// - - /** - * @notice The uniswap pool corresponding to the project token-terminal token market - * (this should be carefully chosen liquidity wise) - */ - mapping(uint256 _projectId => mapping(address _terminalToken => IUniswapV3Pool _pool)) public poolOf; - - /** - * @notice The project token - */ - mapping(uint256 _projectId => address projectTokenOf) public projectTokenOf; - - /** - * @notice Any ETH left-over in this contract (from swap in the end of liquidity range) - */ - mapping(address _beneficiary => mapping(address _token => uint256 _balance)) public sweepBalanceOf; - - /** - * @notice Running cumulative sum of token left-over - */ - mapping(address _token => uint256 _contractBalance) public totalSweepBalance; - - ///////////////////////////////////////////////////////////////////// - // Internal global variables // - ///////////////////////////////////////////////////////////////////// - - /** - * @notice The twap max deviation acepted (in 10_000th) and timeframe to use for the pool twap (from secondAgo to now) - * - * @dev Params are uint128 and uint32 packed in a uint256, with the max deviation in the 128 most significant bits - */ - mapping(uint256 _projectId => uint256 _params) internal twapParamsOf; - - //*********************************************************************// - // ---------------------------- Constructor -------------------------- // - //*********************************************************************// - - /** - * @dev No other logic besides initializing the immutables - */ - constructor( - IWETH9 _weth, - address _factory, - IJBDirectory _directory, - IJBController3_1 _controller, - bytes4 _delegateId - ) JBOperatable(IJBOperatable(address(_controller)).operatorStore()) { - WETH = _weth; - DIRECTORY = _directory; - CONTROLLER = _controller; - UNISWAP_V3_FACTORY = _factory; - delegateId = _delegateId; - - PROJECTS = _controller.projects(); - } - - ///////////////////////////////////////////////////////////////////// - // View functions // - ///////////////////////////////////////////////////////////////////// - - /** - * @notice The datasource implementation - * - * @param _data the data passed to the data source in terminal.pay(..). _data.metadata need to have the Uniswap quote - * this quote should be set as 0 if the user wants to use the vanilla minting path - * @return weight the weight to use (the one passed if not max reserved rate, 0 if swapping or the one corresponding - * to the reserved token to mint if minting) - * @return memo the original memo passed - * @return delegateAllocations The amount to send to delegates instead of adding to the local balance. - */ - function payParams(JBPayParamsData calldata _data) - external - view - override - returns (uint256 weight, string memory memo, JBPayDelegateAllocation3_1_1[] memory delegateAllocations) - { - address _projectToken = projectTokenOf[_data.projectId]; - - // Find the total number of tokens to mint, as a fixed point number with 18 decimals - uint256 _tokenCount = mulDiv18(_data.amount.value, _data.weight); - - // Unpack the quote from the pool, given by the frontend - this one takes precedence on the twap - // as it should be closer to the current pool state, if not, use the twap - (bool _validQuote, bytes memory _metadata) = JBDelegateMetadataLib.getMetadata(delegateId, _data.metadata); - - // Get a quote based on either the frontend quote or a twap from the pool - uint256 _quote; - uint256 _slippage; - if (_validQuote) (_quote, _slippage) = abi.decode(_metadata, (uint256, uint256)); - - uint256 _swapAmountOut = _quote == 0 - ? _getQuote(_data.projectId, _data.terminal, _projectToken, _data.amount.value) - : _quote - ((_quote * _slippage) / SLIPPAGE_DENOMINATOR); - - // If the minimum amount received from swapping is greather than received when minting, use the swap pathway - if (_tokenCount < _swapAmountOut) { - // Return this delegate as the one to use, along the quote and reserved rate, and do not mint from the terminal - delegateAllocations = new JBPayDelegateAllocation3_1_1[](1); - delegateAllocations[0] = JBPayDelegateAllocation3_1_1({ - delegate: IJBPayDelegate3_1_1(this), - amount: _data.amount.value, - metadata: abi.encode(_tokenCount, _swapAmountOut, _projectToken) - }); - - return (0, _data.memo, delegateAllocations); - } - - // If minting, do not use this as delegate (delegateAllocations is left uninitialised) - return (_data.weight, _data.memo, delegateAllocations); - } - - /** - * @notice The timeframe to use for the pool twap (from secondAgo to now) - * - * @param _projectId the project id - * - * @return _secondsAgo the period over which the twap is computed - */ - function secondsAgoOf(uint256 _projectId) external view returns(uint32) { - return uint32(twapParamsOf[_projectId]); - } - - /** - * @notice The twap max deviation acepted (in 10_000th) - * - * @param _projectId the project id - * - * @return _delta the maximum deviation allowed between amount received and twap - */ - function twapDeltaOf(uint256 _projectId) external view returns(uint256) { - return twapParamsOf[_projectId] >> 128; - } - - ///////////////////////////////////////////////////////////////////// - // External functions // - ///////////////////////////////////////////////////////////////////// - - /** - * @notice Delegate to either swap to the beneficiary or mint to the beneficiary - * - * @dev This delegate is called only if the quote for the swap is bigger than the lowest received when minting. - * If the swap reverts (slippage, liquidity, etc), the delegate will then mint the same amount of token as - * if the delegate was not used. - * If the beneficiary requests non claimed token, the swap is not used (as it is, per definition, claimed token) - * - * @param _data the delegate data passed by the terminal - */ - function didPay(JBDidPayData3_1_1 calldata _data) external payable override { - // Access control as minting is authorized to this delegate - if (!DIRECTORY.isTerminalOf(_data.projectId, IJBPaymentTerminal(msg.sender))) { - revert JuiceBuyback_Unauthorized(); - } - - (uint256 _tokenCount, uint256 _swapMinAmountOut, IERC20 _projectToken) = - abi.decode(_data.dataSourceMetadata, (uint256, uint256, IERC20)); - - // Try swapping - uint256 _amountReceived = _swap(_data, _swapMinAmountOut, _projectToken); - - // If swap failed, mint instead, with the original weight + add to balance the token in - if (_amountReceived == 0) _mint(_data, _tokenCount); - - // Any leftover in this contract? - uint256 _terminalTokenInThisContract = _data.forwardedAmount.token == JBTokens.ETH - ? address(this).balance - : IERC20(_data.forwardedAmount.token).balanceOf(address(this)); - - // Any previous leftover? - uint256 _terminalTokenPreviouslyInThisContract = totalSweepBalance[_data.forwardedAmount.token]; - - // From these previous leftover, some belonging to the beneficiary? - uint256 _beneficiarySweepBalance = sweepBalanceOf[_data.beneficiary][_data.forwardedAmount.token]; - - // Add any new leftover to the beneficiary and contract balance - if (_terminalTokenInThisContract > 0 && _terminalTokenInThisContract != _beneficiarySweepBalance) { - sweepBalanceOf[_data.beneficiary][_data.forwardedAmount.token] += - _terminalTokenInThisContract - _terminalTokenPreviouslyInThisContract; - - emit BuybackDelegate_PendingSweep( - _data.beneficiary, - _data.forwardedAmount.token, - _terminalTokenInThisContract - _terminalTokenPreviouslyInThisContract - ); - - totalSweepBalance[_data.forwardedAmount.token] = _terminalTokenInThisContract; - } - } - - /** - * @notice The Uniswap V3 pool callback (where token transfer should happens) - * - * @dev Slippage controle is achieved here - */ - function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external override { - // Unpack the data - (uint256 _projectId, uint256 _minimumAmountReceived, address _terminalToken, address _projectToken) = - abi.decode(data, (uint256, uint256, address, address)); - - // Get the terminal token, weth if it's an ETH terminal - address _terminalTokenWithWETH = _terminalToken == JBTokens.ETH ? address(WETH) : _terminalToken; - - // Check if this is really a callback - only create2 pools are added to insure safety of this check (balance pending sweep at risk) - if (msg.sender != address(poolOf[_projectId][_terminalTokenWithWETH])) revert JuiceBuyback_Unauthorized(); - - // Sort the pool tokens - bool _tokenProjectIs0 = _projectToken < _terminalTokenWithWETH; - - // delta is in regard of the pool balance (positive = pool need to receive) - uint256 _amountToSendToPool = _tokenProjectIs0 ? uint256(amount1Delta) : uint256(amount0Delta); - uint256 _amountReceivedForBeneficiary = _tokenProjectIs0 ? uint256(-amount0Delta) : uint256(-amount1Delta); - - // Revert if slippage is too high - if (_amountReceivedForBeneficiary < _minimumAmountReceived) { - revert JuiceBuyback_MaximumSlippage(); - } - - // Wrap ETH if needed - if (_terminalToken == JBTokens.ETH) WETH.deposit{value: _amountToSendToPool}(); - - // Transfer the token to the pool - IERC20(_terminalTokenWithWETH).transfer(msg.sender, _amountToSendToPool); - } - - /** - * @notice Generic redeem params, for interface completion - * - * @dev This is a passthrough of the redemption parameters - * - * @param _data the redeem data passed by the terminal - */ - function redeemParams(JBRedeemParamsData calldata _data) - external - pure - override - returns ( - uint256 reclaimAmount, - string memory memo, - JBRedemptionDelegateAllocation3_1_1[] memory delegateAllocations - ) - { - return (_data.reclaimAmount.value, _data.memo, delegateAllocations); - } - - /** - * @notice Add a pool for a given project. This pools the become the default one for a given token project-terminal token - * - * @dev Uses create2 for callback auth and allows adding a pool not deployed yet. - * This can be called by the project owner or an address having the SET_POOL permission in JBOperatorStore - * - * @param _projectId the project id - * @param _fee the fee of the pool - * @param _secondsAgo the period over which the twap is computed - * @param _twapDelta the maximum deviation allowed between amount received and twap - * @param _terminalToken the terminal token - */ - function setPoolFor(uint256 _projectId, uint24 _fee, uint32 _secondsAgo, uint256 _twapDelta, address _terminalToken) - external - requirePermission(PROJECTS.ownerOf(_projectId), _projectId, JBBuybackDelegateOperations.CHANGE_POOL) - returns (IUniswapV3Pool _newPool) - { - if (_twapDelta < MIN_TWAP_DELTA || _twapDelta > MAX_TWAP_DELTA) revert JuiceBuyback_InvalidTwapDelta(); - - if ( _secondsAgo < MIN_SECONDS_AGO || _secondsAgo > MAX_SECONDS_AGO) revert JuiceBuyback_InvalidTwapPeriod(); - - // Get the project token - address _projectToken = address(CONTROLLER.tokenStore().tokenOf(_projectId)); - - if (_projectToken == address(0)) revert JuiceBuyback_NoProjectToken(); - - if (_terminalToken == JBTokens.ETH) _terminalToken = address(WETH); - - bool _projectTokenIs0 = address(_projectToken) < _terminalToken; - - // Compute the corresponding pool - _newPool = IUniswapV3Pool( - address( - uint160( - uint256( - keccak256( - abi.encodePacked( - hex"ff", - UNISWAP_V3_FACTORY, - keccak256( - abi.encode( - _projectTokenIs0 ? _projectToken : _terminalToken, - _projectTokenIs0 ? _terminalToken : _projectToken, - _fee - ) - ), - bytes32(0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54) - ) - ) - ) - ) - ) - ); - - // If this pool is already used, rather use the secondsAgo and twapDelta setters - if (poolOf[_projectId][_terminalToken] == _newPool) revert JuiceBuyback_PoolAlreadySet(); - - // Store the pool - poolOf[_projectId][_terminalToken] = _newPool; - - // Store the twap period and max slippage - twapParamsOf[_projectId] = _twapDelta << 128 | _secondsAgo; - projectTokenOf[_projectId] = address(_projectToken); - - emit BuybackDelegate_SecondsAgoChanged(_projectId, 0, _secondsAgo); - emit BuybackDelegate_TwapDeltaChanged(_projectId, 0, _twapDelta); - emit BuybackDelegate_PoolAdded(_projectId, _terminalToken, address(_newPool)); - } - - /** - * @notice Increase the period over which the twap is computed - * - * @dev This can be called by the project owner or an address having the SET_TWAP_PERIOD permission in JBOperatorStore - * - * @param _newSecondsAgo the new period - */ - function changeSecondsAgo(uint256 _projectId, uint32 _newSecondsAgo) - external - requirePermission(PROJECTS.ownerOf(_projectId), _projectId, JBBuybackDelegateOperations.SET_POOL_PARAMS) - { - if (_newSecondsAgo < MIN_SECONDS_AGO || _newSecondsAgo > MAX_SECONDS_AGO) revert JuiceBuyback_InvalidTwapPeriod(); - - uint256 _twapParams = twapParamsOf[_projectId]; - uint256 _oldValue = uint128(_twapParams); - - twapParamsOf[_projectId] = uint256(_newSecondsAgo) | ((_twapParams >> 128) << 128); - - emit BuybackDelegate_SecondsAgoChanged(_projectId, _oldValue, _newSecondsAgo); - } - - /** - * @notice Set the maximum deviation allowed between amount received and twap - * - * @dev This can be called by the project owner or an address having the SET_POOL permission in JBOperatorStore - * - * @param _newDelta the new delta, in 10_000th - */ - function setTwapDelta(uint256 _projectId, uint256 _newDelta) - external - requirePermission(PROJECTS.ownerOf(_projectId), _projectId, JBBuybackDelegateOperations.SET_POOL_PARAMS) - { - if (_newDelta < MIN_TWAP_DELTA || _newDelta > MAX_TWAP_DELTA) revert JuiceBuyback_InvalidTwapDelta(); - - uint256 _twapParams = twapParamsOf[_projectId]; - uint256 _oldDelta = _twapParams >> 128; - - twapParamsOf[_projectId] = _newDelta << 128 | ((_twapParams << 128) >> 128); - - emit BuybackDelegate_TwapDeltaChanged(_projectId, _oldDelta, _newDelta); - } - - /** - * @notice Sweep the token left-over in this contract - */ - function sweep(address _beneficiary, address _token) external { - // The beneficiary ETH balance in this contract leftover - uint256 _balance = sweepBalanceOf[_beneficiary][_token]; - - // If no balance, don't do anything - if (_balance == 0) return; - - // Reset beneficiary balance - sweepBalanceOf[_beneficiary][_token] = 0; - totalSweepBalance[_token] -= _balance; - - if (_token == JBTokens.ETH) { - // Send the eth to the beneficiary - (bool _success,) = payable(_beneficiary).call{value: _balance}(""); - if (!_success) revert JuiceBuyback_TransferFailed(); - } else { - IERC20(_token).transfer(_beneficiary, _balance); - } - - emit BuybackDelegate_PendingSweep(_beneficiary, address(_token), 0); - } - - //*********************************************************************// - // ---------------------- internal functions ------------------------- // - //*********************************************************************// - - /** - * @notice Get a quote based on twap over a secondsAgo period, taking into account a twapDelta max deviation - * - * @param _amountIn the amount to swap - * - * @return _amountOut the minimum amount received according to the twap - */ - function _getQuote(uint256 _projectId, IJBPaymentTerminal _terminal, address _projectToken, uint256 _amountIn) - internal - view - returns (uint256 _amountOut) - { - address _terminalToken = IJBSingleTokenPaymentTerminal(address(_terminal)).token(); - - // Get the pool - IUniswapV3Pool _pool = poolOf[_projectId][address(_terminalToken)]; - - // If non-existing or non-initialized pool, quote 0 - try _pool.slot0() returns (uint160, int24, uint16, uint16, uint16, uint8, bool unlocked) { - // non initialized? - if (!unlocked) return 0; - } catch { - // invalid address or not deployed yet? - return 0; - } - - // Get and unpack the twap params - uint256 _twapParams = twapParamsOf[_projectId]; - uint32 _quotePeriod = uint32(_twapParams); - uint256 _maxDelta = _twapParams >> 128; - - // Get the twap tick - (int24 arithmeticMeanTick,) = OracleLibrary.consult(address(_pool), _quotePeriod); - - // Get a quote based on this twap tick - _amountOut = OracleLibrary.getQuoteAtTick({ - tick: arithmeticMeanTick, - baseAmount: uint128(_amountIn), - baseToken: _terminalToken == JBTokens.ETH ? address(WETH) : _terminalToken, - quoteToken: address(_projectToken) - }); - - // Return the lowest twap accepted - _amountOut -= (_amountOut * _maxDelta) / SLIPPAGE_DENOMINATOR; - } - - /** - * @notice Swap the terminal token to receive the project toke_beforeTransferTon - * - * @dev This delegate first receive the whole amount of project token, - * then send the non-reserved token to the beneficiary, - * then burn the rest of this delegate balance (ie the amount of reserved token), - * then mint the same amount as received (this will add the reserved token, following the fc rate) - * then burn the difference (ie this delegate balance) - * -> End result is having the correct balances (beneficiary and reserve), according to the reserve rate - * - * @param _data the didPayData passed by the terminal - * @param _minimumReceivedFromSwap the minimum amount received, to prevent slippage - */ - function _swap(JBDidPayData3_1_1 calldata _data, uint256 _minimumReceivedFromSwap, IERC20 _projectToken) - internal - returns (uint256 _amountReceived) - { - address _terminalToken = - _data.forwardedAmount.token == JBTokens.ETH ? address(WETH) : _data.forwardedAmount.token; - - bool _projectTokenIs0 = address(_projectToken) < _terminalToken; - - IUniswapV3Pool _pool = poolOf[_data.projectId][_terminalToken]; - - // Pass the token and min amount to receive as extra data - try _pool.swap({ - recipient: address(this), - zeroForOne: !_projectTokenIs0, - amountSpecified: int256(_data.forwardedAmount.value), - sqrtPriceLimitX96: _projectTokenIs0 ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1, - data: abi.encode(_data.projectId, _minimumReceivedFromSwap, _terminalToken, _projectToken) - }) returns (int256 amount0, int256 amount1) { - // Swap succeded, take note of the amount of PROJECT_TOKEN received (negative as it is an exact input) - _amountReceived = uint256(-(_projectTokenIs0 ? amount0 : amount1)); - } catch { - // implies _amountReceived = 0 -> will later mint when back in didPay - return _amountReceived; - } - - // Burn the whole amount received - CONTROLLER.burnTokensOf({ - holder: address(this), - projectId: _data.projectId, - tokenCount: _amountReceived, - memo: "", - preferClaimedTokens: true - }); - - // Mint it again, to add the correct portion to the reserved token and take the claimed preference into account - CONTROLLER.mintTokensOf({ - projectId: _data.projectId, - tokenCount: _amountReceived, - beneficiary: address(_data.beneficiary), - memo: _data.memo, - preferClaimedTokens: _data.preferClaimedTokens, - useReservedRate: true - }); - - emit BuybackDelegate_Swap(_data.projectId, _data.forwardedAmount.value, _amountReceived); - } - - /** - * @notice Mint the token out, sending back the token in the terminal - * - * @param _data the didPayData passed by the terminal - * @param _amount the amount of token out to mint - */ - function _mint(JBDidPayData3_1_1 calldata _data, uint256 _amount) internal { - // Mint to the beneficiary with the fc reserve rate - CONTROLLER.mintTokensOf({ - projectId: _data.projectId, - tokenCount: _amount, - beneficiary: _data.beneficiary, - memo: _data.memo, - preferClaimedTokens: _data.preferClaimedTokens, - useReservedRate: true - }); - - // Add the token or eth back to the terminal balance - if (_data.forwardedAmount.token != JBTokens.ETH) { - IERC20(_data.forwardedAmount.token).approve(msg.sender, _data.forwardedAmount.value); - } - - IJBPayoutRedemptionPaymentTerminal3_1_1(msg.sender).addToBalanceOf{ - value: _data.forwardedAmount.token == JBTokens.ETH ? _data.forwardedAmount.value : 0 - }(_data.projectId, _data.forwardedAmount.value, _data.forwardedAmount.token, "", ""); - - emit BuybackDelegate_Mint(_data.projectId); - } - - //*********************************************************************// - // ---------------------- peripheral functions ----------------------- // - //*********************************************************************// - - function supportsInterface(bytes4 _interfaceId) public view override(ERC165, IERC165) returns (bool) { - return _interfaceId == type(IJBFundingCycleDataSource3_1_1).interfaceId - || _interfaceId == type(IJBPayDelegate3_1_1).interfaceId || super.supportsInterface(_interfaceId); - } -} diff --git a/contracts/interfaces/IJBGenericBuybackDelegate.sol b/contracts/interfaces/IJBBuybackDelegate.sol similarity index 56% rename from contracts/interfaces/IJBGenericBuybackDelegate.sol rename to contracts/interfaces/IJBBuybackDelegate.sol index 30eb71c..f4a95b5 100644 --- a/contracts/interfaces/IJBGenericBuybackDelegate.sol +++ b/contracts/interfaces/IJBBuybackDelegate.sol @@ -12,63 +12,61 @@ import {IUniswapV3SwapCallback} from "@uniswap/v3-core/contracts/interfaces/call import {IWETH9} from "./external/IWETH9.sol"; -interface IJBGenericBuybackDelegate is IJBPayDelegate3_1_1, IJBFundingCycleDataSource3_1_1, IUniswapV3SwapCallback { +interface IJBBuybackDelegate is IJBPayDelegate3_1_1, IJBFundingCycleDataSource3_1_1, IUniswapV3SwapCallback { ///////////////////////////////////////////////////////////////////// // Errors // ///////////////////////////////////////////////////////////////////// error JuiceBuyback_MaximumSlippage(); + error JuiceBuyback_InsufficientPayAmount(); + error JuiceBuyback_NotEnoughTokensReceived(); error JuiceBuyback_NewSecondsAgoTooLow(); error JuiceBuyback_NoProjectToken(); error JuiceBuyback_PoolAlreadySet(); error JuiceBuyback_TransferFailed(); - error JuiceBuyback_InvalidTwapDelta(); - error JuiceBuyback_InvalidTwapPeriod(); + error JuiceBuyback_InvalidTwapSlippageTolerance(); + error JuiceBuyback_InvalidTwapWindow(); error JuiceBuyback_Unauthorized(); ///////////////////////////////////////////////////////////////////// // Events // ///////////////////////////////////////////////////////////////////// - event BuybackDelegate_Swap(uint256 indexed projectId, uint256 amountEth, uint256 amountOut); - event BuybackDelegate_Mint(uint256 indexed projectId); - event BuybackDelegate_SecondsAgoChanged(uint256 indexed projectId, uint256 oldSecondsAgo, uint256 newSecondsAgo); - event BuybackDelegate_TwapDeltaChanged(uint256 indexed projectId, uint256 oldTwapDelta, uint256 newTwapDelta); - event BuybackDelegate_PendingSweep(address indexed beneficiary, address indexed token, uint256 amount); - event BuybackDelegate_PoolAdded(uint256 indexed projectId, address indexed terminalToken, address newPool); + event BuybackDelegate_Swap(uint256 indexed projectId, uint256 amountIn, IUniswapV3Pool pool, uint256 amountOut, address caller); + 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_PoolAdded(uint256 indexed projectId, address indexed terminalToken, address newPool, address caller); ///////////////////////////////////////////////////////////////////// // Getters // ///////////////////////////////////////////////////////////////////// function SLIPPAGE_DENOMINATOR() external view returns (uint256); - function MIN_TWAP_DELTA() external view returns (uint256); - function MAX_TWAP_DELTA() external view returns (uint256); - function MIN_SECONDS_AGO() external view returns (uint256); - function MAX_SECONDS_AGO() external view returns (uint256); + function MIN_TWAP_SLIPPAGE_TOLERANCE() external view returns (uint256); + function MAX_TWAP_SLIPPAGE_TOLERANCE() external view returns (uint256); + function MIN_TWAP_WINDOW() external view returns (uint256); + function MAX_TWAP_WINDOW() external view returns (uint256); function UNISWAP_V3_FACTORY() external view returns (address); function DIRECTORY() external view returns (IJBDirectory); function CONTROLLER() external view returns (IJBController3_1); function PROJECTS() external view returns (IJBProjects); function WETH() external view returns (IWETH9); - function delegateId() external view returns (bytes4); - function poolOf(uint256 _projectId, address _terminalToken) external view returns (IUniswapV3Pool _pool); - function secondsAgoOf(uint256 _projectId) external view returns (uint32 _seconds); - function twapDeltaOf(uint256 _projectId) external view returns (uint256 _delta); - function projectTokenOf(uint256 _projectId) external view returns (address projectTokenOf); - function sweepBalanceOf(address _beneficiary, address _token) external view returns (uint256 _balance); - function totalSweepBalance(address _token) external view returns (uint256 _contractBalance); + function DELEGATE_ID() external view returns (bytes4); + function poolOf(uint256 projectId, address terminalToken) external view returns (IUniswapV3Pool pool); + function twapWindowOf(uint256 projectId) external view returns (uint32 window); + function twapSlippageToleranceOf(uint256 projectId) external view returns (uint256 slippageTolerance); + function projectTokenOf(uint256 projectId) external view returns (address projectTokenOf); ///////////////////////////////////////////////////////////////////// // State-changing functions // ///////////////////////////////////////////////////////////////////// - function setPoolFor(uint256 _projectId, uint24 _fee, uint32 _secondsAgo, uint256 _twapDelta, address _terminalToken) + function setPoolFor(uint256 projectId, uint24 fee, uint32 twapWindow, uint256 twapSlippageTolerance, address terminalToken) external - returns (IUniswapV3Pool _newPool); + returns (IUniswapV3Pool newPool); - function changeSecondsAgo(uint256 _projectId, uint32 _newSecondsAgo) external; + function setTwapWindowOf(uint256 projectId, uint32 newWindow) external; - function setTwapDelta(uint256 _projectId, uint256 _newDelta) external; - function sweep(address _beneficiary, address _token) external; + function setTwapSlippageToleranceOf(uint256 projectId, uint256 newSlippageTolerance) external; } diff --git a/contracts/scripts/Deploy.s.sol b/contracts/scripts/Deploy.s.sol index dfa2afe..7783dbc 100644 --- a/contracts/scripts/Deploy.s.sol +++ b/contracts/scripts/Deploy.s.sol @@ -3,10 +3,9 @@ pragma solidity ^0.8.20; import "forge-std/Script.sol"; -import "../JBGenericBuybackDelegate.sol"; +import "../JBBuybackDelegate.sol"; contract DeployGeneric is Script { - uint256 _chainId = block.chainid; string _network; @@ -26,33 +25,40 @@ contract DeployGeneric is Script { bytes4 constant _delegateId = bytes4("BUYB"); function setUp() public { - if(_chainId == 1) { + if (_chainId == 1) { _network = "mainnet"; _weth = _wethMainnet; _factory = _factoryMainnet; - } - else if(_chainId == 5) { + } else if (_chainId == 5) { _network = "goerli"; _weth = _wethGoerli; _factory = _factoryGoerli; - } - else if(_chainId == 1337) { + } else if (_chainId == 1337) { _network = "sepolia"; _weth = _wethSepolia; _factory = _factorySepolia; + } else { + revert("Invalid RPC / no juice contracts deployed on this network"); } - else revert("Invalid RPC / no juice contracts deployed on this network"); _directory = IJBDirectory( stdJson.readAddress( - vm.readFile(string.concat("node_modules/@jbx-protocol/juice-contracts-v3/deployments/", _network, "/JBDirectory.json")), + vm.readFile( + string.concat( + "node_modules/@jbx-protocol/juice-contracts-v3/deployments/", _network, "/JBDirectory.json" + ) + ), ".address" ) ); _controller = IJBController3_1( stdJson.readAddress( - vm.readFile(string.concat("node_modules/@jbx-protocol/juice-contracts-v3/deployments/", _network, "/JBController3_1.json")), + vm.readFile( + string.concat( + "node_modules/@jbx-protocol/juice-contracts-v3/deployments/", _network, "/JBController3_1.json" + ) + ), ".address" ) ); @@ -70,7 +76,7 @@ contract DeployGeneric is Script { console.log(address(_controller)); vm.startBroadcast(); - JBGenericBuybackDelegate _delegate = new JBGenericBuybackDelegate( + JBBuybackDelegate _delegate = new JBBuybackDelegate( _weth, _factory, _directory, @@ -81,4 +87,4 @@ contract DeployGeneric is Script { console.log("Delegate deployed at:"); console.log(address(_delegate)); } -} \ No newline at end of file +} diff --git a/contracts/test/JBBuybackDelegate_Fork.t.sol b/contracts/test/JBBuybackDelegate_Fork.t.sol index b710727..97b39cd 100644 --- a/contracts/test/JBBuybackDelegate_Fork.t.sol +++ b/contracts/test/JBBuybackDelegate_Fork.t.sol @@ -4,39 +4,19 @@ pragma solidity ^0.8.16; import "../interfaces/external/IWETH9.sol"; import "./helpers/TestBaseWorkflowV3.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBController3_1.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleStore.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleBallot.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleDataSource.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBOperatable.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPayDelegate.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBRedemptionDelegate.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPayoutRedemptionPaymentTerminal.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBSingleTokenPaymentTerminal.sol"; - -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBSingleTokenPaymentTerminalStore.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBToken.sol"; - -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBConstants.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBCurrencies.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBFundingCycleMetadataResolver.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBOperations.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBTokens.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/structs/JBFundingCycle.sol"; +import {JBDelegateMetadataHelper} from "@jbx-protocol/juice-delegate-metadata-lib/src/JBDelegateMetadataHelper.sol"; import "@paulrberg/contracts/math/PRBMath.sol"; import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; -import "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol"; import "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol"; import "@uniswap/v3-core/contracts/libraries/TickMath.sol"; import "@exhausted-pigeon/uniswap-v3-forge-quoter/src/UniswapV3ForgeQuoter.sol"; import "../JBBuybackDelegate.sol"; -import "../mock/MockAllocator.sol"; -import "forge-std/Test.sol"; +import {mulDiv18} from "@prb/math/src/Common.sol"; /** * @notice Buyback fork integration tests, using $jbx v3 @@ -44,8 +24,7 @@ import "forge-std/Test.sol"; contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { using JBFundingCycleMetadataResolver for JBFundingCycle; - event BuybackDelegate_Swap(uint256 projectId, uint256 amountEth, uint256 amountOut); - event BuybackDelegate_Mint(uint256 projectId); + event BuybackDelegate_Swap(uint256 indexed projectId, uint256 amountIn, IUniswapV3Pool pool, uint256 amountOut, address caller); event Mint( address indexed holder, uint256 indexed projectId, @@ -55,19 +34,34 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { address caller ); + // Constants + uint256 constant SLIPPAGE_DENOMINATOR = 10000; + + IUniswapV3Factory constant factory = IUniswapV3Factory(0x1F98431c8aD98523631AE4a59f267346ea31F984); + IERC20 constant jbx = IERC20(0x4554CC10898f92D45378b98D6D6c2dD54c687Fb2); // 0 - 69420*10**18 + IWETH9 constant weth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // 1 - 1*10**18 + + uint256 constant price = 69420 ether; + uint32 constant cardinality = 100000; + uint256 constant twapDelta = 500; + uint24 constant fee = 10000; + + uint256 constant amountPaid = 1 ether; + + // Contracts needed IJBFundingCycleStore jbFundingCycleStore; IJBDirectory jbDirectory; IJBProjects jbProjects; IJBSplitsStore jbSplitsStore; IJBPayoutRedemptionPaymentTerminal3_1_1 jbEthPaymentTerminal; - IJBSingleTokenPaymentTerminal terminal; IJBSingleTokenPaymentTerminalStore jbTerminalStore; IJBController3_1 jbController; IJBTokenStore jbTokenStore; - IJBOperatorStore jbOperatorStore; + IUniswapV3Pool pool; + JBDelegateMetadataHelper metadataHelper; // Structure needed JBProjectMetadata projectMetadata; @@ -77,31 +71,18 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { IJBPaymentTerminal[] terminals; JBGroupedSplits[] groupedSplits; + // Target contract JBBuybackDelegate delegate; - - IUniswapV3Pool pool; - - uint256 constant SLIPPAGE_DENOMINATOR = 10000; - - IUniswapV3Factory factory = IUniswapV3Factory(0x1F98431c8aD98523631AE4a59f267346ea31F984); - IERC20 jbx = IERC20(0x4554CC10898f92D45378b98D6D6c2dD54c687Fb2); // 0 - 69420*10**18 - IWETH9 weth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // 1 - 1*10**18 - - uint256 price = 69420 ether; - - uint32 cardinality = 100000; - - uint256 twapDelta = 500; - - uint24 fee = 10000; + + address beneficiary = makeAddr('benefichiary'); // sqrtPriceX96 = sqrt(1*10**18 << 192 / 69420*10**18) = 300702666377442711115399168 (?) uint160 sqrtPriceX96 = 300702666377442711115399168; - uint256 amountOutForOneEth; + uint256 amountOutQuoted; function setUp() public { - vm.createSelectFork("https://rpc.ankr.com/eth"); + vm.createSelectFork("https://rpc.ankr.com/eth",17962427); // Collect the mainnet deployment addresses jbDirectory = IJBDirectory( @@ -147,14 +128,24 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { jbOperatorStore = IJBOperatable(address(jbTokenStore)).operatorStore(); jbSplitsStore = jbController.splitsStore(); + delegate = new JBBuybackDelegate({ + _weth: weth, + _factory: address(factory), + _directory: IJBDirectory(address(jbDirectory)), + _controller: jbController, + _delegateId: bytes4(hex'69') + }); + + // JBX V3 pool wasn't deployed at that block pool = IUniswapV3Pool(factory.createPool(address(weth), address(jbx), fee)); pool.initialize(sqrtPriceX96); // 1 eth <=> 69420 jbx - vm.startPrank(address(123), address(123)); - deal(address(weth), address(123), 10000000 ether); - deal(address(jbx), address(123), 10000000 ether); + address LP = makeAddr('LP'); + vm.startPrank(LP, LP); + deal(address(weth), LP, 10000000 ether); + deal(address(jbx), LP, 10000000 ether); - // approve: + // create a full range position address POSITION_MANAGER = 0xC36442b4a4522E871399CD717aBDD847Ab11FE88; jbx.approve(POSITION_MANAGER, 10000000 ether); weth.approve(POSITION_MANAGER, 10000000 ether); @@ -171,7 +162,7 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { amount1Desired: 10000000 ether, amount0Min: 0, amount1Min: 0, - recipient: address(123), + recipient: LP, deadline: block.timestamp }); @@ -179,19 +170,12 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { vm.stopPrank(); - amountOutForOneEth = getAmountOut(pool, 1 ether, address(weth)); - - delegate = new JBBuybackDelegate( - IERC20(address(jbx)), - weth, - address(factory), - fee, - cardinality, - twapDelta, - jbDirectory, - jbController, - bytes4(hex'69') - ); + vm.prank(jbProjects.ownerOf(1)); + delegate.setPoolFor(1, fee, cardinality, twapDelta, address(weth)); + + amountOutQuoted = getAmountOut(pool, 1 ether, address(weth)); + + metadataHelper = new JBDelegateMetadataHelper(); vm.label(address(pool), "uniswapPool"); vm.label(address(factory), "uniswapFactory"); @@ -219,43 +203,48 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { * * @dev Should mint for both beneficiary and reserve */ - function test_mintIfWeightGreatherThanPrice(uint256 _weight) public { - // Reconfigure with a weight bigger than the quote - _weight = bound(_weight, amountOutForOneEth + 1, type(uint88).max); + function test_mintIfWeightGreatherThanPrice(uint256 _weight, uint256 _amountIn) public { + _amountIn = bound(_amountIn, 100, 100 ether); + + uint256 _amountOutQuoted = getAmountOut(pool, _amountIn, address(weth)); + + // Reconfigure with a weight bigger than the price implied by the quote + _weight = bound(_weight, (_amountOutQuoted * 10**18 / _amountIn) + 1, type(uint88).max); + _reconfigure(1, address(delegate), _weight, 5000); uint256 _reservedBalanceBefore = jbController.reservedTokenBalanceOf(1); // Build the metadata using the quote at that block bytes[] memory _data = new bytes[](1); - _data[0] = abi.encode(amountOutForOneEth, 500); + _data[0] = abi.encode(_amountOutQuoted, _amountIn); // Pass the delegate id bytes4[] memory _ids = new bytes4[](1); _ids[0] = bytes4(hex"69"); // Generate the metadata - bytes memory _delegateMetadata = delegate.createMetadata(_ids, _data); + bytes memory _delegateMetadata = metadataHelper.createMetadata(_ids, _data); // This shouldn't mint via the delegate vm.expectEmit(true, true, true, true); emit Mint({ - holder: address(123), + holder: beneficiary, projectId: 1, - amount: _weight / 2, // Half is reserved + amount: mulDiv18(_weight, _amountIn) / 2, // Half is reserved tokensWereClaimed: true, preferClaimedTokens: true, caller: address(jbController) }); - uint256 _balBeforePayment = jbx.balanceOf(address(123)); + uint256 _balBeforePayment = jbx.balanceOf(beneficiary); // Pay the project - jbEthPaymentTerminal.pay{value: 1 ether}( + jbEthPaymentTerminal.pay{value: _amountIn}( 1, - 1 ether, + _amountIn, address(0), - address(123), + beneficiary, /* _minReturnedTokens */ 0, /* _preferClaimedTokens */ @@ -266,14 +255,14 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { _delegateMetadata ); - uint256 _balAfterPayment = jbx.balanceOf(address(123)); + uint256 _balAfterPayment = jbx.balanceOf(beneficiary); uint256 _diff = _balAfterPayment - _balBeforePayment; // Check: token received by the beneficiary - assertEq(_diff, _weight / 2); + assertEq(_diff, mulDiv18(_weight, _amountIn) / 2); // Check: token added to the reserve - 1 wei sensitivity for rounding errors - assertApproxEqAbs(jbController.reservedTokenBalanceOf(1), _reservedBalanceBefore + _weight / 2, 1); + assertApproxEqAbs(jbController.reservedTokenBalanceOf(1), _reservedBalanceBefore + mulDiv18(_weight, _amountIn) / 2, 1); } /** @@ -281,35 +270,42 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { * * @dev Should swap for both beneficiary and reserve (by burning/minting) */ - function test_swapIfQuoteBetter(uint256 _weight) public { - // Reconfigure with a weight smaller than the quote, slippage included - _weight = bound(_weight, 0, amountOutForOneEth - ((amountOutForOneEth * 500) / 10000) - 1); - _reconfigure(1, address(delegate), _weight, 5000); + function test_swapIfQuoteBetter(uint256 _weight, uint256 _amountIn, uint256 _reservedRate) public { + _amountIn = bound(_amountIn, 100, 100 ether); + + uint256 _amountOutQuoted = getAmountOut(pool, _amountIn, address(weth)); + + // Reconfigure with a weight smaller than the price implied by the quote + _weight = bound(_weight, 1, (_amountOutQuoted * 10**18 / _amountIn) - 1); + + _reservedRate = bound(_reservedRate, 0, 10000); + + _reconfigure(1, address(delegate), _weight, _reservedRate); uint256 _reservedBalanceBefore = jbController.reservedTokenBalanceOf(1); // Build the metadata using the quote at that block bytes[] memory _data = new bytes[](1); - _data[0] = abi.encode(amountOutForOneEth, 500); + _data[0] = abi.encode(_amountOutQuoted, _amountIn); // Pass the delegate id bytes4[] memory _ids = new bytes4[](1); _ids[0] = bytes4(hex"69"); // Generate the metadata - bytes memory _delegateMetadata = delegate.createMetadata(_ids, _data); + bytes memory _delegateMetadata = metadataHelper.createMetadata(_ids, _data); - vm.expectEmit(true, true, true, true); - emit BuybackDelegate_Swap(1, 1 ether, amountOutForOneEth); + uint256 _balBeforePayment = jbx.balanceOf(beneficiary); - uint256 _balBeforePayment = jbx.balanceOf(address(123)); + vm.expectEmit(true, true, true, true); + emit BuybackDelegate_Swap(1, _amountIn, pool, _amountOutQuoted, address(jbEthPaymentTerminal)); // Pay the project - jbEthPaymentTerminal.pay{value: 1 ether}( + jbEthPaymentTerminal.pay{value: _amountIn}( 1, - 1 ether, + _amountIn, address(0), - address(123), + beneficiary, /* _minReturnedTokens */ 0, /* _preferClaimedTokens */ @@ -320,14 +316,11 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { _delegateMetadata ); - uint256 _balAfterPayment = jbx.balanceOf(address(123)); - uint256 _diff = _balAfterPayment - _balBeforePayment; - // Check: token received by the beneficiary - assertEq(_diff, amountOutForOneEth / 2); + assertApproxEqAbs(jbx.balanceOf(beneficiary) - _balBeforePayment, _amountOutQuoted - (_amountOutQuoted * _reservedRate / 10000), 1, "wrong balance"); // Check: token added to the reserve - 1 wei sensitivity for rounding errors - assertApproxEqAbs(jbController.reservedTokenBalanceOf(1), _reservedBalanceBefore + amountOutForOneEth / 2, 1); + assertApproxEqAbs(jbController.reservedTokenBalanceOf(1), _reservedBalanceBefore + _amountOutQuoted * _reservedRate / 10000, 1, "wrong reserve"); } /** @@ -341,21 +334,21 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { // Build the metadata using the quote at that block // Build the metadata using the quote at that block bytes[] memory _data = new bytes[](1); - _data[0] = abi.encode(amountOutForOneEth, 500); + _data[0] = abi.encode(amountOutQuoted, amountPaid); // Pass the delegate id bytes4[] memory _ids = new bytes4[](1); _ids[0] = bytes4(hex"69"); // Generate the metadata - bytes memory _delegateMetadata = delegate.createMetadata(_ids, _data); + bytes memory _delegateMetadata = metadataHelper.createMetadata(_ids, _data); // Pay the project - jbEthPaymentTerminal.pay{value: 1 ether}( + jbEthPaymentTerminal.pay{value: amountPaid}( 1, - 1 ether, + amountPaid, address(0), - address(123), + beneficiary, /* _minReturnedTokens */ 0, /* _preferClaimedTokens */ @@ -366,29 +359,29 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { _delegateMetadata ); - uint256 _balanceBeneficiary = jbx.balanceOf(address(123)); + uint256 _balanceBeneficiary = jbx.balanceOf(beneficiary); uint256 _reserveBalance = jbController.reservedTokenBalanceOf(1); // Update the quote, this is now a different one as we already swapped - uint256 _previousQuote = amountOutForOneEth; - amountOutForOneEth = getAmountOut(pool, 1 ether, address(weth)); + uint256 _previousQuote = amountOutQuoted; + amountOutQuoted = getAmountOut(pool, 1 ether, address(weth)); // Sanity check - assert(_previousQuote != amountOutForOneEth); + assert(_previousQuote != amountOutQuoted); // Update the metadata - _data[0] = abi.encode(amountOutForOneEth, 500); + _data[0] = abi.encode(amountOutQuoted, amountPaid); // Generate the metadata - _delegateMetadata = delegate.createMetadata(_ids, _data); + _delegateMetadata = metadataHelper.createMetadata(_ids, _data); // Pay the project - jbEthPaymentTerminal.pay{value: 1 ether}( + jbEthPaymentTerminal.pay{value: amountPaid}( 1, - 1 ether, + amountPaid, address(0), - address(123), + beneficiary, /* _minReturnedTokens */ 0, /* _preferClaimedTokens */ @@ -400,10 +393,10 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { ); // Check: token received by the beneficiary - assertEq(jbx.balanceOf(address(123)), _balanceBeneficiary + amountOutForOneEth / 2); + assertEq(jbx.balanceOf(beneficiary), _balanceBeneficiary + amountOutQuoted / 2); // Check: token added to the reserve - 1 wei sensitivity for rounding errors - assertApproxEqAbs(jbController.reservedTokenBalanceOf(1), _reserveBalance + amountOutForOneEth / 2, 1); + assertApproxEqAbs(jbController.reservedTokenBalanceOf(1), _reserveBalance + amountOutQuoted / 2, 1); } /** @@ -423,26 +416,26 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { // Build the metadata using the quote bytes[] memory _data = new bytes[](1); - _data[0] = abi.encode(_quote, 500); + _data[0] = abi.encode(_quote, _amountIn); // Pass the delegate id bytes4[] memory _ids = new bytes4[](1); _ids[0] = bytes4(hex"69"); // Generate the metadata - bytes memory _delegateMetadata = delegate.createMetadata(_ids, _data); + bytes memory _delegateMetadata = metadataHelper.createMetadata(_ids, _data); vm.expectEmit(true, true, true, true); - emit BuybackDelegate_Swap(1, _amountIn, _quote); + emit BuybackDelegate_Swap(1, _amountIn, pool, _quote, address(jbEthPaymentTerminal)); - uint256 _balBeforePayment = jbx.balanceOf(address(123)); + uint256 _balBeforePayment = jbx.balanceOf(beneficiary); // Pay the project jbEthPaymentTerminal.pay{value: _amountIn}( 1, _amountIn, address(0), - address(123), + beneficiary, /* _minReturnedTokens */ 0, /* _preferClaimedTokens */ @@ -453,7 +446,7 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { _delegateMetadata ); - uint256 _balAfterPayment = jbx.balanceOf(address(123)); + uint256 _balAfterPayment = jbx.balanceOf(beneficiary); uint256 _diff = _balAfterPayment - _balBeforePayment; // Check: token received by the beneficiary @@ -468,158 +461,31 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { * * @dev Should swap for both beneficiary and reserve (by burning/minting) */ - function test_swapWhenQuoteNotProvidedInMetadata(uint256 _amountIn) public { - _amountIn = bound(_amountIn, 100, 100 ether); - - // Reconfigure with a weight of 1 - _reconfigure(1, address(delegate), 10, 0); - - uint256 _reservedBalanceBefore = jbController.reservedTokenBalanceOf(1); - - uint256 _quote = _getTwapQuote(_amountIn, cardinality, twapDelta); - - // for checking balance difference after payment - uint256 _balanceBeforePayment = jbx.balanceOf(address(123)); - - // to check if the swap failed and mint happened - uint256 _terminalBalanceBeforePayment = jbTerminalStore.balanceOf(terminal, 1); - - // Pay the project - jbEthPaymentTerminal.pay{value: _amountIn}( - 1, - _amountIn, - address(0), - address(123), - /* _minReturnedTokens */ - 0, - /* _preferClaimedTokens */ - true, - /* _memo */ - "Take my money!", - /* _delegateMetadata */ - new bytes(0) - ); - - uint256 _balanceAfterPayment = jbx.balanceOf(address(123)); - - // check if there was any increase in the terminal balance - uint256 _terminalBalanceAfterPayment = jbTerminalStore.balanceOf(terminal, 1); - uint256 _terminalBalanceDiff = _terminalBalanceAfterPayment - _terminalBalanceBeforePayment; - - // if terminal balance is 0 that means a swap happened else mint happened - if (_terminalBalanceDiff == 0) { - assertGt(jbx.balanceOf(address(123)), _quote); - } else { - // calculating token count - uint256 _balanceDifference = _balanceAfterPayment - _balanceBeforePayment; - uint256 _tokenCount = mulDiv18(_amountIn, 10); - assertEq(_balanceDifference, _tokenCount); - } - - // Check: reserve unchanged - assertEq(jbController.reservedTokenBalanceOf(1), _reservedBalanceBefore); - } - - /** - * @notice If the amount of token returned by swapping is greater than by minting, swap & use quote from uniswap lib rather than a user provided quote & some amount is refunded after the swap - * - * @dev Should swap for both beneficiary and reserve (by burning/minting) - */ - function test_swapWhenQuoteNotProvidedInMetadataAndWhenRefundHappensAfterTheSwap() public { - // we need to swap with a large amount to go near the price limit and trigger a refund - uint256 _largeSwapAmount = 150 ether; - deal(address(123), _largeSwapAmount); - - // Reconfigure with a weight of 1 - _reconfigure(1, address(delegate), 10, 0); - - uint256 _reservedBalanceBefore = jbController.reservedTokenBalanceOf(1); - - uint256 _quote = _getTwapQuote(_largeSwapAmount, cardinality, twapDelta); - - // for checking balance difference after payment - uint256 _balanceBeforePayment = jbx.balanceOf(address(123)); - - // to check if the swap failed and mint happened - uint256 _terminalBalanceBeforePayment = jbTerminalStore.balanceOf(terminal, 1); - - // Pay the project - jbEthPaymentTerminal.pay{value: _largeSwapAmount}( - 1, - _largeSwapAmount, - address(0), - address(123), - /* _minReturnedTokens */ - 0, - /* _preferClaimedTokens */ - true, - /* _memo */ - "Take my money!", - /* _delegateMetadata */ - new bytes(0) - ); - - uint256 _balanceAfterPayment = jbx.balanceOf(address(123)); - - // check if there was any increase in the terminal balance - uint256 _terminalBalanceAfterPayment = jbTerminalStore.balanceOf(terminal, 1); - uint256 _terminalBalanceDiff = _terminalBalanceAfterPayment - _terminalBalanceBeforePayment; + function test_swapWhenQuoteNotProvidedInMetadata(uint256 _amountIn, uint256 _reservedRate) public { + _amountIn = bound(_amountIn, 10, 10 ether); + _reservedRate = bound(_reservedRate, 0, 10000); - // if terminal balance is 0 that means a swap happened else mint happened - if (_terminalBalanceDiff == 0) { - assertGt(jbx.balanceOf(address(123)), _quote); - } else { - // calculating token count - uint256 _balanceDifference = _balanceAfterPayment - _balanceBeforePayment; - uint256 _tokenCount = mulDiv18(_largeSwapAmount, 10); - - assertEq(_balanceDifference, _tokenCount); - } - - // Check: reserve unchanged - assertEq(jbController.reservedTokenBalanceOf(1), _reservedBalanceBefore); - - // beneficiary sweeping the leftover amount - uint256 _balanceBeforeSweepingLeftOverFunds = address(123).balance; - - uint256 _currentSweepBalance = delegate.sweepBalance(); - - delegate.sweep(address(123)); - - uint256 _balanceAftereSweepingLeftOverFunds = address(123).balance; - - assertEq(_balanceAftereSweepingLeftOverFunds - _balanceBeforeSweepingLeftOverFunds, _currentSweepBalance); - } - - /** - * @notice If the amount of token returned by swapping is greater than by minting, swap & use quote from uniswap lib when cardinality is increased - * - * @dev Should swap for both beneficiary and reserve (by burning/minting) - */ - function test_swapWhenCardinalityIsIncreased(uint256 _amountIn) public { - _amountIn = bound(_amountIn, 100, 100 ether); + uint256 _weight = 10 ether; - // Reconfigure with a weight of 1 - _reconfigure(1, address(delegate), 10, 0); + _reconfigure(1, address(delegate), _weight, _reservedRate); uint256 _reservedBalanceBefore = jbController.reservedTokenBalanceOf(1); - uint256 _quote = _getTwapQuote(_amountIn, 200000, twapDelta); + // The twap which is going to be used + uint256 _twap = _getTwapQuote(_amountIn, cardinality, twapDelta); - delegate.increaseSecondsAgo(200000); + // The actual quote, here for test only + uint256 _quote = getAmountOut(pool, _amountIn, address(weth)); // for checking balance difference after payment - uint256 _balanceBeforePayment = jbx.balanceOf(address(123)); - - // to check if the swap failed and mint happened - uint256 _terminalBalanceBeforePayment = jbTerminalStore.balanceOf(terminal, 1); + uint256 _balanceBeforePayment = jbx.balanceOf(beneficiary); // Pay the project jbEthPaymentTerminal.pay{value: _amountIn}( 1, _amountIn, address(0), - address(123), + beneficiary, /* _minReturnedTokens */ 0, /* _preferClaimedTokens */ @@ -630,56 +496,43 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { new bytes(0) ); - uint256 _balanceAfterPayment = jbx.balanceOf(address(123)); + uint256 _balanceAfterPayment = jbx.balanceOf(beneficiary); + uint256 _tokenReceived = _balanceAfterPayment - _balanceBeforePayment; - // check if there was any increase in the terminal balance - uint256 _terminalBalanceAfterPayment = jbTerminalStore.balanceOf(terminal, 1); - uint256 _terminalBalanceDiff = _terminalBalanceAfterPayment - _terminalBalanceBeforePayment; + uint256 _tokenCount = mulDiv18(_amountIn, _weight); - // if terminal balance is 0 that means a swap happened else mint happened - if (_terminalBalanceDiff == 0) { - assertGt(jbx.balanceOf(address(123)), _quote); + // 1 wei sensitivity for rounding errors + if (_twap > _tokenCount) { + // Path is picked based on twap, but the token received are the one quoted + assertApproxEqAbs(_tokenReceived, _quote - (_quote * _reservedRate) / 10000, 1, "wrong swap"); + assertApproxEqAbs(jbController.reservedTokenBalanceOf(1), _reservedBalanceBefore + (_quote * _reservedRate) / 10000, 1, "Reserve"); } else { - // calculating token count - uint256 _balanceDifference = _balanceAfterPayment - _balanceBeforePayment; - uint256 _tokenCount = mulDiv18(_amountIn, 10); - assertEq(_balanceDifference, _tokenCount); + assertApproxEqAbs(_tokenReceived, _tokenCount - (_tokenCount * _reservedRate) / 10000, 1, "Wrong mint"); + assertApproxEqAbs(jbController.reservedTokenBalanceOf(1), _reservedBalanceBefore + (_tokenCount * _reservedRate) / 10000, 1, "Reserve"); } - - // Check: reserve unchanged - assertEq(jbController.reservedTokenBalanceOf(1), _reservedBalanceBefore); } /** - * @notice If the amount of token returned by swapping is greater than by minting, swap & use quote from uniswap lib when twapDelta is updated + * @notice If the amount of token returned by minting is greater than by swapping, we mint outside of the delegate & when there is no user provided quote presemt in metadata * - * @dev Should swap for both beneficiary and reserve (by burning/minting) + * @dev Should mint for both beneficiary and reserve */ - function test_swapWhenTwapDeltaIsUpdated(uint256 _amountIn, uint256 _twapDelta) public { - _amountIn = bound(_amountIn, 100, 100 ether); - // restricting to avoid slippage errors - _twapDelta = bound(_twapDelta, 300, 8000); - - // Reconfigure with a weight of 1 - _reconfigure(1, address(delegate), 10, 0); + function test_swapWhenMintIsPreferredEvenWhenMetadataIsNotPresent(uint256 _amountIn) public { + _amountIn = bound(_amountIn, 1 ether, 1000 ether); uint256 _reservedBalanceBefore = jbController.reservedTokenBalanceOf(1); - uint256 _quote = _getTwapQuote(_amountIn, cardinality, _twapDelta); - - delegate.setTwapDelta(_twapDelta); + // Reconfigure with a weight of amountOutQuoted + 1 + _reconfigure(1, address(delegate), amountOutQuoted + 1, 0); - // for checking balance difference after payment - uint256 _balanceBeforePayment = jbx.balanceOf(address(123)); + uint256 _balBeforePayment = jbx.balanceOf(beneficiary); - // to check if the swap failed and mint happened - uint256 _terminalBalanceBeforePayment = jbTerminalStore.balanceOf(terminal, 1); // Pay the project jbEthPaymentTerminal.pay{value: _amountIn}( 1, _amountIn, address(0), - address(123), + beneficiary, /* _minReturnedTokens */ 0, /* _preferClaimedTokens */ @@ -690,47 +543,49 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { new bytes(0) ); - uint256 _balanceAfterPayment = jbx.balanceOf(address(123)); + uint256 expectedTokenCount = PRBMath.mulDiv(_amountIn, amountOutQuoted + 1, 10 ** 18); - // check if there was any increase in the terminal balance - uint256 _terminalBalanceAfterPayment = jbTerminalStore.balanceOf(terminal, 1); - uint256 _terminalBalanceDiff = _terminalBalanceAfterPayment - _terminalBalanceBeforePayment; + uint256 _balAfterPayment = jbx.balanceOf(beneficiary); + uint256 _diff = _balAfterPayment - _balBeforePayment; - // if terminal balance is 0 that means a swap happened else mint happened - if (_terminalBalanceDiff == 0) { - assertGt(jbx.balanceOf(address(123)), _quote); - } else { - // calculating token count - uint256 _balanceDifference = _balanceAfterPayment - _balanceBeforePayment; - uint256 _tokenCount = mulDiv18(_amountIn, 10); - assertEq(_balanceDifference, _tokenCount); - } + // Check: token received by the beneficiary + assertEq(_diff, expectedTokenCount); // Check: reserve unchanged assertEq(jbController.reservedTokenBalanceOf(1), _reservedBalanceBefore); } /** - * @notice If the amount of token returned by minting is greater than by swapping, we mint outside of the delegate & when there is no user provided quote presemt in metadata - * - * @dev Should mint for both beneficiary and reserve + * @notice If the amount of token returned by swapping is greater than by minting but slippage is too high, + * revert if a quote was passed in the pay data */ - function test_swapWhenMintIsPreferredEvenWhenMetadataIsNotPresent(uint256 _amountIn) public { - _amountIn = bound(_amountIn, 1 ether, 1000 ether); + function test_revertIfSlippageTooHighAndQuote() public { + uint256 _weight = 50; + // Reconfigure with a weight smaller than the quote, slippage included + _reconfigure(1, address(delegate), _weight, 5000); - uint256 _reservedBalanceBefore = jbController.reservedTokenBalanceOf(1); + // Build the metadata using the quote at that block + bytes[] memory _data = new bytes[](1); + _data[0] = abi.encode( + 69412820131620254304865 + 10, // 10 more than quote at that block + 0 + ); + + // Pass the delegate id + bytes4[] memory _ids = new bytes4[](1); + _ids[0] = bytes4(hex"69"); - // Reconfigure with a weight of amountOutForOneEth + 1 - _reconfigure(1, address(delegate), amountOutForOneEth + 1, 0); + // Generate the metadata + bytes memory _delegateMetadata = metadataHelper.createMetadata(_ids, _data); - uint256 _balBeforePayment = jbx.balanceOf(address(123)); + vm.expectRevert(IJBBuybackDelegate.JuiceBuyback_MaximumSlippage.selector); // Pay the project - jbEthPaymentTerminal.pay{value: _amountIn}( + jbEthPaymentTerminal.pay{value: 1 ether}( 1, - _amountIn, + 1 ether, address(0), - address(123), + beneficiary, /* _minReturnedTokens */ 0, /* _preferClaimedTokens */ @@ -738,57 +593,45 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { /* _memo */ "Take my money!", /* _delegateMetadata */ - new bytes(0) + _delegateMetadata ); - - uint256 expectedTokenCount = PRBMath.mulDiv(_amountIn, amountOutForOneEth + 1, 10 ** 18); - - uint256 _balAfterPayment = jbx.balanceOf(address(123)); - uint256 _diff = _balAfterPayment - _balBeforePayment; - - // Check: token received by the beneficiary - assertEq(_diff, expectedTokenCount); - - // Check: reserve unchanged - assertEq(jbController.reservedTokenBalanceOf(1), _reservedBalanceBefore); } - /** - * @notice If the amount of token returned by swapping is greater than by minting but slippage is too high, mint - */ - function test_mintIfSlippageTooHigh() public { - uint256 _weight = 50; - // Reconfigure with a weight smaller than the quote, slippage included + function test_mintWithExtraFunds(uint256 _amountIn, uint256 _amountInExtra) public { + _amountIn = bound(_amountIn, 100, 10 ether); + _amountInExtra = bound(_amountInExtra, 100, 10 ether); + + // Refresh the quote + amountOutQuoted = getAmountOut(pool, _amountIn, address(weth)); + + // Reconfigure with a weight smaller than the quote + uint256 _weight = amountOutQuoted * 10**18 / _amountIn - 1; _reconfigure(1, address(delegate), _weight, 5000); uint256 _reservedBalanceBefore = jbController.reservedTokenBalanceOf(1); // Build the metadata using the quote at that block bytes[] memory _data = new bytes[](1); - _data[0] = abi.encode( - 69412820131620254304865 + 10, // 10 more than quote at that block - 0 - ); + _data[0] = abi.encode(amountOutQuoted, _amountIn); // Pass the delegate id bytes4[] memory _ids = new bytes4[](1); _ids[0] = bytes4(hex"69"); // Generate the metadata - bytes memory _delegateMetadata = delegate.createMetadata(_ids, _data); + bytes memory _delegateMetadata = metadataHelper.createMetadata(_ids, _data); - // Fall back on delegate minting - vm.expectEmit(true, true, true, true); - emit BuybackDelegate_Mint(1); + uint256 _balBeforePayment = jbx.balanceOf(beneficiary); - uint256 _balBeforePayment = jbx.balanceOf(address(123)); + vm.expectEmit(true, true, true, true); + emit BuybackDelegate_Swap(1, _amountIn, pool, amountOutQuoted, address(jbEthPaymentTerminal)); // Pay the project - jbEthPaymentTerminal.pay{value: 1 ether}( + jbEthPaymentTerminal.pay{value: _amountIn + _amountInExtra}( 1, - 1 ether, + _amountIn + _amountInExtra, address(0), - address(123), + beneficiary, /* _minReturnedTokens */ 0, /* _preferClaimedTokens */ @@ -799,14 +642,11 @@ contract TestJBBuybackDelegate_Fork is Test, UniswapV3ForgeQuoter { _delegateMetadata ); - uint256 _balAfterPayment = jbx.balanceOf(address(123)); - uint256 _diff = _balAfterPayment - _balBeforePayment; - // Check: token received by the beneficiary - assertEq(_diff, _weight / 2); + assertApproxEqAbs(jbx.balanceOf(beneficiary) - _balBeforePayment, amountOutQuoted / 2 + mulDiv18(_amountInExtra, _weight) / 2, 10); - // Check: token added to the reserve - 1 wei sensitivity for rounding errors - assertApproxEqAbs(jbController.reservedTokenBalanceOf(1), _reservedBalanceBefore + _weight / 2, 1); + // Check: token added to the reserve + assertApproxEqAbs(jbController.reservedTokenBalanceOf(1), _reservedBalanceBefore + amountOutQuoted / 2 + mulDiv18(_amountInExtra, _weight) / 2, 10); } function _reconfigure(uint256 _projectId, address _delegate, uint256 _weight, uint256 _reservedRate) internal { diff --git a/contracts/test/JBBuybackDelegate_Integration.t.sol b/contracts/test/JBBuybackDelegate_Integration.t.sol deleted file mode 100644 index c75be38..0000000 --- a/contracts/test/JBBuybackDelegate_Integration.t.sol +++ /dev/null @@ -1,360 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.16; - -import "../interfaces/external/IWETH9.sol"; -import "./helpers/TestBaseWorkflowV3.sol"; - -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBController.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleStore.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleBallot.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleDataSource.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBOperatable.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPayDelegate.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBRedemptionDelegate.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPayoutRedemptionPaymentTerminal.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBSingleTokenPaymentTerminalStore.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBToken.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBConstants.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBCurrencies.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBFundingCycleMetadataResolver.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBOperations.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBTokens.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/structs/JBFundingCycle.sol"; - -import "@paulrberg/contracts/math/PRBMath.sol"; -import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; -import "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol"; - -import "../JBBuybackDelegate.sol"; -import "../mock/MockAllocator.sol"; - -/** - * @notice Integration tests for the JBBuybackDelegate contract. - * - */ -contract TestJBBuybackDelegate_Integration is TestBaseWorkflowV3 { - using JBFundingCycleMetadataResolver for JBFundingCycle; - - event Mint( - address indexed holder, - uint256 indexed projectId, - uint256 amount, - bool tokensWereClaimed, - bool preferClaimedTokens, - address caller - ); - - JBProjectMetadata _projectMetadata; - JBFundingCycleData _data; - JBFundingCycleData _dataReconfiguration; - JBFundingCycleData _dataWithoutBallot; - JBFundingCycleMetadata _metadata; - JBFundAccessConstraints[] _fundAccessConstraints; // Default empty - IJBPaymentTerminal[] _terminals; // Default empty - - uint256 _projectId; - uint256 reservedRate = 4500; - uint256 weight = 10 ** 18; // Minting 1 token per eth - - uint32 cardinality = 1000; - - uint256 twapDelta = 500; - - JBBuybackDelegate _delegate; - - // Use the L1 UniswapV3Pool jbx/eth 1% fee for create2 magic - IUniswapV3Pool pool = IUniswapV3Pool(0x48598Ff1Cee7b4d31f8f9050C2bbAE98e17E6b17); - IJBToken jbx = IJBToken(0x3abF2A4f8452cCC2CF7b4C1e4663147600646f66); - IWETH9 weth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); - address _uniswapFactory = 0x1F98431c8aD98523631AE4a59f267346ea31F984; - uint24 fee = 10000; - - /** - * @notice Set up a new JBX project and use the buyback delegate as the datasource - */ - function setUp() public override { - // label - vm.label(address(pool), "uniswapPool"); - vm.label(address(_uniswapFactory), "uniswapFactory"); - vm.label(address(weth), "$WETH"); - vm.label(address(jbx), "$JBX"); - - // mock - vm.etch(address(pool), "0x69"); - vm.etch(address(weth), "0x69"); - vm.etch(address(jbx), "0x69"); - - // super is the Jbx V3 fixture - super.setUp(); - - // Deploy the delegate - _delegate = new JBBuybackDelegate( - IERC20(address(jbx)), - weth, - _uniswapFactory, - fee, - cardinality, - twapDelta, - IJBDirectory(address(_jbDirectory)), - _jbController, - bytes4(hex'69') - ); - - _projectMetadata = JBProjectMetadata({content: "myIPFSHash", domain: 1}); - - _data = JBFundingCycleData({ - duration: 6 days, - weight: weight, - discountRate: 0, - ballot: IJBFundingCycleBallot(address(0)) - }); - - _metadata = JBFundingCycleMetadata({ - global: JBGlobalFundingCycleMetadata({ - allowSetTerminals: false, - allowSetController: false, - pauseTransfers: false - }), - reservedRate: reservedRate, - redemptionRate: 5000, - ballotRedemptionRate: 0, - pausePay: false, - pauseDistributions: false, - pauseRedeem: false, - pauseBurn: false, - allowMinting: true, - preferClaimedTokenOverride: false, - allowTerminalMigration: false, - allowControllerMigration: false, - holdFees: false, - useTotalOverflowForRedemptions: false, - useDataSourceForPay: true, - useDataSourceForRedeem: false, - dataSource: address(_delegate), - metadata: 0 - }); - - _fundAccessConstraints.push( - JBFundAccessConstraints({ - terminal: _jbETHPaymentTerminal, - token: jbLibraries().ETHToken(), - distributionLimit: 2 ether, - overflowAllowance: type(uint232).max, - distributionLimitCurrency: 1, // Currency = ETH - overflowAllowanceCurrency: 1 - }) - ); - - _terminals = [_jbETHPaymentTerminal]; - - JBGroupedSplits[] memory _groupedSplits = new JBGroupedSplits[](1); // Default empty - - _projectId = _jbController.launchProjectFor( - _multisig, - _projectMetadata, - _data, - _metadata, - 0, // Start asap - _groupedSplits, - _fundAccessConstraints, - _terminals, - "" - ); - } - - /** - * @notice If the quote amount is lower than the token that would be received after minting, the buyback delegate isn't used at all - */ - function testDatasourceDelegateWhenQuoteIsLowerThanTokenCount(uint256 _quote) public { - // Do not use a quote of 0, as it would then fetch a twap - _quote = bound(_quote, 1, weight); - - uint256 payAmountInWei = 2 ether; - - // setting the quote in metadata, bigger than the weight - bytes[] memory _quoteData = new bytes[](1); - _quoteData[0] = abi.encode(_quote, 500); - - // Pass the delegate id - bytes4[] memory _ids = new bytes4[](1); - _ids[0] = bytes4(hex"69"); - - // Generate the metadata - bytes memory _delegateMetadata = _delegate.createMetadata(_ids, _quoteData); - - // Compute the project token which should have been minted (for the beneficiary or the reserve) - uint256 totalMinted = PRBMath.mulDiv(payAmountInWei, weight, 10 ** 18); - uint256 amountBeneficiary = - (totalMinted * (JBConstants.MAX_RESERVED_RATE - reservedRate)) / JBConstants.MAX_RESERVED_RATE; - uint256 amountReserved = totalMinted - amountBeneficiary; - - // This shouldn't mint via the delegate - vm.expectEmit(true, true, true, true); - emit Mint({ - holder: _beneficiary, - projectId: _projectId, - amount: amountBeneficiary, - tokensWereClaimed: false, - preferClaimedTokens: true, - caller: address(_jbController) - }); - - _jbETHPaymentTerminal.pay{value: payAmountInWei}( - _projectId, - payAmountInWei, - address(0), - _beneficiary, - /* _minReturnedTokens */ - 0, - /* _preferClaimedTokens */ - true, - /* _memo */ - "Take my money!", - /* _delegateMetadata */ - _delegateMetadata - ); - - // Check: correct beneficiary balance? - assertEq(_jbTokenStore.balanceOf(_beneficiary, _projectId), amountBeneficiary); - - // Check: correct reserve? - assertEq(_jbController.reservedTokenBalanceOf(_projectId), amountReserved); - } - - /** - * @notice If claimed token flag is not true then make sure the delegate mints the tokens & the balance distribution is correct - */ - function testDatasourceDelegateMintIfPreferenceIsNotToClaimTokens() public { - uint256 payAmountInWei = 10 ether; - - // setting the quote in metadata - bytes[] memory _quoteData = new bytes[](1); - _quoteData[0] = abi.encode(1 ether, 10000); - - // Pass the delegate id - bytes4[] memory _ids = new bytes4[](1); - _ids[0] = bytes4(hex"69"); - - // Generate the metadata - bytes memory _delegateMetadata = _delegate.createMetadata(_ids, _quoteData); - - uint256 totalMinted = PRBMath.mulDiv(payAmountInWei, weight, 10 ** 18); - uint256 amountBeneficiary = - PRBMath.mulDiv(totalMinted, JBConstants.MAX_RESERVED_RATE - reservedRate, JBConstants.MAX_RESERVED_RATE); - - uint256 amountReserved = totalMinted - amountBeneficiary; - - // This shouldn't mint via the delegate - vm.expectEmit(true, true, true, true); - emit Mint({ - holder: _beneficiary, - projectId: _projectId, - amount: amountBeneficiary, - tokensWereClaimed: false, - preferClaimedTokens: false, - caller: address(_jbController) - }); - - _jbETHPaymentTerminal.pay{value: payAmountInWei}( - _projectId, - payAmountInWei, - address(0), - _beneficiary, - /* _minReturnedTokens */ - 0, // Cannot be used in this setting - /* _preferClaimedTokens */ - false, - /* _memo */ - "Take my money!", - /* _delegateMetadata */ - _delegateMetadata - ); - - assertEq(_jbTokenStore.balanceOf(_beneficiary, _projectId), amountBeneficiary); - assertEq(_jbController.reservedTokenBalanceOf(_projectId), amountReserved); - assertEq(_jbPaymentTerminalStore.balanceOf(_jbETHPaymentTerminal, _projectId), payAmountInWei); - } - - /** - * @notice if claimed token flag is true and the quote is greather than the weight, we go for the swap path - */ - function testDatasourceDelegateSwapIfPreferenceIsToClaimTokens() public { - uint256 payAmountInWei = 1 ether; - uint256 quoteOnUniswap = (weight * 106) / 100; // Take slippage into account - - // Trick the delegate balance post-swap (avoid callback revert on slippage) - vm.prank(_multisig); - _jbController.mintTokensOf(_projectId, quoteOnUniswap, address(_delegate), "", false, false); - - // setting the quote in metadata - bytes[] memory _quoteData = new bytes[](1); - _quoteData[0] = abi.encode(quoteOnUniswap, 500); - - // Pass the delegate id - bytes4[] memory _ids = new bytes4[](1); - _ids[0] = bytes4(hex"69"); - - // Generate the metadata - bytes memory _delegateMetadata = _delegate.createMetadata(_ids, _quoteData); - - // Mock the jbx transfer to the beneficiary - same logic as in delegate to avoid rounding errors - uint256 reservedAmount = PRBMath.mulDiv(quoteOnUniswap, reservedRate, JBConstants.MAX_RESERVED_RATE); - - uint256 nonReservedAmount = quoteOnUniswap - reservedAmount; - - // mock the burn call - vm.mockCall( - address(_jbController), - abi.encodeCall(_jbController.burnTokensOf, (address(_delegate), _projectId, quoteOnUniswap, "", true)), - abi.encode(true) - ); - - uint256 _beneficiaryBalanceBefore = _jbTokenStore.balanceOf(_beneficiary, _projectId); - - // Mock the swap returned value, which is the amount of token transfered (negative = exact amount) - vm.mockCall( - address(pool), - abi.encodeWithSelector(IUniswapV3PoolActions.swap.selector), - abi.encode(-int256(quoteOnUniswap), 0) - ); - - // Check: swap triggered? - vm.expectCall(address(pool), abi.encodeWithSelector(IUniswapV3PoolActions.swap.selector)); - - _jbETHPaymentTerminal.pay{value: payAmountInWei}( - _projectId, - payAmountInWei, - address(0), - _beneficiary, - /* _minReturnedTokens */ - 0, - /* _preferClaimedTokens */ - true, - /* _memo */ - "Take my money!", - /* _delegateMetadata */ - _delegateMetadata - ); - - assertEq(_jbTokenStore.balanceOf(_beneficiary, _projectId), _beneficiaryBalanceBefore + nonReservedAmount); - - // Check: correct reserve balance? - assertEq(_jbController.reservedTokenBalanceOf(_projectId), reservedAmount); - } - - /** - * @notice Test the uniswap callback reverting when max slippage is hit - * - * @dev This would mean the _mint is then called - */ - function testRevertIfSlippageIsTooMuchWhenSwapping() public { - // construct metadata, minimum amount received is 100 - bytes memory metadata = abi.encode(100 ether); - - vm.prank(address(pool)); - vm.expectRevert(abi.encodeWithSignature("JuiceBuyback_MaximumSlippage()")); - - // callback giving 1 instead - _delegate.uniswapV3SwapCallback(-1 ether, 1 ether, metadata); - } -} diff --git a/contracts/test/JBBuybackDelegate_Invariant.t.sol b/contracts/test/JBBuybackDelegate_Invariant.t.sol new file mode 100644 index 0000000..8abefef --- /dev/null +++ b/contracts/test/JBBuybackDelegate_Invariant.t.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; + +import "./helpers/TestBaseWorkflowV3.sol"; + +import {JBTokens} from "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBTokens.sol"; +import {JBDelegateMetadataHelper} from "@jbx-protocol/juice-delegate-metadata-lib/src/JBDelegateMetadataHelper.sol"; +import {PoolTestHelper} from "@exhausted-pigeon/uniswap-v3-foundry-pool/src/PoolTestHelper.sol"; + + +/** + * @notice Invariant tests for the JBBuybackDelegate contract. + * + * @dev Invariant tested: + * - BBD1: totalSupply after pay == total supply before pay + (amountIn * weight / 10^18) + */ +contract TestJBBuybackDelegate_Invariant is TestBaseWorkflowV3, PoolTestHelper { + + BBDHandler handler; + JBDelegateMetadataHelper _metadataHelper = new JBDelegateMetadataHelper(); + + /** + * @notice Set up a new JBX project and use the buyback delegate as the datasource + */ + function setUp() public override { + // super is the Jbx V3 fixture: deploy full protocol, launch project 1, emit token, deploy delegate, set the pool + super.setUp(); + + handler = new BBDHandler(_jbETHPaymentTerminal, _projectId, pool, _delegate); + + PoolTestHelper _helper = new PoolTestHelper(); + IUniswapV3Pool _newPool = IUniswapV3Pool(address(_helper.createPool(address(weth), address(_jbController.tokenStore().tokenOf(_projectId)), fee, 1000 ether, PoolTestHelper.Chains.Mainnet))); + + targetContract(address(handler)); + } + + function invariant_BBD1() public { + uint256 _amountIn = handler.ghost_accumulatorAmountIn(); + + assertEq( + _jbController.totalOutstandingTokensOf(_projectId), + _amountIn * weight / 10 ** 18 + ); + } + + function test_inv() public { + assert(true); + } +} + +contract BBDHandler is Test { + JBDelegateMetadataHelper immutable metadataHelper; + JBETHPaymentTerminal3_1_1 immutable jbETHPaymentTerminal; + IUniswapV3Pool immutable pool; + IJBBuybackDelegate immutable delegate; + uint256 immutable projectId; + + address public _beneficiary; + + uint256 public ghost_accumulatorAmountIn; + uint256 public ghost_liquidityProvided; + uint256 public ghost_liquidityToUse; + + modifier useLiquidity(uint256 _seed) { + ghost_liquidityToUse = bound(_seed, 1, ghost_liquidityProvided); + _; + } + + constructor( + JBETHPaymentTerminal3_1_1 _terminal, + uint256 _projectId, + IUniswapV3Pool _pool, + IJBBuybackDelegate _delegate + ) { + metadataHelper = new JBDelegateMetadataHelper(); + + jbETHPaymentTerminal = _terminal; + projectId = _projectId; + pool = _pool; + delegate = _delegate; + + _beneficiary = makeAddr('_beneficiary'); + } + + function trigger_pay(uint256 _amountIn) public { + _amountIn = bound(_amountIn, 0, 10000 ether); + + // bool zeroForOne = jbETHPaymentTerminal.token() > address(JBTokens.ETH); + + // vm.mockCall( + // address(pool), + // abi.encodeCall( + // IUniswapV3PoolActions.swap, + // ( + // address(delegate), + // zeroForOne, + // int256(_amountIn), + // zeroForOne + // ? TickMath.MIN_SQRT_RATIO + 1 + // : TickMath.MAX_SQRT_RATIO - 1, + // abi.encode(projectId, JBTokens.ETH) + // ) + // ), + // abi.encode(0, 0) + // ); + + vm.deal(address(this), _amountIn); + ghost_accumulatorAmountIn += _amountIn; + + uint256 _quote = 1; + + // set only valid metadata + bytes[] memory _quoteData = new bytes[](1); + _quoteData[0] = abi.encode(_quote, _amountIn); + + // Pass the delegate id + bytes4[] memory _ids = new bytes4[](1); + _ids[0] = bytes4(hex"69"); + + // Generate the metadata + bytes memory _delegateMetadata = metadataHelper.createMetadata(_ids, _quoteData); + + jbETHPaymentTerminal.pay{value: _amountIn}( + projectId, + _amountIn, + address(0), + _beneficiary, + /* _minReturnedTokens */ + 0, + /* _preferClaimedTokens */ + true, + /* _memo */ + "Take my money!", + /* _delegateMetadata */ + _delegateMetadata + ); + } + + function addLiquidity(uint256 _amount0, uint256 _amount1, int24 _lowerTick, int24 _upperTick) public { + // ghost_liquidityProvided += pool.addLiquidity() + + } + +} diff --git a/contracts/test/JBBuybackDelegate_Unit.t.sol b/contracts/test/JBBuybackDelegate_Unit.t.sol index 4603ded..db05b17 100644 --- a/contracts/test/JBBuybackDelegate_Unit.t.sol +++ b/contracts/test/JBBuybackDelegate_Unit.t.sol @@ -6,17 +6,19 @@ import "./helpers/TestBaseWorkflowV3.sol"; import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBController3_1.sol"; import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBDirectory.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBConstants.sol"; +import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBRedemptionDelegate3_1_1.sol"; import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBTokens.sol"; import {JBDelegateMetadataHelper} from "@jbx-protocol/juice-delegate-metadata-lib/src/JBDelegateMetadataHelper.sol"; -import "@paulrberg/contracts/math/PRBMath.sol"; +import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import "forge-std/Test.sol"; +import "./helpers/PoolAddress.sol"; import "../JBBuybackDelegate.sol"; +import "../libraries/JBBuybackDelegateOperations.sol"; /** * @notice Unit tests for the JBBuybackDelegate contract. @@ -25,27 +27,36 @@ import "../JBBuybackDelegate.sol"; contract TestJBBuybackDelegate_Units is Test { using stdStorage for StdStorage; - ForTest_BuybackDelegate delegate; + ForTest_JBBuybackDelegate delegate; - event BuybackDelegate_Swap(uint256 projectId, uint256 amountEth, uint256 amountOut); - event BuybackDelegate_Mint(uint256 projectId); - event BuybackDelegate_SecondsAgoIncrease(uint256 oldSecondsAgo, uint256 newSecondsAgo); - event BuybackDelegate_TwapDeltaChanged(uint256 oldTwapDelta, uint256 newTwapDelta); - event BuybackDelegate_PendingSweep(address indexed beneficiary, uint256 amount); + event BuybackDelegate_Swap(uint256 indexed projectId, uint256 amountIn, IUniswapV3Pool pool, uint256 amountOut, address caller); + event BuybackDelegate_Mint(uint256 indexed projectId, uint256 amount, 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_PoolAdded(uint256 indexed projectId, address indexed terminalToken, address newPool, address caller); // Use the L1 UniswapV3Pool jbx/eth 1% fee for create2 magic IUniswapV3Pool pool = IUniswapV3Pool(0x48598Ff1Cee7b4d31f8f9050C2bbAE98e17E6b17); IERC20 projectToken = IERC20(0x3abF2A4f8452cCC2CF7b4C1e4663147600646f66); IWETH9 weth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); - address _uniswapFactory = 0x1F98431c8aD98523631AE4a59f267346ea31F984; uint24 fee = 10000; + // A random non-weth pool: The PulseDogecoin Staking Carnival Token/HEX @ 0.3% + IERC20 otherRandomProjectToken = IERC20(0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39); + IERC20 randomTerminalToken = IERC20(0x488Db574C77dd27A07f9C97BAc673BC8E9fC6Bf3); + IUniswapV3Pool randomPool = IUniswapV3Pool(0x7668B2Ea8490955F68F5c33E77FE150066c94fb9); + uint24 randomFee = 3000; + uint256 randomId = 420; + + address _uniswapFactory = 0x1F98431c8aD98523631AE4a59f267346ea31F984; + IJBPayoutRedemptionPaymentTerminal3_1_1 jbxTerminal = IJBPayoutRedemptionPaymentTerminal3_1_1(makeAddr("IJBPayoutRedemptionPaymentTerminal3_1")); IJBProjects projects = IJBProjects(makeAddr("IJBProjects")); IJBOperatorStore operatorStore = IJBOperatorStore(makeAddr("IJBOperatorStore")); IJBController3_1 controller = IJBController3_1(makeAddr("controller")); IJBDirectory directory = IJBDirectory(makeAddr("directory")); + IJBTokenStore tokenStore = IJBTokenStore(makeAddr("tokenStore")); JBDelegateMetadataHelper metadataHelper = new JBDelegateMetadataHelper(); @@ -57,11 +68,13 @@ contract TestJBBuybackDelegate_Units is Test { uint32 secondsAgo = 100; uint256 twapDelta = 100; + uint256 projectId = 69; + JBPayParamsData payParams = JBPayParamsData({ terminal: jbxTerminal, payer: dude, amount: JBTokenAmount({token: address(weth), value: 1 ether, decimals: 18, currency: 1}), - projectId: 69, + projectId: projectId, currentFundingCycleConfiguration: 0, beneficiary: dude, weight: 69, @@ -72,10 +85,10 @@ contract TestJBBuybackDelegate_Units is Test { JBDidPayData3_1_1 didPayData = JBDidPayData3_1_1({ payer: dude, - projectId: 69, + projectId: projectId, currentFundingCycleConfiguration: 0, - amount: JBTokenAmount({token: address(weth), value: 1 ether, decimals: 18, currency: 1}), - forwardedAmount: JBTokenAmount({token: address(weth), value: 1 ether, decimals: 18, currency: 1}), + amount: JBTokenAmount({token: JBTokens.ETH, value: 1 ether, decimals: 18, currency: 1}), + forwardedAmount: JBTokenAmount({token: JBTokens.ETH, value: 1 ether, decimals: 18, currency: 1}), projectTokenCount: 69, beneficiary: dude, preferClaimedTokens: true, @@ -99,19 +112,28 @@ contract TestJBBuybackDelegate_Units is Test { vm.label(address(weth), "weth"); vm.mockCall(address(jbxTerminal), abi.encodeCall(jbxTerminal.store, ()), abi.encode(terminalStore)); + vm.mockCall(address(controller), abi.encodeCall(IJBOperatable.operatorStore, ()), abi.encode(operatorStore)); + vm.mockCall(address(controller), abi.encodeCall(controller.projects, ()), abi.encode(projects)); + + vm.mockCall(address(projects), abi.encodeCall(projects.ownerOf, (projectId)), abi.encode(owner)); + + vm.mockCall(address(jbxTerminal), abi.encodeCall(IJBSingleTokenPaymentTerminal.token, ()), abi.encode(weth)); + + vm.mockCall(address(controller), abi.encodeCall(controller.tokenStore, ()), abi.encode(tokenStore)); vm.prank(owner); - delegate = new ForTest_BuybackDelegate({ - _projectToken: projectToken, - _weth: weth, - _factory: _uniswapFactory, - _fee: fee, // 1 % fee - _secondsAgo: secondsAgo, - _twapDelta: twapDelta, - _directory: directory, - _controller: controller, - _id: bytes4(hex'69') - }); + delegate = new ForTest_JBBuybackDelegate({ + _weth: weth, + _factory: _uniswapFactory, + _directory: directory, + _controller: controller, + _id: bytes4(hex'69') + }); + + delegate.ForTest_initPool(pool, projectId, secondsAgo, twapDelta, address(projectToken), address(weth)); + delegate.ForTest_initPool( + randomPool, randomId, secondsAgo, twapDelta, address(otherRandomProjectToken), address(randomTerminalToken) + ); } /** @@ -119,18 +141,24 @@ contract TestJBBuybackDelegate_Units is Test { * * @dev _tokenCount == weight, as we use a value of 1. */ - function test_payParams_callWithQuote(uint256 _tokenCount, uint256 _swapOutCount, uint256 _slippage) public { - // Avoid overflow when computing slippage (cannot swap uint256.max tokens) - _swapOutCount = bound(_swapOutCount, 1, type(uint240).max); + function test_payParams_callWithQuote(uint256 _weight, uint256 _swapOutCount, uint256 _amountIn, uint256 _decimals) public { + // Avoid accidentally using the twap (triggered if out == 0) + _swapOutCount = bound(_swapOutCount, 1, type(uint256).max); - _slippage = bound(_slippage, 1, 10000); + // Avoid mulDiv overflow + _weight = bound(_weight, 1, 1 ether); - // Take max slippage into account - uint256 _swapQuote = _swapOutCount - ((_swapOutCount * _slippage) / 10000); + // Use between 1 wei and the whole amount from pay(..) + _amountIn = bound(_amountIn, 1, payParams.amount.value); + + // The terminal token decimals + _decimals = bound(_decimals, 1, 18); + + uint256 _tokenCount = mulDiv(_amountIn, _weight, 10**_decimals); // Pass the quote as metadata bytes[] memory _data = new bytes[](1); - _data[0] = abi.encode(_swapOutCount, _slippage); + _data[0] = abi.encode(_swapOutCount, _amountIn); // Pass the delegate id bytes4[] memory _ids = new bytes4[](1); @@ -140,8 +168,9 @@ contract TestJBBuybackDelegate_Units is Test { bytes memory _metadata = metadataHelper.createMetadata(_ids, _data); // Set the relevant payParams data - payParams.weight = _tokenCount; + payParams.weight = _weight; payParams.metadata = _metadata; + payParams.amount = JBTokenAmount({token: address(weth), value: 1 ether, decimals: _decimals, currency: 1}); // Returned values to catch: JBPayDelegateAllocation3_1_1[] memory _allocationsReturned; @@ -153,25 +182,33 @@ contract TestJBBuybackDelegate_Units is Test { (_weightReturned, _memoReturned, _allocationsReturned) = delegate.payParams(payParams); // Mint pathway if more token received when minting: - if (_tokenCount >= _swapQuote) { + if (_tokenCount >= _swapOutCount) { // No delegate allocation returned - assertEq(_allocationsReturned.length, 0); + assertEq(_allocationsReturned.length, 0, "Wrong allocation length"); // weight unchanged - assertEq(_weightReturned, _tokenCount); + assertEq(_weightReturned, _weight, "Weight isn't unchanged"); } // Swap pathway (return the delegate allocation) else { - assertEq(_allocationsReturned.length, 1); - assertEq(address(_allocationsReturned[0].delegate), address(delegate)); - assertEq(_allocationsReturned[0].amount, 1 ether); - assertEq(_allocationsReturned[0].metadata, abi.encode(_tokenCount, _swapQuote)); - - assertEq(_weightReturned, 0); + assertEq(_allocationsReturned.length, 1, "Wrong allocation length"); + assertEq(address(_allocationsReturned[0].delegate), address(delegate), "wrong delegate address returned"); + assertEq(_allocationsReturned[0].amount, _amountIn, "worng amount in returned"); + assertEq( + _allocationsReturned[0].metadata, + abi.encode(true, address(projectToken) < address(weth), _swapOutCount, payParams.amount.value - _amountIn, payParams.weight), + "wrong metadata" + ); + + assertEq( + _weightReturned, + 0, + "wrong weight returned (if swapping)" + ); } // Same memo in any case - assertEq(_memoReturned, payParams.memo); + assertEq(_memoReturned, payParams.memo, "wrong memo"); } /** @@ -218,7 +255,7 @@ contract TestJBBuybackDelegate_Units is Test { (_weightReturned, _memoReturned, _allocationsReturned) = delegate.payParams(payParams); // Bypass testing uniswap oracle lib - uint256 _twapAmountOut = delegate.ForTest_getQuote(1 ether); + uint256 _twapAmountOut = delegate.ForTest_getQuote(projectId, address(projectToken), 1 ether, address(weth)); // Mint pathway if more token received when minting: if (_tokenCount >= _twapAmountOut) { @@ -233,7 +270,14 @@ contract TestJBBuybackDelegate_Units is Test { assertEq(_allocationsReturned.length, 1); assertEq(address(_allocationsReturned[0].delegate), address(delegate)); assertEq(_allocationsReturned[0].amount, 1 ether); - assertEq(_allocationsReturned[0].metadata, abi.encode(_tokenCount, _twapAmountOut)); + + assertEq( + _allocationsReturned[0].metadata, + abi.encode( + false, address(projectToken) < address(weth), _twapAmountOut, 0, payParams.weight + ), + "wrong metadata" + ); assertEq(_weightReturned, 0); } @@ -276,20 +320,63 @@ contract TestJBBuybackDelegate_Units is Test { } /** - * @notice Test didPay with token received from swapping + * @notice Test payParams when an amount to swap with greather than the token send is passed + */ + function test_payParams_RevertIfTryingToOverspend(uint256 _swapOutCount, uint256 _amountIn) public { + // Use anything more than the amount sent + _amountIn = bound(_amountIn, payParams.amount.value + 1, type(uint128).max); + + uint256 _weight = 1 ether; + + uint256 _tokenCount = mulDiv(_amountIn, _weight, 10**18); + + // Avoid accidentally using the twap (triggered if out == 0) + _swapOutCount = bound(_swapOutCount, _tokenCount + 1, type(uint256).max); + + // Pass the quote as metadata + bytes[] memory _data = new bytes[](1); + _data[0] = abi.encode(_swapOutCount, _amountIn); + + // Pass the delegate id + bytes4[] memory _ids = new bytes4[](1); + _ids[0] = bytes4(hex"69"); + + // Generate the metadata + bytes memory _metadata = metadataHelper.createMetadata(_ids, _data); + + // Set the relevant payParams data + payParams.weight = _weight; + payParams.metadata = _metadata; + + // Returned values to catch: + JBPayDelegateAllocation3_1_1[] memory _allocationsReturned; + string memory _memoReturned; + uint256 _weightReturned; + + vm.expectRevert(IJBBuybackDelegate.JuiceBuyback_InsufficientPayAmount.selector); + + // Test: call payParams + vm.prank(terminalStore); + (_weightReturned, _memoReturned, _allocationsReturned) = delegate.payParams(payParams); + } + + /** + * @notice Test didPay with token received from swapping, within slippage and no leftover in the delegate */ - function test_didPay_swap(uint256 _tokenCount, uint256 _twapQuote, uint256 _reservedRate) public { + function test_didPay_swap_ETH(uint256 _tokenCount, uint256 _twapQuote) public { // Bound to avoid overflow and insure swap quote > mint quote _tokenCount = bound(_tokenCount, 2, type(uint256).max - 1); _twapQuote = bound(_twapQuote, _tokenCount + 1, type(uint256).max); - _reservedRate = bound(_reservedRate, 0, 10000); // The metadata coming from payParams(..) - didPayData.dataSourceMetadata = abi.encode(_tokenCount, _twapQuote); + didPayData.dataSourceMetadata = abi.encode( + true, // use quote + address(projectToken) < address(weth), + _tokenCount, + 0, + _twapQuote + ); - // The amount the beneficiary should receive - uint256 _nonReservedToken = - PRBMath.mulDiv(_twapQuote, JBConstants.MAX_RESERVED_RATE - _reservedRate, JBConstants.MAX_RESERVED_RATE); // mock the swap call vm.mockCall( @@ -301,23 +388,23 @@ contract TestJBBuybackDelegate_Units is Test { address(weth) < address(projectToken), int256(1 ether), address(projectToken) < address(weth) ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1, - abi.encode(_twapQuote) + abi.encode(projectId, JBTokens.ETH) ) ), abi.encode(-int256(_twapQuote), -int256(_twapQuote)) ); - - // mock the transfer call - vm.mockCall( - address(projectToken), abi.encodeCall(projectToken.transfer, (dude, _nonReservedToken)), abi.encode(true) - ); - - // mock the call to the directory, to get the controller - vm.mockCall(address(jbxTerminal), abi.encodeCall(jbxTerminal.directory, ()), abi.encode(address(directory))); - vm.mockCall( - address(directory), - abi.encodeCall(directory.controllerOf, (didPayData.projectId)), - abi.encode(address(controller)) + vm.expectCall( + address(pool), + abi.encodeCall( + pool.swap, + ( + address(delegate), + address(weth) < address(projectToken), + int256(1 ether), + address(projectToken) < address(weth) ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1, + abi.encode(projectId, JBTokens.ETH) + ) + ) ); // mock call to pass the authorization check @@ -326,6 +413,10 @@ contract TestJBBuybackDelegate_Units is Test { abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))), abi.encode(true) ); + vm.expectCall( + address(directory), + abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))) + ); // mock the burn call vm.mockCall( @@ -333,6 +424,10 @@ contract TestJBBuybackDelegate_Units is Test { abi.encodeCall(controller.burnTokensOf, (address(delegate), didPayData.projectId, _twapQuote, "", true)), abi.encode(true) ); + vm.expectCall( + address(controller), + abi.encodeCall(controller.burnTokensOf, (address(delegate), didPayData.projectId, _twapQuote, "", true)) + ); // mock the minting call vm.mockCall( @@ -342,24 +437,38 @@ contract TestJBBuybackDelegate_Units is Test { ), abi.encode(true) ); + vm.expectCall( + address(controller), + abi.encodeCall( + controller.mintTokensOf, (didPayData.projectId, _twapQuote, address(dude), didPayData.memo, true, true) + ) + ); // expect event vm.expectEmit(true, true, true, true); - emit BuybackDelegate_Swap(didPayData.projectId, didPayData.amount.value, _twapQuote); + emit BuybackDelegate_Swap(didPayData.projectId, didPayData.amount.value, pool, _twapQuote, address(jbxTerminal)); vm.prank(address(jbxTerminal)); delegate.didPay(didPayData); } /** - * @notice Test didPay when eth leftover from swap + * @notice Test didPay with token received from swapping, within slippage and no leftover in the delegate */ - function test_didPay_keepTrackOfETHToSweep() public { - uint256 _tokenCount = 10; - uint256 _twapQuote = 11; + function test_didPay_swap_ETH_with_extrafunds(uint256 _tokenCount, uint256 _twapQuote) public { + // Bound to avoid overflow and insure swap quote > mint quote + _tokenCount = bound(_tokenCount, 2, type(uint256).max - 1); + _twapQuote = bound(_twapQuote, _tokenCount + 1, type(uint256).max); // The metadata coming from payParams(..) - didPayData.dataSourceMetadata = abi.encode(_tokenCount, _twapQuote); + didPayData.dataSourceMetadata = abi.encode( + true, // use quote + address(projectToken) < address(weth), + _twapQuote, + 0, + _tokenCount + ); + // mock the swap call vm.mockCall( @@ -371,32 +480,126 @@ contract TestJBBuybackDelegate_Units is Test { address(weth) < address(projectToken), int256(1 ether), address(projectToken) < address(weth) ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1, - abi.encode(_twapQuote) + abi.encode(projectId, JBTokens.ETH) ) ), abi.encode(-int256(_twapQuote), -int256(_twapQuote)) ); + vm.expectCall( + address(pool), + abi.encodeCall( + pool.swap, + ( + address(delegate), + address(weth) < address(projectToken), + int256(1 ether), + address(projectToken) < address(weth) ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1, + abi.encode(projectId, JBTokens.ETH) + ) + ) + ); + + // mock call to pass the authorization check + vm.mockCall( + address(directory), + abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))), + abi.encode(true) + ); + vm.expectCall( + address(directory), + abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))) + ); + + // mock the burn call + vm.mockCall( + address(controller), + abi.encodeCall(controller.burnTokensOf, (address(delegate), didPayData.projectId, _twapQuote, "", true)), + abi.encode(true) + ); + vm.expectCall( + address(controller), + abi.encodeCall(controller.burnTokensOf, (address(delegate), didPayData.projectId, _twapQuote, "", true)) + ); - // Mock the project token transfer - vm.mockCall(address(projectToken), abi.encodeCall(projectToken.transfer, (dude, _twapQuote)), abi.encode(true)); + // mock the minting call + vm.mockCall( + address(controller), + abi.encodeCall( + controller.mintTokensOf, (didPayData.projectId, _twapQuote, address(dude), didPayData.memo, true, true) + ), + abi.encode(true) + ); + vm.expectCall( + address(controller), + abi.encodeCall( + controller.mintTokensOf, (didPayData.projectId, _twapQuote, address(dude), didPayData.memo, true, true) + ) + ); + + // expect event + vm.expectEmit(true, true, true, true); + emit BuybackDelegate_Swap(didPayData.projectId, didPayData.amount.value, pool, _twapQuote, address(jbxTerminal)); + + vm.prank(address(jbxTerminal)); + delegate.didPay(didPayData); + } + + /** + * @notice Test didPay with token received from swapping + */ + function test_didPay_swap_ERC20(uint256 _tokenCount, uint256 _twapQuote, uint256 _decimals ) public { + // Bound to avoid overflow and insure swap quote > mint quote + _tokenCount = bound(_tokenCount, 2, type(uint256).max - 1); + _twapQuote = bound(_twapQuote, _tokenCount + 1, type(uint256).max); - // Add some leftover (nothing will be wrapped/transfered as it happens in the callback) - vm.deal(address(delegate), 10 ether); + _decimals = bound(_decimals, 1, 18); - // Add a previous leftover, to test the incremental accounting (ie 5 out of 10 were there) - stdstore.target(address(delegate)).sig("sweepBalance()").checked_write(5 ether); + didPayData.amount = + JBTokenAmount({token: address(randomTerminalToken), value: 1 ether, decimals: _decimals, currency: 1}); + didPayData.forwardedAmount = + JBTokenAmount({token: address(randomTerminalToken), value: 1 ether, decimals: _decimals, currency: 1}); + didPayData.projectId = randomId; - // Out of these 5, 1 was for payer - stdstore.target(address(delegate)).sig("sweepBalanceOf(address)").with_key(didPayData.payer).checked_write( - 1 ether + // The metadata coming from payParams(..) + didPayData.dataSourceMetadata = abi.encode( + true, // use quote + address(projectToken) < address(weth), + _tokenCount, + 0, + _twapQuote ); - // mock the call to the directory, to get the controller - vm.mockCall(address(jbxTerminal), abi.encodeCall(jbxTerminal.directory, ()), abi.encode(address(directory))); + // mock the swap call vm.mockCall( - address(directory), - abi.encodeCall(directory.controllerOf, (didPayData.projectId)), - abi.encode(address(controller)) + address(randomPool), + abi.encodeCall( + randomPool.swap, + ( + address(delegate), + address(randomTerminalToken) < address(otherRandomProjectToken), + int256(1 ether), + address(otherRandomProjectToken) < address(randomTerminalToken) + ? TickMath.MAX_SQRT_RATIO - 1 + : TickMath.MIN_SQRT_RATIO + 1, + abi.encode(randomId, randomTerminalToken) + ) + ), + abi.encode(-int256(_twapQuote), -int256(_twapQuote)) + ); + vm.expectCall( + address(randomPool), + abi.encodeCall( + randomPool.swap, + ( + address(delegate), + address(randomTerminalToken) < address(otherRandomProjectToken), + int256(1 ether), + address(otherRandomProjectToken) < address(randomTerminalToken) + ? TickMath.MAX_SQRT_RATIO - 1 + : TickMath.MIN_SQRT_RATIO + 1, + abi.encode(randomId, randomTerminalToken) + ) + ) ); // mock call to pass the authorization check @@ -405,6 +608,10 @@ contract TestJBBuybackDelegate_Units is Test { abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))), abi.encode(true) ); + vm.expectCall( + address(directory), + abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))) + ); // mock the burn call vm.mockCall( @@ -412,6 +619,10 @@ contract TestJBBuybackDelegate_Units is Test { abi.encodeCall(controller.burnTokensOf, (address(delegate), didPayData.projectId, _twapQuote, "", true)), abi.encode(true) ); + vm.expectCall( + address(controller), + abi.encodeCall(controller.burnTokensOf, (address(delegate), didPayData.projectId, _twapQuote, "", true)) + ); // mock the minting call vm.mockCall( @@ -421,31 +632,43 @@ contract TestJBBuybackDelegate_Units is Test { ), abi.encode(true) ); + vm.expectCall( + address(controller), + abi.encodeCall( + controller.mintTokensOf, (didPayData.projectId, _twapQuote, address(dude), didPayData.memo, true, true) + ) + ); - // check: correct event? + // No leftover + vm.mockCall( + address(randomTerminalToken), + abi.encodeCall(randomTerminalToken.balanceOf, (address(delegate))), + abi.encode(0) + ); + vm.expectCall(address(randomTerminalToken), abi.encodeCall(randomTerminalToken.balanceOf, (address(delegate)))); + + // expect event vm.expectEmit(true, true, true, true); - emit BuybackDelegate_PendingSweep(dude, 5 ether); + emit BuybackDelegate_Swap(didPayData.projectId, didPayData.amount.value, randomPool, _twapQuote, address(jbxTerminal)); vm.prank(address(jbxTerminal)); delegate.didPay(didPayData); - - // Check: correct overall sweep balance? - assertEq(delegate.sweepBalance(), 10 ether); - - // Check: correct dude sweep balance (1 previous plus 5 from now)? - assertEq(delegate.sweepBalanceOf(dude), 6 ether); } /** - * @notice Test didPay with swap reverting, should then mint + * @notice Test didPay with swap reverting / returning 0, while a non-0 quote was provided */ - - function test_didPay_swapRevert(uint256 _tokenCount, uint256 _twapQuote) public { - _tokenCount = bound(_tokenCount, 2, type(uint256).max - 1); - _twapQuote = bound(_twapQuote, _tokenCount + 1, type(uint256).max); + function test_didPay_swapRevertWithQuote(uint256 _tokenCount) public { + _tokenCount = bound(_tokenCount, 1, type(uint256).max - 1); // The metadata coming from payParams(..) - didPayData.dataSourceMetadata = abi.encode(_tokenCount, _twapQuote); + didPayData.dataSourceMetadata = abi.encode( + true, // use quote + address(projectToken) < address(weth), + _tokenCount, + 0, + 1 ether // weight - unused + ); // mock the swap call reverting vm.mockCallRevert( @@ -457,18 +680,203 @@ contract TestJBBuybackDelegate_Units is Test { address(weth) < address(projectToken), int256(1 ether), address(projectToken) < address(weth) ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1, - abi.encode(_twapQuote) + abi.encode(projectId, weth) ) ), abi.encode("no swap") ); - // mock the call to the directory, to get the controller - vm.mockCall(address(jbxTerminal), abi.encodeCall(jbxTerminal.directory, ()), abi.encode(address(directory))); + // mock call to pass the authorization check vm.mockCall( address(directory), - abi.encodeCall(directory.controllerOf, (didPayData.projectId)), - abi.encode(address(controller)) + abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))), + abi.encode(true) + ); + vm.expectCall( + address(directory), + abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))) + ); + + vm.expectRevert(IJBBuybackDelegate.JuiceBuyback_MaximumSlippage.selector); + + vm.prank(address(jbxTerminal)); + delegate.didPay(didPayData); + } + + /** + * @notice Test didPay with swap reverting while using the twap, should then mint with the delegate balance, random erc20 is terminal token + */ + function test_didPay_swapRevertWithoutQuote_ERC20(uint256 _tokenCount, uint256 _weight, uint256 _decimals, uint256 _extraMint) public { + // The current weight + _weight = bound(_weight, 1, 1 ether); + + // The amount of termminal token in this delegate (avoid overflowing when mul by weight) + _tokenCount = bound(_tokenCount, 2, type(uint128).max); + + // An extra amount of token to mint, based on fund which stayed in the terminal + _extraMint = bound(_extraMint, 2, type(uint128).max); + + // The terminal token decimal + _decimals = bound(_decimals, 1, 18); + + didPayData.amount = + JBTokenAmount({token: address(randomTerminalToken), value: _tokenCount, decimals: _decimals, currency: 1}); + didPayData.forwardedAmount = + JBTokenAmount({token: address(randomTerminalToken), value: _tokenCount, decimals: _decimals, currency: 1}); + didPayData.projectId = randomId; + + vm.mockCall( + address(jbxTerminal), + abi.encodeCall(IJBSingleTokenPaymentTerminal.token, ()), + abi.encode(randomTerminalToken) + ); + + // The metadata coming from payParams(..) + didPayData.dataSourceMetadata = abi.encode( + false, // use quote + address(otherRandomProjectToken) < address(randomTerminalToken), + _tokenCount, + _extraMint, // extra amount to mint with + _weight + ); + + // mock the swap call reverting + vm.mockCallRevert( + address(randomPool), + abi.encodeCall( + randomPool.swap, + ( + address(delegate), + address(randomTerminalToken) < address(otherRandomProjectToken), + int256(_tokenCount), + address(otherRandomProjectToken) < address(randomTerminalToken) + ? TickMath.MAX_SQRT_RATIO - 1 + : TickMath.MIN_SQRT_RATIO + 1, + abi.encode(randomId, randomTerminalToken) + ) + ), + abi.encode("no swap") + ); + + // mock call to pass the authorization check + vm.mockCall( + address(directory), + abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))), + abi.encode(true) + ); + vm.expectCall( + address(directory), + abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))) + ); + + // Mock the balance check + vm.mockCall( + address(randomTerminalToken), + abi.encodeCall(randomTerminalToken.balanceOf, (address(delegate))), + abi.encode(_tokenCount) + ); + vm.expectCall(address(randomTerminalToken), abi.encodeCall(randomTerminalToken.balanceOf, (address(delegate)))); + + + // mock the minting call - this uses the weight and not the (potentially faulty) quote or twap + vm.mockCall( + address(controller), + abi.encodeCall( + controller.mintTokensOf, + (didPayData.projectId, mulDiv(_tokenCount, _weight, 10**_decimals) + mulDiv(_extraMint, _weight, 10**_decimals), didPayData.beneficiary, didPayData.memo, didPayData.preferClaimedTokens, true) + ), + abi.encode(true) + ); + vm.expectCall( + address(controller), + abi.encodeCall( + controller.mintTokensOf, + (didPayData.projectId, mulDiv(_tokenCount, _weight, 10**_decimals) + mulDiv(_extraMint, _weight, 10**_decimals), didPayData.beneficiary, didPayData.memo, didPayData.preferClaimedTokens, true) + ) + ); + + // Mock the approval for the addToBalance + vm.mockCall( + address(randomTerminalToken), + abi.encodeCall(randomTerminalToken.approve, (address(jbxTerminal), _tokenCount)), + abi.encode(true) + ); + vm.expectCall( + address(randomTerminalToken), abi.encodeCall(randomTerminalToken.approve, (address(jbxTerminal), _tokenCount)) + ); + + // mock the add to balance adding the terminal token back to the terminal + vm.mockCall( + address(jbxTerminal), + abi.encodeCall( + IJBPaymentTerminal(address(jbxTerminal)).addToBalanceOf, + (didPayData.projectId, _tokenCount, address(randomTerminalToken), "", "") + ), + "" + ); + vm.expectCall( + address(jbxTerminal), + abi.encodeCall( + IJBPaymentTerminal(address(jbxTerminal)).addToBalanceOf, + (didPayData.projectId, _tokenCount, address(randomTerminalToken), "", "") + ) + ); + + // expect event - only for the non-extra mint + vm.expectEmit(true, true, true, true); + emit BuybackDelegate_Mint(didPayData.projectId, _tokenCount, mulDiv(_tokenCount, _weight, 10**_decimals), address(jbxTerminal)); + + vm.prank(address(jbxTerminal)); + delegate.didPay(didPayData); + } + + /** + * @notice Test didPay with swap reverting while using the twap, should then mint with the delegate balance, random erc20 is terminal token + */ + function test_didPay_swapRevertWithoutQuote_ETH(uint256 _tokenCount, uint256 _weight, uint256 _decimals, uint256 _extraMint) public { + // The current weight + _weight = bound(_weight, 1, 1 ether); + + // The amount of termminal token in this delegate (avoid overflowing when mul by weight) + _tokenCount = bound(_tokenCount, 2, type(uint128).max); + + // An extra amount of token to mint, based on fund which stayed in the terminal + _extraMint = bound(_extraMint, 2, type(uint128).max); + + // The terminal token decimal + _decimals = bound(_decimals, 1, 18); + + didPayData.amount = + JBTokenAmount({token: JBTokens.ETH, value: _tokenCount, decimals: _decimals, currency: 1}); + + didPayData.forwardedAmount = + JBTokenAmount({token: JBTokens.ETH, value: _tokenCount, decimals: _decimals, currency: 1}); + + // The metadata coming from payParams(..) + didPayData.dataSourceMetadata = abi.encode( + false, // use quote + address(projectToken) < address(weth), + _tokenCount, + _extraMint, + _weight + ); + + // mock the swap call reverting + vm.mockCallRevert( + address(pool), + abi.encodeCall( + pool.swap, + ( + address(delegate), + address(weth) < address(projectToken), + int256(_tokenCount), + address(projectToken) < address(weth) + ? TickMath.MAX_SQRT_RATIO - 1 + : TickMath.MIN_SQRT_RATIO + 1, + abi.encode(projectId, weth) + ) + ), + abi.encode("no swap") ); // mock call to pass the authorization check @@ -477,31 +885,53 @@ contract TestJBBuybackDelegate_Units is Test { abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))), abi.encode(true) ); + vm.expectCall( + address(directory), + abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))) + ); + + // Mock the balance check + vm.deal(address(delegate), _tokenCount); // mock the minting call - this uses the weight and not the (potentially faulty) quote or twap vm.mockCall( address(controller), abi.encodeCall( controller.mintTokensOf, - (didPayData.projectId, _tokenCount, dude, didPayData.memo, didPayData.preferClaimedTokens, true) + (didPayData.projectId, mulDiv(_tokenCount, _weight, 10**_decimals) + mulDiv(_extraMint, _weight, 10**_decimals), didPayData.beneficiary, didPayData.memo, didPayData.preferClaimedTokens, true) ), abi.encode(true) ); + vm.expectCall( + address(controller), + abi.encodeCall( + controller.mintTokensOf, + (didPayData.projectId, mulDiv(_tokenCount, _weight, 10**_decimals) + mulDiv(_extraMint, _weight, 10**_decimals), didPayData.beneficiary, didPayData.memo, didPayData.preferClaimedTokens, true) + ) + ); - // mock the add to balance addint eth back to the terminal (need to deal eth as this transfer really occurs in test) - vm.deal(address(delegate), 1 ether); + // mock the add to balance adding the terminal token back to the terminal vm.mockCall( address(jbxTerminal), + _tokenCount, abi.encodeCall( IJBPaymentTerminal(address(jbxTerminal)).addToBalanceOf, - (didPayData.projectId, 1 ether, JBTokens.ETH, "", "") + (didPayData.projectId, _tokenCount, JBTokens.ETH, "", "") ), "" ); + vm.expectCall( + address(jbxTerminal), + _tokenCount, + abi.encodeCall( + IJBPaymentTerminal(address(jbxTerminal)).addToBalanceOf, + (didPayData.projectId, _tokenCount, JBTokens.ETH, "", "") + ) + ); // expect event vm.expectEmit(true, true, true, true); - emit BuybackDelegate_Mint(didPayData.projectId); + emit BuybackDelegate_Mint(didPayData.projectId, _tokenCount, mulDiv(_tokenCount, _weight, 10**_decimals), address(jbxTerminal)); vm.prank(address(jbxTerminal)); delegate.didPay(didPayData); @@ -519,8 +949,12 @@ contract TestJBBuybackDelegate_Units is Test { abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(_notTerminal)))), abi.encode(false) ); + vm.expectCall( + address(directory), + abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(_notTerminal)))) + ); - vm.expectRevert(abi.encodeWithSelector(JBBuybackDelegate.JuiceBuyback_Unauthorized.selector)); + vm.expectRevert(abi.encodeWithSelector(IJBBuybackDelegate.JuiceBuyback_Unauthorized.selector)); vm.prank(_notTerminal); delegate.didPay(didPayData); @@ -532,74 +966,102 @@ contract TestJBBuybackDelegate_Units is Test { * @dev 2 branches: project token is 0 or 1 in the pool slot0 */ function test_uniswapCallback() public { - int256 _delta0 = -1 ether; + int256 _delta0 = -2 ether; int256 _delta1 = 1 ether; - uint256 _minReceived = 25; + + IWETH9 _terminalToken = weth; + IERC20 _projectToken = projectToken; /** - * First branch + * First branch: terminal token = ETH, project token = random IERC20 */ - delegate = new ForTest_BuybackDelegate({ - _projectToken: projectToken, - _weth: weth, - _factory: _uniswapFactory, - _fee: fee, - _secondsAgo: secondsAgo, - _twapDelta: twapDelta, - _directory: directory, - _controller: controller, - _id: bytes4(hex'69') - }); + delegate = new ForTest_JBBuybackDelegate({ + _weth: _terminalToken, + _factory: _uniswapFactory, + _directory: directory, + _controller: controller, + _id: bytes4(hex'69') + }); + + // Init with weth (as weth is stored in the pool of mapping) + delegate.ForTest_initPool( + pool, projectId, secondsAgo, twapDelta, address(_projectToken), address(_terminalToken) + ); // If project is token0, then received is delta0 (the negative value) - (_delta0, _delta1) = address(projectToken) < address(weth) ? (_delta0, _delta1) : (_delta1, _delta0); + (_delta0, _delta1) = address(_projectToken) < address(_terminalToken) ? (_delta0, _delta1) : (_delta1, _delta0); - // mock and expect weth calls, this should transfer from delegate to pool (positive delta in the callback) - vm.mockCall(address(weth), abi.encodeCall(weth.deposit, ()), ""); + // mock and expect _terminalToken calls, this should transfer from delegate to pool (positive delta in the callback) + vm.mockCall(address(_terminalToken), abi.encodeCall(_terminalToken.deposit, ()), ""); + vm.expectCall(address(_terminalToken), abi.encodeCall(_terminalToken.deposit, ())); vm.mockCall( - address(weth), + address(_terminalToken), abi.encodeCall( - weth.transfer, (address(pool), uint256(address(projectToken) < address(weth) ? _delta1 : _delta0)) + _terminalToken.transfer, + (address(pool), uint256(address(_projectToken) < address(_terminalToken) ? _delta1 : _delta0)) ), abi.encode(true) ); + vm.expectCall( + address(_terminalToken), + abi.encodeCall( + _terminalToken.transfer, + (address(pool), uint256(address(_projectToken) < address(_terminalToken) ? _delta1 : _delta0)) + ) + ); + vm.deal(address(delegate), uint256(address(_projectToken) < address(_terminalToken) ? _delta1 : _delta0)); vm.prank(address(pool)); - delegate.uniswapV3SwapCallback(_delta0, _delta1, abi.encode(_minReceived)); + delegate.uniswapV3SwapCallback( + _delta0, _delta1, abi.encode(projectId, JBTokens.ETH) + ); /** - * Second branch + * Second branch: terminal token = random IERC20, project token = weth (as another random ierc20) */ - // Invert both contract addresses, to swap token0 and token1 (this will NOT modify the pool address) - (projectToken, weth) = (JBToken(address(weth)), IWETH9(address(projectToken))); - - delegate = new ForTest_BuybackDelegate({ - _projectToken: projectToken, - _weth: weth, - _factory: _uniswapFactory, - _fee: fee, - _secondsAgo: secondsAgo, - _twapDelta: twapDelta, - _directory: directory, - _controller: controller, - _id: bytes4(hex'69') - }); + // Invert both contract addresses, to swap token0 and token1 + (_projectToken, _terminalToken) = (JBToken(address(_terminalToken)), IWETH9(address(_projectToken))); - // mock and expect weth calls, this should transfer from delegate to pool (positive delta in the callback) - vm.mockCall(address(weth), abi.encodeCall(weth.deposit, ()), ""); + // If project is token0, then received is delta0 (the negative value) + (_delta0, _delta1) = address(_projectToken) < address(_terminalToken) ? (_delta0, _delta1) : (_delta1, _delta0); + + delegate = new ForTest_JBBuybackDelegate({ + _weth: _terminalToken, + _factory: _uniswapFactory, + _directory: directory, + _controller: controller, + _id: bytes4(hex'69') + }); + + delegate.ForTest_initPool( + pool, projectId, secondsAgo, twapDelta, address(_projectToken), address(_terminalToken) + ); vm.mockCall( - address(weth), + address(_terminalToken), abi.encodeCall( - weth.transfer, (address(pool), uint256(address(projectToken) < address(weth) ? _delta1 : _delta0)) + _terminalToken.transfer, + (address(pool), uint256(address(_projectToken) < address(_terminalToken) ? _delta1 : _delta0)) ), abi.encode(true) ); + vm.expectCall( + address(_terminalToken), + abi.encodeCall( + _terminalToken.transfer, + (address(pool), uint256(address(_projectToken) < address(_terminalToken) ? _delta1 : _delta0)) + ) + ); + vm.deal(address(delegate), uint256(address(_projectToken) < address(_terminalToken) ? _delta1 : _delta0)); vm.prank(address(pool)); - delegate.uniswapV3SwapCallback(_delta0, _delta1, abi.encode(_minReceived)); + delegate.uniswapV3SwapCallback( + _delta0, + _delta1, + abi.encode(projectId, address(_terminalToken)) + ); } /** @@ -608,204 +1070,444 @@ contract TestJBBuybackDelegate_Units is Test { function test_uniswapCallback_revertIfWrongCaller() public { int256 _delta0 = -1 ether; int256 _delta1 = 1 ether; - uint256 _minReceived = 25; - vm.expectRevert(abi.encodeWithSelector(JBBuybackDelegate.JuiceBuyback_Unauthorized.selector)); - delegate.uniswapV3SwapCallback(_delta0, _delta1, abi.encode(_minReceived)); + vm.expectRevert(abi.encodeWithSelector(IJBBuybackDelegate.JuiceBuyback_Unauthorized.selector)); + delegate.uniswapV3SwapCallback( + _delta0, _delta1, abi.encode(projectId, weth, address(projectToken) < address(weth)) + ); } /** - * @notice Test uniswapCallback revert if max slippage + * @notice Test adding a new pool (deployed or not) */ - function test_uniswapCallback_revertIfMaxSlippage() public { - int256 _delta0 = -1 ether; - int256 _delta1 = 1 ether; - uint256 _minReceived = 25 ether; + function test_setPoolFor( + uint256 _secondsAgo, + uint256 _twapDelta, + address _terminalToken, + address _projectToken, + uint24 _fee + ) public { + vm.assume(_terminalToken != address(0) && _projectToken != address(0) && _fee != 0); + vm.assume(_terminalToken != _projectToken); - // If project is token0, then received is delta0 (the negative value) - (_delta0, _delta1) = address(projectToken) < address(weth) ? (_delta0, _delta1) : (_delta1, _delta0); + uint256 _MIN_TWAP_WINDOW = delegate.MIN_TWAP_WINDOW(); + uint256 _MAX_TWAP_WINDOW = delegate.MAX_TWAP_WINDOW(); - vm.prank(address(pool)); - vm.expectRevert(abi.encodeWithSelector(JBBuybackDelegate.JuiceBuyback_MaximumSlippage.selector)); - delegate.uniswapV3SwapCallback(_delta0, _delta1, abi.encode(_minReceived)); + uint256 _MIN_TWAP_SLIPPAGE_TOLERANCE = delegate.MIN_TWAP_SLIPPAGE_TOLERANCE(); + uint256 _MAX_TWAP_SLIPPAGE_TOLERANCE = delegate.MAX_TWAP_SLIPPAGE_TOLERANCE(); + + _twapDelta = bound(_twapDelta, _MIN_TWAP_SLIPPAGE_TOLERANCE, _MAX_TWAP_SLIPPAGE_TOLERANCE); + _secondsAgo = bound(_secondsAgo, _MIN_TWAP_WINDOW, _MAX_TWAP_WINDOW); + + address _pool = PoolAddress.computeAddress( + delegate.UNISWAP_V3_FACTORY(), PoolAddress.getPoolKey(_terminalToken, _projectToken, _fee) + ); + + vm.mockCall(address(tokenStore), abi.encodeCall(tokenStore.tokenOf, (projectId)), abi.encode(_projectToken)); + + // check: correct events? + vm.expectEmit(true, true, true, true); + emit BuybackDelegate_TwapWindowChanged(projectId, 0, _secondsAgo, owner); + + vm.expectEmit(true, true, true, true); + emit BuybackDelegate_TwapSlippageToleranceChanged(projectId, 0, _twapDelta, owner); + + vm.expectEmit(true, true, true, true); + emit BuybackDelegate_PoolAdded(projectId, _terminalToken == JBTokens.ETH ? address(weth) : _terminalToken, address(_pool), owner); + + vm.prank(owner); + address _newPool = + address(delegate.setPoolFor(projectId, _fee, uint32(_secondsAgo), _twapDelta, _terminalToken)); + + // Check: correct params stored? + assertEq(delegate.twapWindowOf(projectId), _secondsAgo); + assertEq(delegate.twapSlippageToleranceOf(projectId), _twapDelta); + assertEq(address(delegate.poolOf(projectId, _terminalToken == JBTokens.ETH ? address(weth) : _terminalToken)), _pool); + assertEq(_newPool, _pool); } /** - * @notice Test sweep + * @notice Test if trying to add an existing pool revert + * + * @dev This is to avoid bypassing the twap delta and period authorisation. A new fee-tier results in a new pool */ - function test_sweep(uint256 _delegateLeftover, uint256 _dudeLeftover) public { - _dudeLeftover = bound(_dudeLeftover, 0, _delegateLeftover); + function test_setPoolFor_revertIfPoolAlreadyExists( + uint256 _secondsAgo, + uint256 _twapDelta, + address _terminalToken, + address _projectToken, + uint24 _fee + ) public { + vm.assume(_terminalToken != address(0) && _projectToken != address(0) && _fee != 0); + vm.assume(_terminalToken != _projectToken); + + uint256 _MIN_TWAP_WINDOW = delegate.MIN_TWAP_WINDOW(); + uint256 _MAX_TWAP_WINDOW = delegate.MAX_TWAP_WINDOW(); + + uint256 _MIN_TWAP_SLIPPAGE_TOLERANCE = delegate.MIN_TWAP_SLIPPAGE_TOLERANCE(); + uint256 _MAX_TWAP_SLIPPAGE_TOLERANCE = delegate.MAX_TWAP_SLIPPAGE_TOLERANCE(); - // Add the ETH - vm.deal(address(delegate), _delegateLeftover); + _twapDelta = bound(_twapDelta, _MIN_TWAP_SLIPPAGE_TOLERANCE, _MAX_TWAP_SLIPPAGE_TOLERANCE); + _secondsAgo = bound(_secondsAgo, _MIN_TWAP_WINDOW, _MAX_TWAP_WINDOW); + + vm.mockCall(address(tokenStore), abi.encodeCall(tokenStore.tokenOf, (projectId)), abi.encode(_projectToken)); + + vm.prank(owner); + delegate.setPoolFor(projectId, _fee, uint32(_secondsAgo), _twapDelta, _terminalToken); + + vm.expectRevert(IJBBuybackDelegate.JuiceBuyback_PoolAlreadySet.selector); + vm.prank(owner); + delegate.setPoolFor(projectId, _fee, uint32(_secondsAgo), _twapDelta, _terminalToken); + } - // Store the delegate leftover - stdstore.target(address(delegate)).sig("sweepBalance()").checked_write(_delegateLeftover); + /** + * @notice Revert if not called by project owner or authorised sender + */ + function test_setPoolFor_revertIfWrongCaller() public { + vm.mockCall( + address(operatorStore), + abi.encodeCall( + operatorStore.hasPermission, (dude, owner, projectId, JBBuybackDelegateOperations.CHANGE_POOL) + ), + abi.encode(false) + ); + vm.expectCall( + address(operatorStore), + abi.encodeCall( + operatorStore.hasPermission, (dude, owner, projectId, JBBuybackDelegateOperations.CHANGE_POOL) + ) + ); - // Store the dude leftover - stdstore.target(address(delegate)).sig("sweepBalanceOf(address)").with_key(didPayData.payer).checked_write( - _dudeLeftover + vm.mockCall( + address(operatorStore), + abi.encodeCall( + operatorStore.hasPermission, (dude, owner, 0, JBBuybackDelegateOperations.CHANGE_POOL) + ), + abi.encode(false) + ); + vm.expectCall( + address(operatorStore), + abi.encodeCall( + operatorStore.hasPermission, (dude, owner, 0, JBBuybackDelegateOperations.CHANGE_POOL) + ) ); - uint256 _balanceBeforeSweep = dude.balance; + // check: revert? + vm.expectRevert(abi.encodeWithSignature("UNAUTHORIZED()")); - // Test: sweep vm.prank(dude); - delegate.sweep(dude); + delegate.setPoolFor(projectId, 100, uint32(10), 10, address(0)); + } + + /** + * @notice Test if only twap delta and periods between the extrema's are allowed + */ + function test_setPoolFor_revertIfWrongParams( + address _terminalToken, + address _projectToken, + uint24 _fee + ) public { + vm.assume(_terminalToken != address(0) && _projectToken != address(0) && _fee != 0); + vm.assume(_terminalToken != _projectToken); + + uint256 _MIN_TWAP_WINDOW = delegate.MIN_TWAP_WINDOW(); + uint256 _MAX_TWAP_WINDOW = delegate.MAX_TWAP_WINDOW(); - uint256 _balanceAfterSweep = dude.balance; - uint256 _sweptAmount = _balanceAfterSweep - _balanceBeforeSweep; + uint256 _MIN_TWAP_SLIPPAGE_TOLERANCE = delegate.MIN_TWAP_SLIPPAGE_TOLERANCE(); + uint256 _MAX_TWAP_SLIPPAGE_TOLERANCE = delegate.MAX_TWAP_SLIPPAGE_TOLERANCE(); - // Check: correct overall sweep balance? - assertEq(delegate.sweepBalance(), _delegateLeftover - _dudeLeftover); + vm.mockCall(address(tokenStore), abi.encodeCall(tokenStore.tokenOf, (projectId)), abi.encode(_projectToken)); - // Check: correct dude sweep balance - assertEq(delegate.sweepBalanceOf(dude), 0); + // Check: seconds ago too low + vm.expectRevert(IJBBuybackDelegate.JuiceBuyback_InvalidTwapWindow.selector); + vm.prank(owner); + delegate.setPoolFor(projectId, _fee, uint32(_MIN_TWAP_WINDOW - 1), _MIN_TWAP_SLIPPAGE_TOLERANCE + 1, _terminalToken); + + // Check: seconds ago too high + vm.expectRevert(IJBBuybackDelegate.JuiceBuyback_InvalidTwapWindow.selector); + vm.prank(owner); + delegate.setPoolFor(projectId, _fee, uint32(_MAX_TWAP_WINDOW + 1), _MIN_TWAP_SLIPPAGE_TOLERANCE + 1, _terminalToken); - // Check: correct swept balance - assertEq(_sweptAmount, _dudeLeftover); + // Check: min twap deviation too low + vm.expectRevert(IJBBuybackDelegate.JuiceBuyback_InvalidTwapSlippageTolerance.selector); + vm.prank(owner); + delegate.setPoolFor(projectId, _fee, uint32(_MIN_TWAP_WINDOW + 1), _MIN_TWAP_SLIPPAGE_TOLERANCE - 1, _terminalToken); + + // Check: max twap deviation too high + vm.expectRevert(IJBBuybackDelegate.JuiceBuyback_InvalidTwapSlippageTolerance.selector); + vm.prank(owner); + delegate.setPoolFor(projectId, _fee, uint32(_MIN_TWAP_WINDOW + 1), _MAX_TWAP_SLIPPAGE_TOLERANCE + 1, _terminalToken); } /** - * @notice Test sweep revert if transfer fails + * @notice Reverts if the project hasn't emitted a token (yet), as the pool address isn't unreliable then */ - function test_Sweep_revertIfTransferFails() public { - // Store the delegate leftover - stdstore.target(address(delegate)).sig("sweepBalance()").checked_write(1 ether); + function test_setPoolFor_revertIfNoProjectToken( + uint256 _secondsAgo, + uint256 _twapDelta, + address _terminalToken, + address _projectToken, + uint24 _fee + ) public { + vm.assume(_terminalToken != address(0) && _projectToken != address(0) && _fee != 0); + vm.assume(_terminalToken != _projectToken); - // Store the dude leftover - stdstore.target(address(delegate)).sig("sweepBalanceOf(address)").with_key(didPayData.payer).checked_write( - 1 ether - ); + uint256 _MIN_TWAP_WINDOW = delegate.MIN_TWAP_WINDOW(); + uint256 _MAX_TWAP_WINDOW = delegate.MAX_TWAP_WINDOW(); - // Deal enough ETH - vm.deal(address(delegate), 1 ether); + uint256 _MIN_TWAP_SLIPPAGE_TOLERANCE = delegate.MIN_TWAP_SLIPPAGE_TOLERANCE(); + uint256 _MAX_TWAP_SLIPPAGE_TOLERANCE = delegate.MAX_TWAP_SLIPPAGE_TOLERANCE(); - // no fallback -> will revert - vm.etch(dude, "6969"); + _twapDelta = bound(_twapDelta, _MIN_TWAP_SLIPPAGE_TOLERANCE, _MAX_TWAP_SLIPPAGE_TOLERANCE); + _secondsAgo = bound(_secondsAgo, _MIN_TWAP_WINDOW, _MAX_TWAP_WINDOW); - // Check: revert? - vm.prank(dude); - vm.expectRevert(abi.encodeWithSelector(JBBuybackDelegate.JuiceBuyback_TransferFailed.selector)); - delegate.sweep(dude); + vm.mockCall(address(tokenStore), abi.encodeCall(tokenStore.tokenOf, (projectId)), abi.encode(address(0))); + + vm.expectRevert(IJBBuybackDelegate.JuiceBuyback_NoProjectToken.selector); + vm.prank(owner); + delegate.setPoolFor(projectId, _fee, uint32(_secondsAgo), _twapDelta, _terminalToken); } /** * @notice Test increase seconds ago */ - function test_increaseSecondsAgo(uint256 _oldValue, uint256 _newValue) public { - // Avoid overflow in second bound - _oldValue = bound(_oldValue, 0, type(uint32).max - 1); - - // Store a preexisting secondsAgo (packed slot, need a setter instead of stdstore) - delegate.ForTest_setSecondsAgo(uint32(_oldValue)); + function test_setTwapWindowOf(uint256 _newValue) public { + uint256 _MAX_TWAP_WINDOW = delegate.MAX_TWAP_WINDOW(); + uint256 _MIN_TWAP_WINDOW = delegate.MIN_TWAP_WINDOW(); - // Only increase accepted - _newValue = bound(_newValue, delegate.secondsAgo() + 1, type(uint32).max); + _newValue = bound(_newValue, _MIN_TWAP_WINDOW, _MAX_TWAP_WINDOW); // check: correct event? vm.expectEmit(true, true, true, true); - emit BuybackDelegate_SecondsAgoIncrease(delegate.secondsAgo(), _newValue); + emit BuybackDelegate_TwapWindowChanged(projectId, delegate.twapWindowOf(projectId), _newValue, owner); // Test: change seconds ago vm.prank(owner); - delegate.increaseSecondsAgo(uint32(_newValue)); + delegate.setTwapWindowOf(projectId, uint32(_newValue)); // Check: correct seconds ago? - assertEq(delegate.secondsAgo(), _newValue); + assertEq(delegate.twapWindowOf(projectId), _newValue); } /** - * @notice Test increase seconds ago revert if no increase + * @notice Test increase seconds ago revert if wrong caller */ - function test_increaseSecondsAgo_revertIfNoIncrease(uint256 _oldValue, uint256 _newValue) public { - // Avoid overflow in second bound - _oldValue = bound(_oldValue, 0, type(uint32).max - 1); + function test_setTwapWindowOf_revertIfWrongCaller(address _notOwner) public { + vm.assume(owner != _notOwner); - // Store a preexisting secondsAgo - delegate.ForTest_setSecondsAgo(uint32(_oldValue)); + vm.mockCall( + address(operatorStore), + abi.encodeCall( + operatorStore.hasPermission, (_notOwner, owner, projectId, JBBuybackDelegateOperations.SET_POOL_PARAMS) + ), + abi.encode(false) + ); + vm.expectCall( + address(operatorStore), + abi.encodeCall( + operatorStore.hasPermission, (_notOwner, owner, projectId, JBBuybackDelegateOperations.SET_POOL_PARAMS) + ) + ); - // Not an increase - _newValue = bound(_newValue, 0, delegate.secondsAgo()); + vm.mockCall( + address(operatorStore), + abi.encodeCall( + operatorStore.hasPermission, (_notOwner, owner, 0, JBBuybackDelegateOperations.SET_POOL_PARAMS) + ), + abi.encode(false) + ); + vm.expectCall( + address(operatorStore), + abi.encodeCall( + operatorStore.hasPermission, (_notOwner, owner, 0, JBBuybackDelegateOperations.SET_POOL_PARAMS) + ) + ); // check: revert? - vm.expectRevert(abi.encodeWithSelector(JBBuybackDelegate.JuiceBuyback_NewSecondsAgoTooLow.selector)); + vm.expectRevert(abi.encodeWithSignature("UNAUTHORIZED()")); - // Test: change seconds ago - vm.prank(owner); - delegate.increaseSecondsAgo(uint32(_newValue)); + // Test: change seconds ago (left uninit/at 0) + vm.startPrank(_notOwner); + delegate.setTwapWindowOf(projectId, 999); } /** - * @notice Test increase seconds ago revert if wrong caller + * @notice Test increase seconds ago reverting on boundary */ - function test_increaseSecondsAgo_revertIfWrongCaller(address _notOwner) public { - vm.assume(owner != _notOwner); + function test_setTwapWindowOf_revertIfNewValueTooBigOrTooLow(uint256 _newValueSeed) public { + uint256 _MAX_TWAP_WINDOW = delegate.MAX_TWAP_WINDOW(); + uint256 _MIN_TWAP_WINDOW = delegate.MIN_TWAP_WINDOW(); - emit log_address(owner); - emit log_address(_notOwner); - emit log_address(delegate.owner()); + uint256 _newValue = bound(_newValueSeed, _MAX_TWAP_WINDOW + 1, type(uint32).max); - // check: revert? - vm.expectRevert("Ownable: caller is not the owner"); + // Check: revert? + vm.expectRevert(abi.encodeWithSelector(IJBBuybackDelegate.JuiceBuyback_InvalidTwapWindow.selector)); - // Test: change seconds ago (left uninit/at 0) - vm.startPrank(_notOwner); - delegate.increaseSecondsAgo(999); + // Test: try to change seconds ago + vm.prank(owner); + delegate.setTwapWindowOf(projectId, uint32(_newValue)); + + _newValue = bound(_newValueSeed, 0, _MIN_TWAP_WINDOW - 1); + + // Check: revert? + vm.expectRevert(abi.encodeWithSelector(IJBBuybackDelegate.JuiceBuyback_InvalidTwapWindow.selector)); + + // Test: try to change seconds ago + vm.prank(owner); + delegate.setTwapWindowOf(projectId, uint32(_newValue)); } /** * @notice Test set twap delta */ - function test_setTwapDelta(uint256 _oldDelta, uint256 _newDelta) public { - // Store a preexisting twap delta - stdstore.target(address(delegate)).sig("twapDelta()").checked_write(_oldDelta); + function test_setTwapSlippageToleranceOf(uint256 _newDelta) public { + uint256 _MIN_TWAP_SLIPPAGE_TOLERANCE = delegate.MIN_TWAP_SLIPPAGE_TOLERANCE(); + uint256 _MAX_TWAP_SLIPPAGE_TOLERANCE = delegate.MAX_TWAP_SLIPPAGE_TOLERANCE(); + _newDelta = bound(_newDelta, _MIN_TWAP_SLIPPAGE_TOLERANCE, _MAX_TWAP_SLIPPAGE_TOLERANCE); // Check: correct event? vm.expectEmit(true, true, true, true); - emit BuybackDelegate_TwapDeltaChanged(_oldDelta, _newDelta); + emit BuybackDelegate_TwapSlippageToleranceChanged(projectId, delegate.twapSlippageToleranceOf(projectId), _newDelta, owner); // Test: set the twap vm.prank(owner); - delegate.setTwapDelta(_newDelta); + delegate.setTwapSlippageToleranceOf(projectId, _newDelta); // Check: correct twap? - assertEq(delegate.twapDelta(), _newDelta); + assertEq(delegate.twapSlippageToleranceOf(projectId), _newDelta); } /** * @notice Test set twap delta reverts if wrong caller */ - function test_setTwapDelta_revertWrongCaller(address _notOwner) public { + function test_setTwapSlippageToleranceOf_revertWrongCaller(address _notOwner) public { vm.assume(owner != _notOwner); + vm.mockCall( + address(operatorStore), + abi.encodeCall( + operatorStore.hasPermission, (_notOwner, owner, projectId, JBBuybackDelegateOperations.SET_POOL_PARAMS) + ), + abi.encode(false) + ); + vm.expectCall( + address(operatorStore), + abi.encodeCall( + operatorStore.hasPermission, (_notOwner, owner, projectId, JBBuybackDelegateOperations.SET_POOL_PARAMS) + ) + ); + + vm.mockCall( + address(operatorStore), + abi.encodeCall( + operatorStore.hasPermission, (_notOwner, owner, 0, JBBuybackDelegateOperations.SET_POOL_PARAMS) + ), + abi.encode(false) + ); + vm.expectCall( + address(operatorStore), + abi.encodeCall( + operatorStore.hasPermission, (_notOwner, owner, 0, JBBuybackDelegateOperations.SET_POOL_PARAMS) + ) + ); + // check: revert? - vm.expectRevert("Ownable: caller is not the owner"); + vm.expectRevert(abi.encodeWithSignature("UNAUTHORIZED()")); // Test: set the twap vm.prank(_notOwner); - delegate.setTwapDelta(1); + delegate.setTwapSlippageToleranceOf(projectId, 1); } -} -contract ForTest_BuybackDelegate is JBBuybackDelegate { - constructor( - IERC20 _projectToken, - IWETH9 _weth, - address _factory, - uint24 _fee, - uint32 _secondsAgo, - uint256 _twapDelta, - IJBDirectory _directory, - IJBController3_1 _controller, - bytes4 _id - ) JBBuybackDelegate(_projectToken, _weth, _factory, _fee, _secondsAgo, _twapDelta, _directory, _controller, _id) {} + /** + * @notice Test set twap delta + */ + function test_setTwapSlippageToleranceOf_revertIfInvalidNewValue(uint256 _newDeltaSeed) public { + uint256 _MIN_TWAP_SLIPPAGE_TOLERANCE = delegate.MIN_TWAP_SLIPPAGE_TOLERANCE(); + uint256 _MAX_TWAP_SLIPPAGE_TOLERANCE = delegate.MAX_TWAP_SLIPPAGE_TOLERANCE(); + + uint256 _newDelta = bound(_newDeltaSeed, 0, _MIN_TWAP_SLIPPAGE_TOLERANCE - 1); + + vm.expectRevert(abi.encodeWithSelector(IJBBuybackDelegate.JuiceBuyback_InvalidTwapSlippageTolerance.selector)); + + // Test: set the twap + vm.prank(owner); + delegate.setTwapSlippageToleranceOf(projectId, _newDelta); + + _newDelta = bound(_newDeltaSeed, _MAX_TWAP_SLIPPAGE_TOLERANCE + 1, type(uint256).max); + + vm.expectRevert(abi.encodeWithSelector(IJBBuybackDelegate.JuiceBuyback_InvalidTwapSlippageTolerance.selector)); - function ForTest_getQuote(uint256 _amountIn) external view returns (uint256 _amountOut) { - return _getQuote(_amountIn); + // Test: set the twap + vm.prank(owner); + delegate.setTwapSlippageToleranceOf(projectId, _newDelta); } - function ForTest_setSecondsAgo(uint32 _secondsAgo) external { - secondsAgo = _secondsAgo; + /** + * @notice Test if using the delegate as a redemption delegate (which shouldn't be) doesn't influence redemption + */ + function test_redeemParams_unchangedRedemption(uint256 _amountIn) public { + JBRedeemParamsData memory _data = JBRedeemParamsData({ + terminal: IJBPaymentTerminal(makeAddr('terminal')), + holder: makeAddr('hooldooor'), + projectId: 69, + currentFundingCycleConfiguration: 420, + tokenCount: 4, + totalSupply: 5, + overflow: 6, + reclaimAmount: JBTokenAmount(address(1), _amountIn, 2, 3), + useTotalOverflow: true, + redemptionRate: 7, + memo: 'memooo', + metadata: '' + }); + + (uint256 _amountOut, string memory _memoOut, JBRedemptionDelegateAllocation3_1_1[] memory _allocationOut) = delegate.redeemParams(_data); + + assertEq(_amountOut, _amountIn); + assertEq(_memoOut, _data.memo); + assertEq(_allocationOut.length, 0); + } + + function test_supportsInterface(bytes4 _random) public { + vm.assume(_random != type(IJBBuybackDelegate).interfaceId + && _random != type(IJBFundingCycleDataSource3_1_1).interfaceId + && _random != type(IJBPayDelegate3_1_1).interfaceId + && _random != type(IERC165).interfaceId + ); + + assertTrue(ERC165Checker.supportsInterface(address(delegate), type(IJBFundingCycleDataSource3_1_1).interfaceId)); + assertTrue(ERC165Checker.supportsInterface(address(delegate), type(IJBPayDelegate3_1_1).interfaceId)); + assertTrue(ERC165Checker.supportsInterface(address(delegate), type(IJBBuybackDelegate).interfaceId)); + assertTrue(ERC165Checker.supportsERC165(address(delegate))); + + assertFalse(ERC165Checker.supportsInterface(address(delegate), _random)); + } +} + +contract ForTest_JBBuybackDelegate is JBBuybackDelegate { + constructor(IWETH9 _weth, address _factory, IJBDirectory _directory, IJBController3_1 _controller, bytes4 _id) + JBBuybackDelegate(_weth, _factory, _directory, _controller, _id) + {} + + function ForTest_getQuote(uint256 _projectId, address _projectToken, uint256 _amountIn, address _terminalToken) + external + view + returns (uint256 _amountOut) + { + return _getQuote(_projectId, _projectToken, _amountIn, _terminalToken); + } + + function ForTest_initPool( + IUniswapV3Pool _pool, + uint256 _projectId, + uint32 _secondsAgo, + uint256 _twapDelta, + address _projectToken, + address _terminalToken + ) external { + _twapParamsOf[_projectId] = _twapDelta << 128 | _secondsAgo; + projectTokenOf[_projectId] = _projectToken; + poolOf[_projectId][_terminalToken] = _pool; } } diff --git a/contracts/test/JBGenericBuybackDelegate_Integration.t.sol b/contracts/test/JBGenericBuybackDelegate_Integration.t.sol deleted file mode 100644 index d41a0d1..0000000 --- a/contracts/test/JBGenericBuybackDelegate_Integration.t.sol +++ /dev/null @@ -1,389 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.16; - -import "../interfaces/external/IWETH9.sol"; -import "./helpers/TestBaseWorkflowV3.sol"; - -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBController.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleStore.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleBallot.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBFundingCycleDataSource.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBOperatable.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPayDelegate.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBRedemptionDelegate.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPayoutRedemptionPaymentTerminal.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBSingleTokenPaymentTerminalStore.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBToken.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBConstants.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBCurrencies.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBFundingCycleMetadataResolver.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBOperations.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBTokens.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/structs/JBFundingCycle.sol"; - -import {JBDelegateMetadataHelper} from "@jbx-protocol/juice-delegate-metadata-lib/src/JBDelegateMetadataHelper.sol"; - -import "@paulrberg/contracts/math/PRBMath.sol"; -import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; -import "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol"; - -import "../JBGenericBuybackDelegate.sol"; -import "../mock/MockAllocator.sol"; - -/** - * @notice Integration tests for the JBBuybackDelegate contract. - * - */ -contract TestJBGenericBuybackDelegate_Integration is TestBaseWorkflowV3 { - using stdStorage for StdStorage; - using JBFundingCycleMetadataResolver for JBFundingCycle; - - event Mint( - address indexed holder, - uint256 indexed projectId, - uint256 amount, - bool tokensWereClaimed, - bool preferClaimedTokens, - address caller - ); - - JBProjectMetadata _projectMetadata; - JBFundingCycleData _data; - JBFundingCycleData _dataReconfiguration; - JBFundingCycleData _dataWithoutBallot; - JBFundingCycleMetadata _metadata; - JBFundAccessConstraints[] _fundAccessConstraints; // Default empty - IJBPaymentTerminal[] _terminals; // Default empty - - uint256 _projectId; - uint256 reservedRate = 4500; - uint256 weight = 10 ** 18; // Minting 1 token per eth - - uint32 cardinality = 1000; - - uint256 twapDelta = 500; - - JBGenericBuybackDelegate _delegate; - JBDelegateMetadataHelper _metadataHelper = new JBDelegateMetadataHelper(); - - // Use the L1 UniswapV3Pool jbx/eth 1% fee for create2 magic - IUniswapV3Pool pool = IUniswapV3Pool(0x48598Ff1Cee7b4d31f8f9050C2bbAE98e17E6b17); - IJBToken jbx = IJBToken(0x3abF2A4f8452cCC2CF7b4C1e4663147600646f66); - IWETH9 weth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); - address _uniswapFactory = 0x1F98431c8aD98523631AE4a59f267346ea31F984; - uint24 fee = 10000; - - /** - * @notice Set up a new JBX project and use the buyback delegate as the datasource - */ - function setUp() public override { - // label - vm.label(address(pool), "uniswapPool"); - vm.label(address(_uniswapFactory), "uniswapFactory"); - vm.label(address(weth), "$WETH"); - vm.label(address(jbx), "$JBX"); - - // mock - vm.etch(address(pool), "0x69"); - vm.etch(address(weth), "0x69"); - - // super is the Jbx V3 fixture - super.setUp(); - - // Deploy the delegate - _delegate = new JBGenericBuybackDelegate({ - _weth: weth, - _factory: _uniswapFactory, - _directory: IJBDirectory(address(_jbDirectory)), - _controller: _jbController, - _delegateId: bytes4(hex'69') - }); - - _projectMetadata = JBProjectMetadata({content: "myIPFSHash", domain: 1}); - - _data = JBFundingCycleData({ - duration: 6 days, - weight: weight, - discountRate: 0, - ballot: IJBFundingCycleBallot(address(0)) - }); - - _metadata = JBFundingCycleMetadata({ - global: JBGlobalFundingCycleMetadata({ - allowSetTerminals: false, - allowSetController: false, - pauseTransfers: false - }), - reservedRate: reservedRate, - redemptionRate: 5000, - ballotRedemptionRate: 0, - pausePay: false, - pauseDistributions: false, - pauseRedeem: false, - pauseBurn: false, - allowMinting: true, - preferClaimedTokenOverride: false, - allowTerminalMigration: false, - allowControllerMigration: false, - holdFees: false, - useTotalOverflowForRedemptions: false, - useDataSourceForPay: true, - useDataSourceForRedeem: false, - dataSource: address(_delegate), - metadata: 0 - }); - - _fundAccessConstraints.push( - JBFundAccessConstraints({ - terminal: _jbETHPaymentTerminal, - token: jbLibraries().ETHToken(), - distributionLimit: 2 ether, - overflowAllowance: type(uint232).max, - distributionLimitCurrency: 1, // Currency = ETH - overflowAllowanceCurrency: 1 - }) - ); - - _terminals = [_jbETHPaymentTerminal]; - - JBGroupedSplits[] memory _groupedSplits = new JBGroupedSplits[](1); // Default empty - - // Mint a few empty project id, to move away from protocol project 1 - _jbProjects.createFor(address(1), JBProjectMetadata("", 0)); - _jbProjects.createFor(address(1), JBProjectMetadata("", 0)); - _jbProjects.createFor(address(1), JBProjectMetadata("", 0)); - - _projectId = _jbController.launchProjectFor( - _multisig, - _projectMetadata, - _data, - _metadata, - 0, // Start asap - _groupedSplits, - _fundAccessConstraints, - _terminals, - "" - ); - - // "Issue" the token with $JBX address (for create2 trick) - to refactor - stdstore - .target(address(_jbTokenStore)) - .sig(_jbTokenStore.tokenOf.selector) - .with_key(_projectId) - .checked_write(address(jbx)); - - JBToken _template = new JBToken("jbx", "jbx", _projectId); - vm.etch(address(jbx), address(_template).code); - - // Correct ownership - stdstore - .target(address(jbx)) - .sig(Ownable(address(jbx)).owner.selector) - .checked_write(address(_jbTokenStore)); - } - - modifier poolAdded() { - vm.prank(_multisig); - address _newPool = address(_delegate.setPoolFor(_projectId, fee, uint32(cardinality), twapDelta, address(weth))); - assertEq(_newPool, address(pool), "wrong computed pool address"); - _; - } - - /** - * @notice If the quote amount is lower than the token that would be received after minting, the buyback delegate isn't used at all - */ - function testDatasourceDelegateWhenQuoteIsLowerThanTokenCount(uint256 _quote) public poolAdded { - // Do not use a quote of 0, as it would then fetch a twap - _quote = bound(_quote, 1, weight); - - uint256 payAmountInWei = 2 ether; - - // setting the quote in metadata, bigger than the weight - bytes[] memory _quoteData = new bytes[](1); - _quoteData[0] = abi.encode(_quote, 500); - - // Pass the delegate id - bytes4[] memory _ids = new bytes4[](1); - _ids[0] = bytes4(hex"69"); - - // Generate the metadata - bytes memory _delegateMetadata = _metadataHelper.createMetadata(_ids, _quoteData); - - // Compute the project token which should have been minted (for the beneficiary or the reserve) - uint256 totalMinted = PRBMath.mulDiv(payAmountInWei, weight, 10 ** 18); - uint256 amountBeneficiary = - (totalMinted * (JBConstants.MAX_RESERVED_RATE - reservedRate)) / JBConstants.MAX_RESERVED_RATE; - uint256 amountReserved = totalMinted - amountBeneficiary; - - // This shouldn't mint via the delegate - vm.expectEmit(true, true, true, true); - emit Mint({ - holder: _beneficiary, - projectId: _projectId, - amount: amountBeneficiary, - tokensWereClaimed: true, - preferClaimedTokens: true, - caller: address(_jbController) - }); - - _jbETHPaymentTerminal.pay{value: payAmountInWei}( - _projectId, - payAmountInWei, - address(0), - _beneficiary, - /* _minReturnedTokens */ - 0, - /* _preferClaimedTokens */ - true, - /* _memo */ - "Take my money!", - /* _delegateMetadata */ - _delegateMetadata - ); - - // Check: correct beneficiary balance? - assertEq(_jbTokenStore.balanceOf(_beneficiary, _projectId), amountBeneficiary); - - // Check: correct reserve? - assertEq(_jbController.reservedTokenBalanceOf(_projectId), amountReserved); - } - - /** - * @notice If claimed token flag is not true then make sure the delegate mints the tokens & the balance distribution is correct - */ - function testDatasourceDelegateMintIfPreferenceIsNotToClaimTokens() public poolAdded { - uint256 payAmountInWei = 10 ether; - - // setting the quote in metadata - bytes[] memory _quoteData = new bytes[](1); - _quoteData[0] = abi.encode(1 ether, 10000); - - // Pass the delegate id - bytes4[] memory _ids = new bytes4[](1); - _ids[0] = bytes4(hex"69"); - - // Generate the metadata - bytes memory _delegateMetadata = _metadataHelper.createMetadata(_ids, _quoteData); - - uint256 totalMinted = PRBMath.mulDiv(payAmountInWei, weight, 10 ** 18); - uint256 amountBeneficiary = - PRBMath.mulDiv(totalMinted, JBConstants.MAX_RESERVED_RATE - reservedRate, JBConstants.MAX_RESERVED_RATE); - - uint256 amountReserved = totalMinted - amountBeneficiary; - - // This shouldn't mint via the delegate - vm.expectEmit(true, true, true, true); - emit Mint({ - holder: _beneficiary, - projectId: _projectId, - amount: amountBeneficiary, - tokensWereClaimed: false, - preferClaimedTokens: false, - caller: address(_jbController) - }); - - _jbETHPaymentTerminal.pay{value: payAmountInWei}( - _projectId, - payAmountInWei, - address(0), - _beneficiary, - /* _minReturnedTokens */ - 0, // Cannot be used in this setting - /* _preferClaimedTokens */ - false, - /* _memo */ - "Take my money!", - /* _delegateMetadata */ - _delegateMetadata - ); - - assertEq(_jbTokenStore.balanceOf(_beneficiary, _projectId), amountBeneficiary); - assertEq(_jbController.reservedTokenBalanceOf(_projectId), amountReserved); - assertEq(_jbPaymentTerminalStore.balanceOf(_jbETHPaymentTerminal, _projectId), payAmountInWei); - } - - /** - * @notice if claimed token flag is true and the quote is greather than the weight, we go for the swap path - */ - function testDatasourceDelegateSwapIfPreferenceIsToClaimTokens() public poolAdded { - uint256 payAmountInWei = 1 ether; - uint256 quoteOnUniswap = (weight * 106) / 100; // Take slippage into account - - // Trick the delegate balance post-swap (avoid callback revert on slippage) - vm.prank(_multisig); - _jbController.mintTokensOf(_projectId, quoteOnUniswap, address(_delegate), "", false, false); - - // setting the quote in metadata - bytes[] memory _quoteData = new bytes[](1); - _quoteData[0] = abi.encode(quoteOnUniswap, 500); - - // Pass the delegate id - bytes4[] memory _ids = new bytes4[](1); - _ids[0] = bytes4(hex"69"); - - // Generate the metadata - bytes memory _delegateMetadata = _metadataHelper.createMetadata(_ids, _quoteData); - - // Mock the jbx transfer to the beneficiary - same logic as in delegate to avoid rounding errors - uint256 reservedAmount = PRBMath.mulDiv(quoteOnUniswap, reservedRate, JBConstants.MAX_RESERVED_RATE); - - uint256 nonReservedAmount = quoteOnUniswap - reservedAmount; - - // mock the burn call - vm.mockCall( - address(_jbController), - abi.encodeCall(_jbController.burnTokensOf, (address(_delegate), _projectId, quoteOnUniswap, "", true)), - abi.encode(true) - ); - - uint256 _beneficiaryBalanceBefore = _jbTokenStore.balanceOf(_beneficiary, _projectId); - - // Mock the swap returned value, which is the amount of token transfered (negative = exact amount) - vm.mockCall( - address(pool), - abi.encodeWithSelector(IUniswapV3PoolActions.swap.selector), - abi.encode(-int256(quoteOnUniswap), 0) - ); - - // Check: swap triggered? - vm.expectCall(address(pool), abi.encodeWithSelector(IUniswapV3PoolActions.swap.selector)); - - _jbETHPaymentTerminal.pay{value: payAmountInWei}( - _projectId, - payAmountInWei, - address(0), - _beneficiary, - /* _minReturnedTokens */ - 0, - /* _preferClaimedTokens */ - true, - /* _memo */ - "Take my money!", - /* _delegateMetadata */ - _delegateMetadata - ); - - assertEq(_jbTokenStore.balanceOf(_beneficiary, _projectId), _beneficiaryBalanceBefore + nonReservedAmount); - - // Check: correct reserve balance? - assertEq(_jbController.reservedTokenBalanceOf(_projectId), reservedAmount); - } - -// TODO: refactor with a REAL integration test (using terminal.pay) - - /** - * @notice Test the uniswap callback reverting when max slippage is hit - * - * @dev This would mean the _mint is then called - */ - function testRevertIfSlippageIsTooMuchWhenSwapping() public poolAdded { - // Generate the metadata - bytes memory _delegateMetadata = abi.encode(_projectId, uint256(100 ether), weth, jbx); - - vm.expectRevert(abi.encodeWithSignature("JuiceBuyback_MaximumSlippage()")); - - // callback giving 1 instead - vm.prank(address(pool)); - _delegate.uniswapV3SwapCallback(-1 ether, 1 ether, _delegateMetadata); - } -} diff --git a/contracts/test/JBGenericBuybackDelegate_Unit.t.sol b/contracts/test/JBGenericBuybackDelegate_Unit.t.sol deleted file mode 100644 index c870e29..0000000 --- a/contracts/test/JBGenericBuybackDelegate_Unit.t.sol +++ /dev/null @@ -1,1259 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.16; - -import "../interfaces/external/IWETH9.sol"; -import "./helpers/TestBaseWorkflowV3.sol"; - -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBController3_1.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBDirectory.sol"; -import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBTokens.sol"; - -import {JBDelegateMetadataHelper} from "@jbx-protocol/juice-delegate-metadata-lib/src/JBDelegateMetadataHelper.sol"; - -import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; - -import "forge-std/Test.sol"; - -import "./helpers/PoolAddress.sol"; -import "../JBGenericBuybackDelegate.sol"; -import "../libraries/JBBuybackDelegateOperations.sol"; - -/** - * @notice Unit tests for the JBGenericBuybackDelegate contract. - * - */ -contract TestJBGenericBuybackDelegate_Units is Test { - using stdStorage for StdStorage; - - ForTest_JBGenericBuybackDelegate delegate; - - event BuybackDelegate_Swap(uint256 indexed projectId, uint256 amountEth, uint256 amountOut); - event BuybackDelegate_Mint(uint256 indexed projectId); - event BuybackDelegate_SecondsAgoChanged(uint256 indexed projectId, uint256 oldSecondsAgo, uint256 newSecondsAgo); - event BuybackDelegate_TwapDeltaChanged(uint256 indexed projectId, uint256 oldTwapDelta, uint256 newTwapDelta); - event BuybackDelegate_PendingSweep(address indexed beneficiary, address indexed token, uint256 amount); - event BuybackDelegate_PoolAdded(uint256 indexed projectId, address indexed terminalToken, address newPool); - - // Use the L1 UniswapV3Pool jbx/eth 1% fee for create2 magic - IUniswapV3Pool pool = IUniswapV3Pool(0x48598Ff1Cee7b4d31f8f9050C2bbAE98e17E6b17); - IERC20 projectToken = IERC20(0x3abF2A4f8452cCC2CF7b4C1e4663147600646f66); - IWETH9 weth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); - uint24 fee = 10000; - - // A random non-weth pool: The PulseDogecoin Staking Carnival Token/HEX @ 0.3% - IERC20 otherRandomProjectToken = IERC20(0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39); - IERC20 randomTerminalToken = IERC20(0x488Db574C77dd27A07f9C97BAc673BC8E9fC6Bf3); - IUniswapV3Pool randomPool = IUniswapV3Pool(0x7668B2Ea8490955F68F5c33E77FE150066c94fb9); - uint24 randomFee = 3000; - uint256 randomId = 420; - - address _uniswapFactory = 0x1F98431c8aD98523631AE4a59f267346ea31F984; - - IJBPayoutRedemptionPaymentTerminal3_1_1 jbxTerminal = - IJBPayoutRedemptionPaymentTerminal3_1_1(makeAddr("IJBPayoutRedemptionPaymentTerminal3_1")); - IJBProjects projects = IJBProjects(makeAddr("IJBProjects")); - IJBOperatorStore operatorStore = IJBOperatorStore(makeAddr("IJBOperatorStore")); - IJBController3_1 controller = IJBController3_1(makeAddr("controller")); - IJBDirectory directory = IJBDirectory(makeAddr("directory")); - IJBTokenStore tokenStore = IJBTokenStore(makeAddr("tokenStore")); - - JBDelegateMetadataHelper metadataHelper = new JBDelegateMetadataHelper(); - - address terminalStore = makeAddr("terminalStore"); - - address dude = makeAddr("dude"); - address owner = makeAddr("owner"); - - uint32 secondsAgo = 100; - uint256 twapDelta = 100; - - uint256 projectId = 69; - - JBPayParamsData payParams = JBPayParamsData({ - terminal: jbxTerminal, - payer: dude, - amount: JBTokenAmount({token: address(weth), value: 1 ether, decimals: 18, currency: 1}), - projectId: projectId, - currentFundingCycleConfiguration: 0, - beneficiary: dude, - weight: 69, - reservedRate: 0, - memo: "myMemo", - metadata: "" - }); - - JBDidPayData3_1_1 didPayData = JBDidPayData3_1_1({ - payer: dude, - projectId: projectId, - currentFundingCycleConfiguration: 0, - amount: JBTokenAmount({token: JBTokens.ETH, value: 1 ether, decimals: 18, currency: 1}), - forwardedAmount: JBTokenAmount({token: JBTokens.ETH, value: 1 ether, decimals: 18, currency: 1}), - projectTokenCount: 69, - beneficiary: dude, - preferClaimedTokens: true, - memo: "myMemo", - dataSourceMetadata: "", - payerMetadata: "" - }); - - function setUp() external { - vm.etch(address(projectToken), "6969"); - vm.etch(address(weth), "6969"); - vm.etch(address(pool), "6969"); - vm.etch(address(jbxTerminal), "6969"); - vm.etch(address(projects), "6969"); - vm.etch(address(operatorStore), "6969"); - vm.etch(address(controller), "6969"); - vm.etch(address(directory), "6969"); - - vm.label(address(pool), "pool"); - vm.label(address(projectToken), "projectToken"); - vm.label(address(weth), "weth"); - - vm.mockCall(address(jbxTerminal), abi.encodeCall(jbxTerminal.store, ()), abi.encode(terminalStore)); - vm.mockCall(address(controller), abi.encodeCall(IJBOperatable.operatorStore, ()), abi.encode(operatorStore)); - vm.mockCall(address(controller), abi.encodeCall(controller.projects, ()), abi.encode(projects)); - - vm.mockCall(address(projects), abi.encodeCall(projects.ownerOf, (projectId)), abi.encode(owner)); - - vm.mockCall(address(jbxTerminal), abi.encodeCall(IJBSingleTokenPaymentTerminal.token, ()), abi.encode(weth)); - - vm.mockCall(address(controller), abi.encodeCall(controller.tokenStore, ()), abi.encode(tokenStore)); - - vm.prank(owner); - delegate = new ForTest_JBGenericBuybackDelegate({ - _weth: weth, - _factory: _uniswapFactory, - _directory: directory, - _controller: controller, - _id: bytes4(hex'69') - }); - - delegate.ForTest_initPool(pool, projectId, secondsAgo, twapDelta, address(projectToken), address(weth)); - delegate.ForTest_initPool( - randomPool, randomId, secondsAgo, twapDelta, address(otherRandomProjectToken), address(randomTerminalToken) - ); - } - - /** - * @notice Test payParams when a quote is provided as metadata - * - * @dev _tokenCount == weight, as we use a value of 1. - */ - function test_payParams_callWithQuote(uint256 _tokenCount, uint256 _swapOutCount, uint256 _slippage) public { - // Avoid overflow when computing slippage (cannot swap uint256.max tokens) - _swapOutCount = bound(_swapOutCount, 1, type(uint240).max); - - _slippage = bound(_slippage, 1, 10000); - - // Take max slippage into account - uint256 _swapQuote = _swapOutCount - ((_swapOutCount * _slippage) / 10000); - - // Pass the quote as metadata - bytes[] memory _data = new bytes[](1); - _data[0] = abi.encode(_swapOutCount, _slippage); - - // Pass the delegate id - bytes4[] memory _ids = new bytes4[](1); - _ids[0] = bytes4(hex"69"); - - // Generate the metadata - bytes memory _metadata = metadataHelper.createMetadata(_ids, _data); - - // Set the relevant payParams data - payParams.weight = _tokenCount; - payParams.metadata = _metadata; - - // Returned values to catch: - JBPayDelegateAllocation3_1_1[] memory _allocationsReturned; - string memory _memoReturned; - uint256 _weightReturned; - - // Test: call payParams - vm.prank(terminalStore); - (_weightReturned, _memoReturned, _allocationsReturned) = delegate.payParams(payParams); - - // Mint pathway if more token received when minting: - if (_tokenCount >= _swapQuote) { - // No delegate allocation returned - assertEq(_allocationsReturned.length, 0); - - // weight unchanged - assertEq(_weightReturned, _tokenCount); - } - // Swap pathway (return the delegate allocation) - else { - assertEq(_allocationsReturned.length, 1); - assertEq(address(_allocationsReturned[0].delegate), address(delegate)); - assertEq(_allocationsReturned[0].amount, 1 ether); - assertEq(_allocationsReturned[0].metadata, abi.encode(_tokenCount, _swapQuote, projectToken)); - - assertEq(_weightReturned, 0); - } - - // Same memo in any case - assertEq(_memoReturned, payParams.memo); - } - - /** - * @notice Test payParams when no quote is provided, falling back on the pool twap - * - * @dev This bypass testing Uniswap Oracle lib by re-using the internal _getQuote - */ - function test_payParams_useTwap(uint256 _tokenCount) public { - // Set the relevant payParams data - payParams.weight = _tokenCount; - payParams.metadata = ""; - - // Mock the pool being unlocked - vm.mockCall(address(pool), abi.encodeCall(pool.slot0, ()), abi.encode(0, 0, 0, 0, 0, 0, true)); - vm.expectCall(address(pool), abi.encodeCall(pool.slot0, ())); - - // Mock the pool's twap - uint32[] memory _secondsAgos = new uint32[](2); - _secondsAgos[0] = secondsAgo; - _secondsAgos[1] = 0; - - uint160[] memory _secondPerLiquidity = new uint160[](2); - _secondPerLiquidity[0] = 100; - _secondPerLiquidity[1] = 1000; - - int56[] memory _tickCumulatives = new int56[](2); - _tickCumulatives[0] = 100; - _tickCumulatives[1] = 1000; - - vm.mockCall( - address(pool), - abi.encodeCall(pool.observe, (_secondsAgos)), - abi.encode(_tickCumulatives, _secondPerLiquidity) - ); - vm.expectCall(address(pool), abi.encodeCall(pool.observe, (_secondsAgos))); - - // Returned values to catch: - JBPayDelegateAllocation3_1_1[] memory _allocationsReturned; - string memory _memoReturned; - uint256 _weightReturned; - - // Test: call payParams - vm.prank(terminalStore); - (_weightReturned, _memoReturned, _allocationsReturned) = delegate.payParams(payParams); - - // Bypass testing uniswap oracle lib - uint256 _twapAmountOut = delegate.ForTest_getQuote(projectId, jbxTerminal, address(projectToken), 1 ether); - - // Mint pathway if more token received when minting: - if (_tokenCount >= _twapAmountOut) { - // No delegate allocation returned - assertEq(_allocationsReturned.length, 0); - - // weight unchanged - assertEq(_weightReturned, _tokenCount); - } - // Swap pathway (set the mutexes and return the delegate allocation) - else { - assertEq(_allocationsReturned.length, 1); - assertEq(address(_allocationsReturned[0].delegate), address(delegate)); - assertEq(_allocationsReturned[0].amount, 1 ether); - assertEq(_allocationsReturned[0].metadata, abi.encode(_tokenCount, _twapAmountOut, projectToken)); - - assertEq(_weightReturned, 0); - } - - // Same memo in any case - assertEq(_memoReturned, payParams.memo); - } - - /** - * @notice Test payParams with a twap but locked pool, which should then mint - */ - function test_payParams_useTwapLockedPool(uint256 _tokenCount) public { - _tokenCount = bound(_tokenCount, 1, type(uint120).max); - - // Set the relevant payParams data - payParams.weight = _tokenCount; - payParams.metadata = ""; - - // Mock the pool being unlocked - vm.mockCall(address(pool), abi.encodeCall(pool.slot0, ()), abi.encode(0, 0, 0, 0, 0, 0, false)); - vm.expectCall(address(pool), abi.encodeCall(pool.slot0, ())); - - // Returned values to catch: - JBPayDelegateAllocation3_1_1[] memory _allocationsReturned; - string memory _memoReturned; - uint256 _weightReturned; - - // Test: call payParams - vm.prank(terminalStore); - (_weightReturned, _memoReturned, _allocationsReturned) = delegate.payParams(payParams); - - // No delegate allocation returned - assertEq(_allocationsReturned.length, 0); - - // weight unchanged - assertEq(_weightReturned, _tokenCount); - - // Same memo - assertEq(_memoReturned, payParams.memo); - } - - /** - * @notice Test didPay with token received from swapping - */ - function test_didPay_swap_ETH(uint256 _tokenCount, uint256 _twapQuote, uint256 _reservedRate) public { - // Bound to avoid overflow and insure swap quote > mint quote - _tokenCount = bound(_tokenCount, 2, type(uint256).max - 1); - _twapQuote = bound(_twapQuote, _tokenCount + 1, type(uint256).max); - _reservedRate = bound(_reservedRate, 0, 10000); - - // The metadata coming from payParams(..) - didPayData.dataSourceMetadata = abi.encode(_tokenCount, _twapQuote, projectToken); - - // mock the swap call - vm.mockCall( - address(pool), - abi.encodeCall( - pool.swap, - ( - address(delegate), - address(weth) < address(projectToken), - int256(1 ether), - address(projectToken) < address(weth) ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1, - abi.encode(projectId, _twapQuote, weth, projectToken) - ) - ), - abi.encode(-int256(_twapQuote), -int256(_twapQuote)) - ); - vm.expectCall( - address(pool), - abi.encodeCall( - pool.swap, - ( - address(delegate), - address(weth) < address(projectToken), - int256(1 ether), - address(projectToken) < address(weth) ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1, - abi.encode(projectId, _twapQuote, weth, projectToken) - ) - ) - ); - - // mock call to pass the authorization check - vm.mockCall( - address(directory), - abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))), - abi.encode(true) - ); - vm.expectCall( - address(directory), - abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))) - ); - - // mock the burn call - vm.mockCall( - address(controller), - abi.encodeCall(controller.burnTokensOf, (address(delegate), didPayData.projectId, _twapQuote, "", true)), - abi.encode(true) - ); - vm.expectCall( - address(controller), - abi.encodeCall(controller.burnTokensOf, (address(delegate), didPayData.projectId, _twapQuote, "", true)) - ); - - // mock the minting call - vm.mockCall( - address(controller), - abi.encodeCall( - controller.mintTokensOf, (didPayData.projectId, _twapQuote, address(dude), didPayData.memo, true, true) - ), - abi.encode(true) - ); - vm.expectCall( - address(controller), - abi.encodeCall( - controller.mintTokensOf, (didPayData.projectId, _twapQuote, address(dude), didPayData.memo, true, true) - ) - ); - - // expect event - vm.expectEmit(true, true, true, true); - emit BuybackDelegate_Swap(didPayData.projectId, didPayData.amount.value, _twapQuote); - - vm.prank(address(jbxTerminal)); - delegate.didPay(didPayData); - } - - /** - * @notice Test didPay with token received from swapping - */ - function test_didPay_swap_ERC20(uint256 _tokenCount, uint256 _twapQuote, uint256 _reservedRate) public { - // Bound to avoid overflow and insure swap quote > mint quote - _tokenCount = bound(_tokenCount, 2, type(uint256).max - 1); - _twapQuote = bound(_twapQuote, _tokenCount + 1, type(uint256).max); - _reservedRate = bound(_reservedRate, 0, 10000); - - didPayData.amount = - JBTokenAmount({token: address(randomTerminalToken), value: 1 ether, decimals: 18, currency: 1}); - didPayData.forwardedAmount = - JBTokenAmount({token: address(randomTerminalToken), value: 1 ether, decimals: 18, currency: 1}); - didPayData.projectId = randomId; - - // The metadata coming from payParams(..) - didPayData.dataSourceMetadata = abi.encode(_tokenCount, _twapQuote, otherRandomProjectToken); - - // mock the swap call - vm.mockCall( - address(randomPool), - abi.encodeCall( - randomPool.swap, - ( - address(delegate), - address(randomTerminalToken) < address(otherRandomProjectToken), - int256(1 ether), - address(otherRandomProjectToken) < address(randomTerminalToken) - ? TickMath.MAX_SQRT_RATIO - 1 - : TickMath.MIN_SQRT_RATIO + 1, - abi.encode(randomId, _twapQuote, randomTerminalToken, otherRandomProjectToken) - ) - ), - abi.encode(-int256(_twapQuote), -int256(_twapQuote)) - ); - vm.expectCall( - address(randomPool), - abi.encodeCall( - randomPool.swap, - ( - address(delegate), - address(randomTerminalToken) < address(otherRandomProjectToken), - int256(1 ether), - address(otherRandomProjectToken) < address(randomTerminalToken) - ? TickMath.MAX_SQRT_RATIO - 1 - : TickMath.MIN_SQRT_RATIO + 1, - abi.encode(randomId, _twapQuote, randomTerminalToken, otherRandomProjectToken) - ) - ) - ); - - // mock call to pass the authorization check - vm.mockCall( - address(directory), - abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))), - abi.encode(true) - ); - vm.expectCall( - address(directory), - abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))) - ); - - // mock the burn call - vm.mockCall( - address(controller), - abi.encodeCall(controller.burnTokensOf, (address(delegate), didPayData.projectId, _twapQuote, "", true)), - abi.encode(true) - ); - vm.expectCall( - address(controller), - abi.encodeCall(controller.burnTokensOf, (address(delegate), didPayData.projectId, _twapQuote, "", true)) - ); - - // mock the minting call - vm.mockCall( - address(controller), - abi.encodeCall( - controller.mintTokensOf, (didPayData.projectId, _twapQuote, address(dude), didPayData.memo, true, true) - ), - abi.encode(true) - ); - vm.expectCall( - address(controller), - abi.encodeCall( - controller.mintTokensOf, (didPayData.projectId, _twapQuote, address(dude), didPayData.memo, true, true) - ) - ); - - // No leftover - vm.mockCall( - address(randomTerminalToken), - abi.encodeCall(randomTerminalToken.balanceOf, (address(delegate))), - abi.encode(0) - ); - vm.expectCall(address(randomTerminalToken), abi.encodeCall(randomTerminalToken.balanceOf, (address(delegate)))); - - // expect event - vm.expectEmit(true, true, true, true); - emit BuybackDelegate_Swap(didPayData.projectId, didPayData.amount.value, _twapQuote); - - vm.prank(address(jbxTerminal)); - delegate.didPay(didPayData); - } - - /** - * @notice Test didPay when eth leftover from swap - */ - function test_didPay_keepTrackOfETHToSweep() public { - uint256 _tokenCount = 10; - uint256 _twapQuote = 11; - - // The metadata coming from payParams(..) - didPayData.dataSourceMetadata = abi.encode(_tokenCount, _twapQuote, projectToken); - - // Add some leftover - vm.deal(address(delegate), 10 ether); - - // Add a previous leftover, to test the incremental accounting (ie 5 out of 10 were there) - stdstore.target(address(delegate)).sig("totalSweepBalance(address)").with_key(JBTokens.ETH).checked_write( - 5 ether - ); - - // Out of these 5, 1 was for payer - stdstore.target(address(delegate)).sig("sweepBalanceOf(address,address)").with_key(didPayData.payer).with_key( - JBTokens.ETH - ).checked_write(1 ether); - - // mock call to pass the authorization check - vm.mockCall( - address(directory), - abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))), - abi.encode(true) - ); - vm.expectCall( - address(directory), - abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))) - ); - - // mock the swap call - vm.mockCall( - address(pool), - abi.encodeCall( - pool.swap, - ( - address(delegate), - address(weth) < address(projectToken), - int256(1 ether), - address(projectToken) < address(weth) ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1, - abi.encode(projectId, _twapQuote, weth, projectToken) - ) - ), - abi.encode(-int256(_twapQuote), -int256(_twapQuote)) - ); - vm.expectCall( - address(pool), - abi.encodeCall( - pool.swap, - ( - address(delegate), - address(weth) < address(projectToken), - int256(1 ether), - address(projectToken) < address(weth) ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1, - abi.encode(projectId, _twapQuote, weth, projectToken) - ) - ) - ); - - // mock the burn call - vm.mockCall( - address(controller), - abi.encodeCall(controller.burnTokensOf, (address(delegate), didPayData.projectId, _twapQuote, "", true)), - abi.encode(true) - ); - vm.expectCall( - address(controller), - abi.encodeCall(controller.burnTokensOf, (address(delegate), didPayData.projectId, _twapQuote, "", true)) - ); - - // mock the minting call - vm.mockCall( - address(controller), - abi.encodeCall( - controller.mintTokensOf, - (didPayData.projectId, _twapQuote, address(dude), didPayData.memo, didPayData.preferClaimedTokens, true) - ), - abi.encode(true) - ); - vm.expectCall( - address(controller), - abi.encodeCall( - controller.mintTokensOf, - (didPayData.projectId, _twapQuote, address(dude), didPayData.memo, didPayData.preferClaimedTokens, true) - ) - ); - - // check: correct event? - vm.expectEmit(true, true, true, true); - emit BuybackDelegate_PendingSweep(dude, JBTokens.ETH, 5 ether); - - vm.prank(address(jbxTerminal)); - delegate.didPay(didPayData); - - // Check: correct overall sweep balance? - assertEq(delegate.totalSweepBalance(JBTokens.ETH), 10 ether); - - // Check: correct dude sweep balance (1 previous plus 5 from now)? - assertEq(delegate.sweepBalanceOf(dude, JBTokens.ETH), 6 ether); - } - - /** - * @notice Test didPay with swap reverting, should then mint - */ - - function test_didPay_swapRevert(uint256 _tokenCount, uint256 _twapQuote) public { - _tokenCount = bound(_tokenCount, 2, type(uint256).max - 1); - _twapQuote = bound(_twapQuote, _tokenCount + 1, type(uint256).max); - - // The metadata coming from payParams(..) - didPayData.dataSourceMetadata = abi.encode(_tokenCount, _twapQuote, projectToken); - - // mock the swap call reverting - vm.mockCallRevert( - address(pool), - abi.encodeCall( - pool.swap, - ( - address(delegate), - address(weth) < address(projectToken), - int256(1 ether), - address(projectToken) < address(weth) ? TickMath.MAX_SQRT_RATIO - 1 : TickMath.MIN_SQRT_RATIO + 1, - abi.encode(projectId, _twapQuote, weth, projectToken) - ) - ), - abi.encode("no swap") - ); - - // mock call to pass the authorization check - vm.mockCall( - address(directory), - abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))), - abi.encode(true) - ); - vm.expectCall( - address(directory), - abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))) - ); - - // mock the minting call - this uses the weight and not the (potentially faulty) quote or twap - vm.mockCall( - address(controller), - abi.encodeCall( - controller.mintTokensOf, - (didPayData.projectId, _tokenCount, dude, didPayData.memo, didPayData.preferClaimedTokens, true) - ), - abi.encode(true) - ); - vm.expectCall( - address(controller), - abi.encodeCall( - controller.mintTokensOf, - (didPayData.projectId, _tokenCount, dude, didPayData.memo, didPayData.preferClaimedTokens, true) - ) - ); - - // mock the add to balance adding eth back to the terminal (need to deal eth as this transfer really occurs in test) - vm.deal(address(delegate), 1 ether); - vm.mockCall( - address(jbxTerminal), - abi.encodeCall( - IJBPaymentTerminal(address(jbxTerminal)).addToBalanceOf, - (didPayData.projectId, 1 ether, JBTokens.ETH, "", "") - ), - "" - ); - vm.expectCall( - address(jbxTerminal), - abi.encodeCall( - IJBPaymentTerminal(address(jbxTerminal)).addToBalanceOf, - (didPayData.projectId, 1 ether, JBTokens.ETH, "", "") - ) - ); - - // expect event - vm.expectEmit(true, true, true, true); - emit BuybackDelegate_Mint(didPayData.projectId); - - vm.prank(address(jbxTerminal)); - delegate.didPay(didPayData); - } - - /** - * @notice Test didPay with swap reverting, should then mint - */ - function test_didPay_swapRevert_ERC20(uint256 _tokenCount, uint256 _twapQuote) public { - _tokenCount = bound(_tokenCount, 2, type(uint256).max - 1); - _twapQuote = bound(_twapQuote, _tokenCount + 1, type(uint256).max); - - didPayData.amount = - JBTokenAmount({token: address(randomTerminalToken), value: 1 ether, decimals: 18, currency: 1}); - didPayData.forwardedAmount = - JBTokenAmount({token: address(randomTerminalToken), value: 1 ether, decimals: 18, currency: 1}); - didPayData.projectId = randomId; - - vm.mockCall( - address(jbxTerminal), - abi.encodeCall(IJBSingleTokenPaymentTerminal.token, ()), - abi.encode(randomTerminalToken) - ); - - // The metadata coming from payParams(..) - didPayData.dataSourceMetadata = abi.encode(_tokenCount, _twapQuote, otherRandomProjectToken); - - // mock the swap call reverting - vm.mockCallRevert( - address(randomPool), - abi.encodeCall( - randomPool.swap, - ( - address(delegate), - address(randomTerminalToken) < address(otherRandomProjectToken), - int256(1 ether), - address(otherRandomProjectToken) < address(randomTerminalToken) - ? TickMath.MAX_SQRT_RATIO - 1 - : TickMath.MIN_SQRT_RATIO + 1, - abi.encode(randomId, _twapQuote, randomTerminalToken, otherRandomProjectToken) - ) - ), - abi.encode("no swap") - ); - - // mock call to pass the authorization check - vm.mockCall( - address(directory), - abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))), - abi.encode(true) - ); - vm.expectCall( - address(directory), - abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(jbxTerminal)))) - ); - - // mock the minting call - this uses the weight and not the (potentially faulty) quote or twap - vm.mockCall( - address(controller), - abi.encodeCall( - controller.mintTokensOf, - (didPayData.projectId, _tokenCount, dude, didPayData.memo, didPayData.preferClaimedTokens, true) - ), - abi.encode(true) - ); - vm.expectCall( - address(controller), - abi.encodeCall( - controller.mintTokensOf, - (didPayData.projectId, _tokenCount, dude, didPayData.memo, didPayData.preferClaimedTokens, true) - ) - ); - - // mock the add to balance adding eth back to the terminal (need to deal eth as this transfer really occurs in test) - vm.deal(address(delegate), 1 ether); - vm.mockCall( - address(jbxTerminal), - abi.encodeCall( - IJBPaymentTerminal(address(jbxTerminal)).addToBalanceOf, - (didPayData.projectId, 1 ether, address(randomTerminalToken), "", "") - ), - "" - ); - vm.expectCall( - address(jbxTerminal), - abi.encodeCall( - IJBPaymentTerminal(address(jbxTerminal)).addToBalanceOf, - (didPayData.projectId, 1 ether, address(randomTerminalToken), "", "") - ) - ); - - // Mock the approval for the addToBalance - vm.mockCall( - address(randomTerminalToken), - abi.encodeCall(randomTerminalToken.approve, (address(jbxTerminal), 1 ether)), - abi.encode(true) - ); - vm.expectCall( - address(randomTerminalToken), abi.encodeCall(randomTerminalToken.approve, (address(jbxTerminal), 1 ether)) - ); - - // Mock the no leftover - vm.mockCall( - address(randomTerminalToken), - abi.encodeCall(randomTerminalToken.balanceOf, (address(delegate))), - abi.encode(0) - ); - vm.expectCall(address(randomTerminalToken), abi.encodeCall(randomTerminalToken.balanceOf, (address(delegate)))); - - // expect event - vm.expectEmit(true, true, true, true); - emit BuybackDelegate_Mint(didPayData.projectId); - - vm.prank(address(jbxTerminal)); - delegate.didPay(didPayData); - } - - /** - * @notice Test didPay revert if wrong caller - */ - function test_didPay_revertIfWrongCaller(address _notTerminal) public { - vm.assume(_notTerminal != address(jbxTerminal)); - - // mock call to fail at the authorization check since directory has no bytecode - vm.mockCall( - address(directory), - abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(_notTerminal)))), - abi.encode(false) - ); - vm.expectCall( - address(directory), - abi.encodeCall(directory.isTerminalOf, (didPayData.projectId, IJBPaymentTerminal(address(_notTerminal)))) - ); - - vm.expectRevert(abi.encodeWithSelector(IJBGenericBuybackDelegate.JuiceBuyback_Unauthorized.selector)); - - vm.prank(_notTerminal); - delegate.didPay(didPayData); - } - - /** - * @notice Test uniswapCallback - * - * @dev 2 branches: project token is 0 or 1 in the pool slot0 - */ - function test_uniswapCallback() public { - int256 _delta0 = -1 ether; - int256 _delta1 = 1 ether; - uint256 _minReceived = 25; - - /** - * First branch - */ - delegate = new ForTest_JBGenericBuybackDelegate({ - _weth: weth, - _factory: _uniswapFactory, - _directory: directory, - _controller: controller, - _id: bytes4(hex'69') - }); - - delegate.ForTest_initPool(pool, projectId, secondsAgo, twapDelta, address(projectToken), address(weth)); - - // If project is token0, then received is delta0 (the negative value) - (_delta0, _delta1) = address(projectToken) < address(weth) ? (_delta0, _delta1) : (_delta1, _delta0); - - // mock and expect weth calls, this should transfer from delegate to pool (positive delta in the callback) - vm.mockCall(address(weth), abi.encodeCall(weth.deposit, ()), ""); - vm.expectCall(address(weth), abi.encodeCall(weth.deposit, ())); - - vm.mockCall( - address(weth), - abi.encodeCall( - weth.transfer, (address(pool), uint256(address(projectToken) < address(weth) ? _delta1 : _delta0)) - ), - abi.encode(true) - ); - vm.expectCall( - address(weth), - abi.encodeCall( - weth.transfer, (address(pool), uint256(address(projectToken) < address(weth) ? _delta1 : _delta0)) - ) - ); - - vm.prank(address(pool)); - delegate.uniswapV3SwapCallback( - _delta0, _delta1, abi.encode(projectId, _minReceived, JBTokens.ETH, projectToken) - ); - - /** - * Second branch - */ - - // Invert both contract addresses, to swap token0 and token1 (this will NOT modify the pool address) - (projectToken, weth) = (JBToken(address(weth)), IWETH9(address(projectToken))); - - delegate = new ForTest_JBGenericBuybackDelegate({ - _weth: weth, - _factory: _uniswapFactory, - _directory: directory, - _controller: controller, - _id: bytes4(hex'69') - }); - - delegate.ForTest_initPool(pool, projectId, secondsAgo, twapDelta, address(projectToken), address(weth)); - - vm.mockCall( - address(weth), - abi.encodeCall( - weth.transfer, (address(pool), uint256(address(projectToken) < address(weth) ? _delta1 : _delta0)) - ), - abi.encode(true) - ); - vm.expectCall( - address(weth), - abi.encodeCall( - weth.transfer, (address(pool), uint256(address(projectToken) < address(weth) ? _delta1 : _delta0)) - ) - ); - - vm.prank(address(pool)); - delegate.uniswapV3SwapCallback(_delta0, _delta1, abi.encode(projectId, _minReceived, weth, projectToken)); - } - - /** - * @notice Test uniswapCallback revert if wrong caller - */ - function test_uniswapCallback_revertIfWrongCaller() public { - int256 _delta0 = -1 ether; - int256 _delta1 = 1 ether; - uint256 _minReceived = 25; - - vm.expectRevert(abi.encodeWithSelector(IJBGenericBuybackDelegate.JuiceBuyback_Unauthorized.selector)); - delegate.uniswapV3SwapCallback(_delta0, _delta1, abi.encode(projectId, _minReceived, weth, projectToken)); - } - - /** - * @notice Test uniswapCallback revert if max slippage - */ - function test_uniswapCallback_revertIfMaxSlippage() public { - int256 _delta0 = -1 ether; - int256 _delta1 = 1 ether; - uint256 _minReceived = 25 ether; - - // If project is token0, then received is delta0 (the negative value) - (_delta0, _delta1) = address(projectToken) < address(weth) ? (_delta0, _delta1) : (_delta1, _delta0); - - vm.prank(address(pool)); - vm.expectRevert(abi.encodeWithSelector(IJBGenericBuybackDelegate.JuiceBuyback_MaximumSlippage.selector)); - delegate.uniswapV3SwapCallback(_delta0, _delta1, abi.encode(projectId, _minReceived, weth, projectToken)); - } - - function test_setPoolFor(uint256 _secondsAgo, uint256 _twapDelta, address _terminalToken, address _projectToken, uint24 _fee) public { - vm.assume(_terminalToken != address(0) && _projectToken != address(0) && _fee != 0); - vm.assume(_terminalToken != _projectToken); - - uint256 _MIN_SECONDS_AGO = delegate.MIN_SECONDS_AGO(); - uint256 _MAX_SECONDS_AGO = delegate.MAX_SECONDS_AGO(); - - uint256 _MIN_TWAP_DELTA = delegate.MIN_TWAP_DELTA(); - uint256 _MAX_TWAP_DELTA = delegate.MAX_TWAP_DELTA(); - - _twapDelta = bound(_twapDelta, _MIN_TWAP_DELTA, _MAX_TWAP_DELTA); - _secondsAgo = bound(_secondsAgo, _MIN_SECONDS_AGO, _MAX_SECONDS_AGO); - - address _pool = PoolAddress.computeAddress( - delegate.UNISWAP_V3_FACTORY(), - PoolAddress.getPoolKey(_terminalToken, _projectToken, _fee) - ); - - vm.mockCall(address(tokenStore), abi.encodeCall(tokenStore.tokenOf, (projectId)), abi.encode(_projectToken)); - - // check: correct events? - vm.expectEmit(true, true, true, true); - emit BuybackDelegate_SecondsAgoChanged(projectId, 0, _secondsAgo); - - vm.expectEmit(true, true, true, true); - emit BuybackDelegate_TwapDeltaChanged(projectId, 0, _twapDelta); - - vm.expectEmit(true, true, true, true); - emit BuybackDelegate_PoolAdded(projectId, _terminalToken, address(_pool)); - - vm.prank(owner); - address _newPool = address(delegate.setPoolFor(projectId, _fee, uint32(_secondsAgo), _twapDelta, _terminalToken)); - - // Check: correct params stored? - assertEq(delegate.secondsAgoOf(projectId), _secondsAgo); - assertEq(delegate.twapDeltaOf(projectId), _twapDelta); - assertEq(address(delegate.poolOf(projectId, _terminalToken)), _pool); - assertEq(_newPool, _pool); - - } - - /** - * @notice Test increase seconds ago - */ - function test_changeSecondsAgo(uint256 _newValue) public { - uint256 _MAX_SECONDS_AGO = delegate.MAX_SECONDS_AGO(); - uint256 _MIN_SECONDS_AGO = delegate.MIN_SECONDS_AGO(); - - _newValue = bound(_newValue, _MIN_SECONDS_AGO, _MAX_SECONDS_AGO); - - // check: correct event? - vm.expectEmit(true, true, true, true); - emit BuybackDelegate_SecondsAgoChanged(projectId, delegate.secondsAgoOf(projectId), _newValue); - - // Test: change seconds ago - vm.prank(owner); - delegate.changeSecondsAgo(projectId, uint32(_newValue)); - - // Check: correct seconds ago? - assertEq(delegate.secondsAgoOf(projectId), _newValue); - } - - /** - * @notice Test increase seconds ago revert if wrong caller - */ - function test_changeSecondsAgo_revertIfWrongCaller(address _notOwner) public { - vm.assume(owner != _notOwner); - - vm.mockCall( - address(operatorStore), - abi.encodeCall( - operatorStore.hasPermission, (_notOwner, owner, projectId, JBBuybackDelegateOperations.SET_POOL_PARAMS) - ), - abi.encode(false) - ); - vm.expectCall( - address(operatorStore), - abi.encodeCall( - operatorStore.hasPermission, (_notOwner, owner, projectId, JBBuybackDelegateOperations.SET_POOL_PARAMS) - ) - ); - - vm.mockCall( - address(operatorStore), - abi.encodeCall( - operatorStore.hasPermission, (_notOwner, owner, 0, JBBuybackDelegateOperations.SET_POOL_PARAMS) - ), - abi.encode(false) - ); - vm.expectCall( - address(operatorStore), - abi.encodeCall( - operatorStore.hasPermission, (_notOwner, owner, 0, JBBuybackDelegateOperations.SET_POOL_PARAMS) - ) - ); - - // check: revert? - vm.expectRevert(abi.encodeWithSignature("UNAUTHORIZED()")); - - // Test: change seconds ago (left uninit/at 0) - vm.startPrank(_notOwner); - delegate.changeSecondsAgo(projectId, 999); - } - - /** - * @notice Test increase seconds ago reverting on boundary - */ - function test_changeSecondsAgo_revertIfNewValueTooBigOrTooLow(uint256 _newValueSeed) public { - uint256 _MAX_SECONDS_AGO = delegate.MAX_SECONDS_AGO(); - uint256 _MIN_SECONDS_AGO = delegate.MIN_SECONDS_AGO(); - - uint256 _newValue = bound(_newValueSeed, _MAX_SECONDS_AGO + 1, type(uint32).max); - - // Check: revert? - vm.expectRevert(abi.encodeWithSelector(IJBGenericBuybackDelegate.JuiceBuyback_InvalidTwapPeriod.selector)); - - // Test: try to change seconds ago - vm.prank(owner); - delegate.changeSecondsAgo(projectId, uint32(_newValue)); - - _newValue = bound(_newValueSeed, 0, _MIN_SECONDS_AGO - 1); - - // Check: revert? - vm.expectRevert(abi.encodeWithSelector(IJBGenericBuybackDelegate.JuiceBuyback_InvalidTwapPeriod.selector)); - - // Test: try to change seconds ago - vm.prank(owner); - delegate.changeSecondsAgo(projectId, uint32(_newValue)); - } - - /** - * @notice Test set twap delta - */ - function test_setTwapDelta(uint256 _newDelta) public { - uint256 _MIN_TWAP_DELTA = delegate.MIN_TWAP_DELTA(); - uint256 _MAX_TWAP_DELTA = delegate.MAX_TWAP_DELTA(); - _newDelta = bound(_newDelta, _MIN_TWAP_DELTA, _MAX_TWAP_DELTA); - - // Check: correct event? - vm.expectEmit(true, true, true, true); - emit BuybackDelegate_TwapDeltaChanged(projectId, delegate.twapDeltaOf(projectId), _newDelta); - - // Test: set the twap - vm.prank(owner); - delegate.setTwapDelta(projectId, _newDelta); - - // Check: correct twap? - assertEq(delegate.twapDeltaOf(projectId), _newDelta); - } - - /** - * @notice Test set twap delta reverts if wrong caller - */ - function test_setTwapDelta_revertWrongCaller(address _notOwner) public { - vm.assume(owner != _notOwner); - - vm.mockCall( - address(operatorStore), - abi.encodeCall( - operatorStore.hasPermission, (_notOwner, owner, projectId, JBBuybackDelegateOperations.SET_POOL_PARAMS) - ), - abi.encode(false) - ); - vm.expectCall( - address(operatorStore), - abi.encodeCall( - operatorStore.hasPermission, (_notOwner, owner, projectId, JBBuybackDelegateOperations.SET_POOL_PARAMS) - ) - ); - - vm.mockCall( - address(operatorStore), - abi.encodeCall( - operatorStore.hasPermission, (_notOwner, owner, 0, JBBuybackDelegateOperations.SET_POOL_PARAMS) - ), - abi.encode(false) - ); - vm.expectCall( - address(operatorStore), - abi.encodeCall( - operatorStore.hasPermission, (_notOwner, owner, 0, JBBuybackDelegateOperations.SET_POOL_PARAMS) - ) - ); - - // check: revert? - vm.expectRevert(abi.encodeWithSignature("UNAUTHORIZED()")); - - // Test: set the twap - vm.prank(_notOwner); - delegate.setTwapDelta(projectId, 1); - } - - /** - * @notice Test set twap delta - */ - function test_setTwapDelta_revertIfInvalidNewValue(uint256 _newDeltaSeed) public { - uint256 _MIN_TWAP_DELTA = delegate.MIN_TWAP_DELTA(); - uint256 _MAX_TWAP_DELTA = delegate.MAX_TWAP_DELTA(); - - uint256 _newDelta = bound(_newDeltaSeed, 0, _MIN_TWAP_DELTA - 1); - - vm.expectRevert(abi.encodeWithSelector(IJBGenericBuybackDelegate.JuiceBuyback_InvalidTwapDelta.selector)); - - // Test: set the twap - vm.prank(owner); - delegate.setTwapDelta(projectId, _newDelta); - - _newDelta = bound(_newDeltaSeed, _MAX_TWAP_DELTA + 1, type(uint256).max); - - vm.expectRevert(abi.encodeWithSelector(IJBGenericBuybackDelegate.JuiceBuyback_InvalidTwapDelta.selector)); - - // Test: set the twap - vm.prank(owner); - delegate.setTwapDelta(projectId, _newDelta); - } - - /** - * @notice Test sweep - */ - function test_sweep_erc20(uint256 _delegateLeftover, uint256 _dudeLeftover) public { - _dudeLeftover = bound(_dudeLeftover, 0, _delegateLeftover); - - // Store the delegate leftover - stdstore.target(address(delegate)).sig("totalSweepBalance(address)").with_key(address(weth)).checked_write( - _delegateLeftover - ); - - // Store the dude leftover - stdstore.target(address(delegate)).sig("sweepBalanceOf(address,address)").with_key(dude).with_key(address(weth)) - .checked_write(_dudeLeftover); - - if (_dudeLeftover > 0) { - vm.mockCall(address(weth), abi.encodeCall(weth.transfer, (dude, _dudeLeftover)), abi.encode(true)); - vm.expectCall(address(weth), abi.encodeCall(weth.transfer, (dude, _dudeLeftover))); - } - - // Test: sweep - vm.prank(dude); - delegate.sweep(dude, address(weth)); - - // Check: correct overall sweep balance? - assertEq(delegate.totalSweepBalance(address(weth)), _delegateLeftover - _dudeLeftover); - - // Check: correct dude sweep balance - assertEq(delegate.sweepBalanceOf(dude, address(weth)), 0); - } - - /** - * @notice Test sweep - */ - function test_sweep_eth(uint256 _delegateLeftover, uint256 _dudeLeftover) public { - _dudeLeftover = bound(_dudeLeftover, 0, _delegateLeftover); - - // Add the ETH - vm.deal(address(delegate), _delegateLeftover); - - // Store the delegate leftover - stdstore.target(address(delegate)).sig("totalSweepBalance(address)").with_key(JBTokens.ETH).checked_write( - _delegateLeftover - ); - - // Store the dude leftover - stdstore.target(address(delegate)).sig("sweepBalanceOf(address,address)").with_key(dude).with_key(JBTokens.ETH) - .checked_write(_dudeLeftover); - - uint256 _balanceBeforeSweep = dude.balance; - - // Test: sweep - vm.prank(dude); - delegate.sweep(dude, JBTokens.ETH); - - uint256 _balanceAfterSweep = dude.balance; - uint256 _sweptAmount = _balanceAfterSweep - _balanceBeforeSweep; - - // Check: correct overall sweep balance? - assertEq(delegate.totalSweepBalance(JBTokens.ETH), _delegateLeftover - _dudeLeftover); - - // Check: correct dude sweep balance - assertEq(delegate.sweepBalanceOf(dude, JBTokens.ETH), 0); - - // Check: correct swept balance - assertEq(_sweptAmount, _dudeLeftover); - } - - /** - * @notice Test sweep revert if transfer fails - */ - function test_sweep_revertIfTransferFails() public { - // Store the delegate total leftover - stdstore.target(address(delegate)).sig("totalSweepBalance(address)").with_key(JBTokens.ETH).checked_write( - 1 ether - ); - - // Store the dude leftover - stdstore.target(address(delegate)).sig("sweepBalanceOf(address,address)").with_key(dude).with_key(JBTokens.ETH) - .checked_write(1 ether); - - // Deal enough ETH - vm.deal(address(delegate), 1 ether); - - // no fallback -> will revert - vm.etch(dude, "6969"); - - // Check: revert? - vm.prank(dude); - vm.expectRevert(abi.encodeWithSelector(IJBGenericBuybackDelegate.JuiceBuyback_TransferFailed.selector)); - delegate.sweep(dude, JBTokens.ETH); - } - -} - -contract ForTest_JBGenericBuybackDelegate is JBGenericBuybackDelegate { - constructor(IWETH9 _weth, address _factory, IJBDirectory _directory, IJBController3_1 _controller, bytes4 _id) - JBGenericBuybackDelegate(_weth, _factory, _directory, _controller, _id) - {} - - function ForTest_getQuote( - uint256 _projectId, - IJBPaymentTerminal _terminal, - address _projectToken, - uint256 _amountIn - ) external view returns (uint256 _amountOut) { - return _getQuote(_projectId, _terminal, _projectToken, _amountIn); - } - - function ForTest_initPool( - IUniswapV3Pool _pool, - uint256 _projectId, - uint32 _secondsAgo, - uint256 _twapDelta, - address _projectToken, - address _terminalToken - ) external { - twapParamsOf[_projectId] = _twapDelta << 128 | _secondsAgo; - projectTokenOf[_projectId] = _projectToken; - poolOf[_projectId][_terminalToken] = _pool; - } -} diff --git a/contracts/test/helpers/PoolAddress.sol b/contracts/test/helpers/PoolAddress.sol index 4d403c8..a402dab 100644 --- a/contracts/test/helpers/PoolAddress.sol +++ b/contracts/test/helpers/PoolAddress.sol @@ -18,11 +18,7 @@ library PoolAddress { /// @param tokenB The second token of a pool, unsorted /// @param fee The fee level of the pool /// @return Poolkey The pool details with ordered token0 and token1 assignments - function getPoolKey( - address tokenA, - address tokenB, - uint24 fee - ) internal pure returns (PoolKey memory) { + function getPoolKey(address tokenA, address tokenB, uint24 fee) internal pure returns (PoolKey memory) { if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); return PoolKey({token0: tokenA, token1: tokenB, fee: fee}); } @@ -38,7 +34,7 @@ library PoolAddress { uint256( keccak256( abi.encodePacked( - hex'ff', + hex"ff", factory, keccak256(abi.encode(key.token0, key.token1, key.fee)), POOL_INIT_CODE_HASH diff --git a/contracts/test/helpers/TestBaseWorkflowV3.sol b/contracts/test/helpers/TestBaseWorkflowV3.sol index 9be6c56..fcc5f02 100644 --- a/contracts/test/helpers/TestBaseWorkflowV3.sol +++ b/contracts/test/helpers/TestBaseWorkflowV3.sol @@ -32,21 +32,29 @@ import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBPaymentTerminal import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBToken.sol"; import "@jbx-protocol/juice-contracts-v3/contracts/libraries/JBConstants.sol"; -import "./AccessJBLib.sol"; +import "@jbx-protocol/juice-contracts-v3/contracts/interfaces/IJBSingleTokenPaymentTerminalStore.sol"; import "@paulrberg/contracts/math/PRBMath.sol"; +import "./AccessJBLib.sol"; +import "../../interfaces/external/IWETH9.sol"; +import "../../JBBuybackDelegate.sol"; + + + // Base contract for Juicebox system tests. // // Provides common functionality, such as deploying contracts on test setup for v3. contract TestBaseWorkflowV3 is Test { + using stdStorage for StdStorage; + //*********************************************************************// // -------------------- internal stored properties ------------------- // //*********************************************************************// // Multisig address used for testing. - address internal _multisig = address(123); - address internal _beneficiary = address(69420); + address internal _multisig = makeAddr('mooltichig'); + address internal _beneficiary = makeAddr('benefishary'); JBOperatorStore internal _jbOperatorStore; JBProjects internal _jbProjects; @@ -61,6 +69,32 @@ contract TestBaseWorkflowV3 is Test { JBETHPaymentTerminal3_1_1 internal _jbETHPaymentTerminal; AccessJBLib internal _accessJBLib; + JBBuybackDelegate _delegate; + + uint256 _projectId; + uint256 reservedRate = 4500; + uint256 weight = 10 ether ; // Minting 10 token per eth + uint32 cardinality = 1000; + uint256 twapDelta = 500; + + JBProjectMetadata _projectMetadata; + JBFundingCycleData _data; + JBFundingCycleData _dataReconfiguration; + JBFundingCycleData _dataWithoutBallot; + JBFundingCycleMetadata _metadata; + JBFundAccessConstraints[] _fundAccessConstraints; // Default empty + IJBPaymentTerminal[] _terminals; // Default empty + + // Use the L1 UniswapV3Pool jbx/eth 1% fee for create2 magic + // IUniswapV3Pool pool = IUniswapV3Pool(0x48598Ff1Cee7b4d31f8f9050C2bbAE98e17E6b17); + IJBToken jbx = IJBToken(0x3abF2A4f8452cCC2CF7b4C1e4663147600646f66); + IWETH9 weth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + address _uniswapFactory = 0x1F98431c8aD98523631AE4a59f267346ea31F984; + uint24 fee = 10000; + + IUniswapV3Pool pool; + + //*********************************************************************// // ------------------------- internal views -------------------------- // //*********************************************************************// @@ -78,6 +112,14 @@ contract TestBaseWorkflowV3 is Test { // Labels vm.label(_multisig, "projectOwner"); vm.label(_beneficiary, "beneficiary"); + vm.label(address(pool), "uniswapPool"); + vm.label(address(_uniswapFactory), "uniswapFactory"); + vm.label(address(weth), "$WETH"); + vm.label(address(jbx), "$JBX"); + + // mock + vm.etch(address(pool), "0x69"); + vm.etch(address(weth), "0x69"); // JBOperatorStore _jbOperatorStore = new JBOperatorStore(); @@ -150,28 +192,82 @@ contract TestBaseWorkflowV3 is Test { _multisig ); vm.label(address(_jbETHPaymentTerminal), "JBETHPaymentTerminal"); - } - //https://ethereum.stackexchange.com/questions/24248/how-to-calculate-an-ethereum-contracts-address-during-its-creation-using-the-so - function addressFrom(address _origin, uint256 _nonce) internal pure returns (address _address) { - bytes memory data; - if (_nonce == 0x00) { - data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, bytes1(0x80)); - } else if (_nonce <= 0x7f) { - data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, uint8(_nonce)); - } else if (_nonce <= 0xff) { - data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), _origin, bytes1(0x81), uint8(_nonce)); - } else if (_nonce <= 0xffff) { - data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), _origin, bytes1(0x82), uint16(_nonce)); - } else if (_nonce <= 0xffffff) { - data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), _origin, bytes1(0x83), uint24(_nonce)); - } else { - data = abi.encodePacked(bytes1(0xda), bytes1(0x94), _origin, bytes1(0x84), uint32(_nonce)); - } - bytes32 hash = keccak256(data); - assembly { - mstore(0, hash) - _address := mload(0) - } + // Deploy the delegate + _delegate = new JBBuybackDelegate({ + _weth: weth, + _factory: _uniswapFactory, + _directory: IJBDirectory(address(_jbDirectory)), + _controller: _jbController, + _delegateId: bytes4(hex'69') + }); + + _projectMetadata = JBProjectMetadata({content: "myIPFSHash", domain: 1}); + + _data = JBFundingCycleData({ + duration: 6 days, + weight: weight, + discountRate: 0, + ballot: IJBFundingCycleBallot(address(0)) + }); + + _metadata = JBFundingCycleMetadata({ + global: JBGlobalFundingCycleMetadata({ + allowSetTerminals: false, + allowSetController: false, + pauseTransfers: false + }), + reservedRate: reservedRate, + redemptionRate: 5000, + ballotRedemptionRate: 0, + pausePay: false, + pauseDistributions: false, + pauseRedeem: false, + pauseBurn: false, + allowMinting: true, + preferClaimedTokenOverride: false, + allowTerminalMigration: false, + allowControllerMigration: false, + holdFees: false, + useTotalOverflowForRedemptions: false, + useDataSourceForPay: true, + useDataSourceForRedeem: false, + dataSource: address(_delegate), + metadata: 0 + }); + + _fundAccessConstraints.push( + JBFundAccessConstraints({ + terminal: _jbETHPaymentTerminal, + token: jbLibraries().ETHToken(), + distributionLimit: 2 ether, + overflowAllowance: type(uint232).max, + distributionLimitCurrency: 1, // Currency = ETH + overflowAllowanceCurrency: 1 + }) + ); + + _terminals = [_jbETHPaymentTerminal]; + + JBGroupedSplits[] memory _groupedSplits = new JBGroupedSplits[](1); // Default empty + + _projectId = _jbController.launchProjectFor( + _multisig, + _projectMetadata, + _data, + _metadata, + 0, // Start asap + _groupedSplits, + _fundAccessConstraints, + _terminals, + "" + ); + + vm.prank(_multisig); + _jbTokenStore.issueFor(_projectId, "jbx", "jbx"); + + vm.prank(_multisig); + pool = _delegate.setPoolFor(_projectId, fee, uint32(cardinality), twapDelta, address(weth)); + } } diff --git a/foundry.toml b/foundry.toml index 0a3f601..8fbdccc 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,4 +4,7 @@ out = 'out' libs = ['lib', 'node_modules'] fs_permissions = [{ access = "read", path = "./node_modules/@jbx-protocol/juice-contracts-v3/deployments/"}] # Get the deployment addresses for forking +[invariant] +fail_on_revert = false + # See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/lcov.info b/lcov.info index 7fb0110..64c63c8 100644 --- a/lcov.info +++ b/lcov.info @@ -1,4131 +1,477 @@ TN: -SF:contracts/JBXBuybackDelegate.sol -FN:165,JBXBuybackDelegate.payParams -FNDA:4,JBXBuybackDelegate.payParams -DA:174,4 -DA:177,4 -DA:180,4 -BRDA:180,0,0,- -BRDA:180,0,1,3 -DA:182,3 -DA:183,3 -DA:186,3 -DA:187,3 -DA:192,3 -DA:196,1 -FN:212,JBXBuybackDelegate.didPay -FNDA:3,JBXBuybackDelegate.didPay -DA:214,3 -BRDA:214,1,0,- -BRDA:214,1,1,3 -DA:217,3 -DA:218,3 -DA:221,3 -DA:222,3 -DA:225,3 -DA:226,3 -DA:229,3 -BRDA:229,2,0,- -BRDA:229,2,1,1 -DA:231,1 -DA:234,1 -BRDA:234,3,0,- -BRDA:234,3,1,1 -DA:235,2 -FN:245,JBXBuybackDelegate.uniswapV3SwapCallback -FNDA:1,JBXBuybackDelegate.uniswapV3SwapCallback -DA:251,1 -BRDA:251,4,0,- -BRDA:251,4,1,1 -DA:254,1 -DA:257,1 -DA:258,1 -DA:261,1 -BRDA:261,5,0,- -BRDA:261,5,1,1 -DA:264,1 -DA:265,0 -FN:268,JBXBuybackDelegate.redeemParams -FNDA:0,JBXBuybackDelegate.redeemParams -FN:296,JBXBuybackDelegate._swap -FNDA:1,JBXBuybackDelegate._swap -DA:298,1 -DA:313,1 -DA:319,1 -DA:322,1 -BRDA:322,6,0,- -BRDA:322,6,1,1 -DA:325,1 -BRDA:325,7,0,- -BRDA:325,7,1,1 -DA:326,1 -DA:329,1 -DA:338,1 -DA:348,1 -DA:350,1 -BRDA:350,8,0,- -BRDA:350,8,1,1 -DA:351,1 -DA:360,1 -FN:370,JBXBuybackDelegate._mint -FNDA:2,JBXBuybackDelegate._mint -DA:371,2 -DA:374,2 -DA:384,2 -DA:388,2 -FN:395,JBXBuybackDelegate.supportsInterface -FNDA:0,JBXBuybackDelegate.supportsInterface -DA:396,0 -FNF:7 -FNH:5 -LF:44 -LH:42 -BRF:18 -BRH:9 -end_of_record -TN: -SF:contracts/mock/MockAllocator.sol -FN:19,MockAllocator.allocate -FNDA:1,MockAllocator.allocate -DA:22,1 -DA:36,1 -DA:39,1 -BRDA:39,0,0,1 -BRDA:39,0,1,- -FN:43,MockAllocator.supportsInterface -FNDA:0,MockAllocator.supportsInterface -DA:49,0 -FNF:2 -FNH:1 -LF:4 -LH:3 -BRF:2 -BRH:1 -end_of_record -TN: -SF:contracts/test/helpers/AccessJBLib.sol -FN:9,AccessJBLib.ETH -FNDA:0,AccessJBLib.ETH -DA:10,0 -FN:13,AccessJBLib.USD -FNDA:0,AccessJBLib.USD -DA:14,0 -FN:17,AccessJBLib.ETHToken -FNDA:1,AccessJBLib.ETHToken -DA:18,1 -FN:21,AccessJBLib.MAX_FEE -FNDA:0,AccessJBLib.MAX_FEE -DA:22,0 -FN:25,AccessJBLib.SPLITS_TOTAL_PERCENT -FNDA:0,AccessJBLib.SPLITS_TOTAL_PERCENT -DA:26,0 -FNF:5 -FNH:1 -LF:5 -LH:1 -BRF:0 -BRH:0 -end_of_record -TN: -SF:contracts/test/helpers/TestBaseWorkflowV3.sol -FN:83,TestBaseWorkflowV3.multisig -FNDA:0,TestBaseWorkflowV3.multisig -DA:84,0 -FN:87,TestBaseWorkflowV3.beneficiary -FNDA:0,TestBaseWorkflowV3.beneficiary -DA:88,0 -FN:91,TestBaseWorkflowV3.jbOperatorStore -FNDA:0,TestBaseWorkflowV3.jbOperatorStore -DA:92,0 -FN:95,TestBaseWorkflowV3.jbProjects -FNDA:0,TestBaseWorkflowV3.jbProjects -DA:96,0 -FN:99,TestBaseWorkflowV3.jbPrices -FNDA:0,TestBaseWorkflowV3.jbPrices -DA:100,0 -FN:103,TestBaseWorkflowV3.jbDirectory -FNDA:0,TestBaseWorkflowV3.jbDirectory -DA:104,0 -FN:107,TestBaseWorkflowV3.jbFundingCycleStore -FNDA:0,TestBaseWorkflowV3.jbFundingCycleStore -DA:108,0 -FN:111,TestBaseWorkflowV3.jbTokenStore -FNDA:0,TestBaseWorkflowV3.jbTokenStore -DA:112,0 -FN:115,TestBaseWorkflowV3.jbSplitsStore -FNDA:0,TestBaseWorkflowV3.jbSplitsStore -DA:116,0 -FN:119,TestBaseWorkflowV3.jbController -FNDA:0,TestBaseWorkflowV3.jbController -DA:120,0 -FN:123,TestBaseWorkflowV3.jbPaymentTerminalStore -FNDA:0,TestBaseWorkflowV3.jbPaymentTerminalStore -DA:124,0 -FN:127,TestBaseWorkflowV3.jbETHPaymentTerminal -FNDA:0,TestBaseWorkflowV3.jbETHPaymentTerminal -DA:128,0 -FN:131,TestBaseWorkflowV3.jbLibraries -FNDA:0,TestBaseWorkflowV3.jbLibraries -DA:132,0 -FN:140,TestBaseWorkflowV3.setUp -FNDA:0,TestBaseWorkflowV3.setUp -DA:142,0 -DA:143,0 -DA:146,0 -DA:147,0 -DA:150,0 -DA:151,0 -DA:154,0 -DA:155,0 -DA:157,0 -DA:160,0 -DA:161,0 -DA:164,0 -DA:165,0 -DA:168,0 -DA:169,0 -DA:172,0 -DA:173,0 -DA:176,0 -DA:184,0 -DA:186,0 -DA:187,0 -DA:190,0 -DA:195,0 -DA:198,0 -DA:201,0 -DA:211,0 -FN:215,TestBaseWorkflowV3.addressFrom -FNDA:0,TestBaseWorkflowV3.addressFrom -DA:216,0 -DA:217,0 -BRDA:217,0,0,- -BRDA:217,0,1,- -DA:218,0 -BRDA:218,1,0,- -BRDA:218,1,1,- -DA:219,0 -DA:220,0 -BRDA:220,2,0,- -BRDA:220,2,1,- -DA:221,0 -DA:222,0 -BRDA:222,3,0,- -BRDA:222,3,1,- -DA:223,0 -DA:224,0 -BRDA:224,4,0,- -BRDA:224,4,1,- -DA:225,0 -DA:226,0 -DA:227,0 -DA:230,0 -FNF:15 -FNH:0 -LF:52 -LH:0 -BRF:10 -BRH:0 -end_of_record -TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/JBController.sol -FN:157,JBController.distributionLimitOf -FNDA:1,JBController.distributionLimitOf -DA:164,1 -DA:167,1 -FN:185,JBController.overflowAllowanceOf -FNDA:0,JBController.overflowAllowanceOf -DA:192,0 -DA:195,0 -FN:207,JBController.reservedTokenBalanceOf -FNDA:3,JBController.reservedTokenBalanceOf -DA:213,3 -FN:230,JBController.totalOutstandingTokensOf -FNDA:0,JBController.totalOutstandingTokensOf -DA:237,0 -DA:240,0 -DA:247,0 -FN:259,JBController.getFundingCycleOf -FNDA:0,JBController.getFundingCycleOf -DA:265,0 -DA:266,0 -FN:279,JBController.latestConfiguredFundingCycleOf -FNDA:0,JBController.latestConfiguredFundingCycleOf -DA:289,0 -DA:290,0 -FN:302,JBController.currentFundingCycleOf -FNDA:1,JBController.currentFundingCycleOf -DA:308,1 -DA:309,1 -FN:321,JBController.queuedFundingCycleOf -FNDA:0,JBController.queuedFundingCycleOf -DA:327,0 -DA:328,0 -FN:344,JBController.supportsInterface -FNDA:0,JBController.supportsInterface -DA:351,0 -FN:411,JBController.launchProjectFor -FNDA:2,JBController.launchProjectFor -DA:423,2 -DA:426,2 -DA:429,2 -DA:432,2 -DA:442,2 -BRDA:442,0,0,- -BRDA:442,0,1,2 -DA:444,2 -FN:468,JBController.launchFundingCyclesFor -FNDA:0,JBController.launchFundingCyclesFor -DA:485,0 -BRDA:485,1,0,- -BRDA:485,1,1,- -DA:486,0 -DA:489,0 -DA:492,0 -DA:502,0 -BRDA:502,2,0,- -BRDA:502,2,1,- -DA:504,0 -FN:524,JBController.reconfigureFundingCyclesOf -FNDA:0,JBController.reconfigureFundingCyclesOf -DA:540,0 -DA:549,0 -FN:568,JBController.mintTokensOf -FNDA:5,JBController.mintTokensOf -DA:577,5 -BRDA:577,3,0,- -BRDA:577,3,1,5 -DA:581,5 -DA:586,5 -DA:589,5 -DA:599,5 -BRDA:598,4,0,- -BRDA:598,4,1,5 -DA:602,0 -DA:605,5 -DA:608,5 -DA:613,5 -BRDA:613,5,0,- -BRDA:613,5,1,5 -DA:615,0 -DA:620,5 -DA:626,5 -BRDA:626,6,0,1 -BRDA:626,6,1,5 -DA:628,1 -DA:633,5 -DA:636,5 -FN:660,JBController.burnTokensOf -FNDA:2,JBController.burnTokensOf -DA:678,2 -BRDA:678,7,0,- -BRDA:678,7,1,2 -DA:681,2 -DA:685,2 -BRDA:684,8,0,- -BRDA:684,8,1,2 -DA:687,0 -DA:690,2 -DA:695,2 -DA:697,2 -FN:709,JBController.distributeReservedTokensOf -FNDA:0,JBController.distributeReservedTokensOf -DA:715,0 -FN:728,JBController.prepForMigrationOf -FNDA:0,JBController.prepForMigrationOf -DA:730,0 -BRDA:730,9,0,- -BRDA:730,9,1,- -DA:731,0 -DA:734,0 -DA:736,0 -FN:749,JBController.migrate -FNDA:0,JBController.migrate -DA:756,0 -DA:759,0 -BRDA:759,10,0,- -BRDA:759,10,1,- -DA:762,0 -DA:765,0 -BRDA:765,11,0,- -BRDA:765,11,1,- -DA:769,0 -BRDA:768,12,0,- -BRDA:768,12,1,- -DA:771,0 -DA:774,0 -DA:777,0 -DA:779,0 -FN:795,JBController._distributeReservedTokensOf -FNDA:0,JBController._distributeReservedTokensOf -DA:800,0 -DA:803,0 -DA:806,0 -DA:809,0 -DA:816,0 -DA:819,0 -DA:822,0 -DA:832,0 -BRDA:832,13,0,- -BRDA:832,13,1,- -DA:833,0 -DA:835,0 -FN:858,JBController._distributeToReservedTokenSplitsOf -FNDA:0,JBController._distributeToReservedTokenSplitsOf -DA:865,0 -DA:868,0 -DA:871,0 -DA:874,0 -DA:876,0 -DA:879,0 -DA:886,0 -BRDA:886,14,0,- -BRDA:886,14,1,- -DA:887,0 -DA:904,0 -BRDA:904,15,0,- -BRDA:904,15,1,- -DA:905,0 -DA:917,0 -DA:920,0 -DA:930,0 -FN:948,JBController._configure -FNDA:2,JBController._configure -DA:957,2 -BRDA:957,16,0,- -BRDA:957,16,1,2 -DA:960,2 -BRDA:960,17,0,- -BRDA:960,17,1,2 -DA:961,0 -DA:964,2 -BRDA:964,18,0,- -BRDA:964,18,1,2 -DA:965,0 -DA:968,2 -DA:976,2 -DA:979,2 -DA:980,2 -DA:983,2 -BRDA:983,19,0,- -BRDA:983,19,1,2 -DA:986,2 -BRDA:986,20,0,- -BRDA:986,20,1,2 -DA:987,0 -DA:990,2 -BRDA:990,21,0,- -BRDA:990,21,1,2 -DA:993,2 -BRDA:993,22,0,- -BRDA:993,22,1,2 -DA:994,0 -DA:997,2 -BRDA:997,23,0,2 -BRDA:997,23,1,2 -DA:998,2 -DA:1005,2 -BRDA:1005,24,0,2 -BRDA:1005,24,1,2 -DA:1006,2 -DA:1012,2 -DA:1021,2 -DA:1025,2 -FN:1038,JBController._reservedTokenAmountFrom -FNDA:3,JBController._reservedTokenAmountFrom -DA:1044,3 -DA:1049,3 -BRDA:1049,25,0,- -BRDA:1049,25,1,3 -DA:1052,3 -BRDA:1052,26,0,- -BRDA:1052,26,1,3 -DA:1054,3 -FNF:21 -FNH:8 -LF:116 -LH:52 -BRF:54 -BRH:21 -end_of_record -TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/JBDirectory.sol -FN:105,JBDirectory.terminalsOf -FNDA:0,JBDirectory.terminalsOf -DA:111,0 -FN:126,JBDirectory.primaryTerminalOf -FNDA:1,JBDirectory.primaryTerminalOf -DA:133,1 -DA:137,1 -BRDA:136,0,0,- -BRDA:136,0,1,1 -DA:139,0 -DA:142,1 -DA:145,1 -DA:147,1 -DA:150,1 -BRDA:150,1,0,1 -BRDA:150,1,1,- -DA:153,0 -DA:158,0 -FN:174,JBDirectory.isTerminalOf -FNDA:14,JBDirectory.isTerminalOf -DA:181,14 -DA:184,14 -DA:186,14 -BRDA:186,2,0,8 -BRDA:186,2,1,6 -DA:189,6 -DA:194,6 -FN:236,JBDirectory.setControllerOf -FNDA:2,JBDirectory.setControllerOf -DA:248,2 -BRDA:248,3,0,- -BRDA:248,3,1,2 -DA:251,2 -DA:255,2 -BRDA:254,4,0,- -BRDA:254,4,1,2 -DA:258,0 -DA:261,2 -DA:263,2 -FN:276,JBDirectory.setTerminalsOf -FNDA:2,JBDirectory.setTerminalsOf -DA:287,2 -DA:291,2 -BRDA:290,5,0,- -BRDA:290,5,1,2 -DA:292,0 -DA:295,2 -DA:298,2 -BRDA:298,6,0,- -BRDA:298,6,1,- -DA:299,0 -DA:300,0 -DA:301,0 -BRDA:301,7,0,- -BRDA:301,7,1,- -DA:304,0 -DA:309,0 -DA:313,2 -FN:331,JBDirectory.setPrimaryTerminalOf -FNDA:0,JBDirectory.setPrimaryTerminalOf -DA:341,0 -BRDA:341,8,0,- -BRDA:341,8,1,- -DA:344,0 -DA:347,0 -DA:349,0 -FN:367,JBDirectory.setIsAllowedToSetFirstController -FNDA:0,JBDirectory.setIsAllowedToSetFirstController -DA:373,0 -DA:375,0 -FN:389,JBDirectory._addTerminalIfNeeded -FNDA:0,JBDirectory._addTerminalIfNeeded -DA:391,0 -BRDA:391,9,0,- -BRDA:391,9,1,- -DA:394,0 -DA:398,0 -BRDA:397,10,0,- -BRDA:397,10,1,- -DA:399,0 -DA:402,0 -DA:404,0 -FNF:8 -FNH:4 -LF:44 -LH:21 -BRF:22 -BRH:7 -end_of_record -TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/JBETHPaymentTerminal.sol -FN:27,JBETHPaymentTerminal._balance -FNDA:0,JBETHPaymentTerminal._balance -DA:28,0 -FN:86,JBETHPaymentTerminal._transferFrom -FNDA:0,JBETHPaymentTerminal._transferFrom -DA:93,0 -FNF:2 -FNH:0 -LF:2 -LH:0 -BRF:0 -BRH:0 -end_of_record -TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/JBFundingCycleStore.sol -FN:87,JBFundingCycleStore.get -FNDA:0,JBFundingCycleStore.get -DA:93,0 -FN:105,JBFundingCycleStore.latestConfiguredOf -FNDA:0,JBFundingCycleStore.latestConfiguredOf -DA:112,0 -DA:115,0 -DA:118,0 -FN:137,JBFundingCycleStore.queuedOf -FNDA:0,JBFundingCycleStore.queuedOf -DA:144,0 -BRDA:144,0,0,- -BRDA:144,0,1,- -DA:147,0 -DA:150,0 -BRDA:150,1,0,- -BRDA:150,1,1,- -DA:151,0 -DA:153,0 -BRDA:153,2,0,- -BRDA:153,2,1,- -DA:156,0 -DA:159,0 -DA:163,0 -BRDA:163,3,0,- -BRDA:163,3,1,- -DA:164,0 -DA:168,0 -BRDA:168,4,0,- -BRDA:168,4,1,- -DA:172,0 -BRDA:172,5,0,- -BRDA:172,5,1,- -DA:175,0 -DA:178,0 -BRDA:178,6,0,- -BRDA:178,6,1,- -DA:181,0 -FN:195,JBFundingCycleStore.currentOf -FNDA:17,JBFundingCycleStore.currentOf -DA:202,17 -BRDA:202,7,0,2 -BRDA:202,7,1,15 -DA:205,15 -DA:208,15 -DA:211,15 -BRDA:211,8,0,15 -BRDA:211,8,1,- -DA:213,15 -DA:217,15 -BRDA:217,9,0,15 -BRDA:217,9,1,- -DA:221,0 -DA:225,0 -DA:228,0 -DA:231,0 -BRDA:231,10,0,- -BRDA:231,10,1,- -DA:232,0 -DA:236,0 -BRDA:236,11,0,- -BRDA:236,11,1,- -DA:239,0 -DA:242,0 -BRDA:242,12,0,- -BRDA:242,12,1,- -DA:245,0 -FN:256,JBFundingCycleStore.currentBallotStateOf -FNDA:0,JBFundingCycleStore.currentBallotStateOf -DA:258,0 -DA:261,0 -DA:263,0 -FN:300,JBFundingCycleStore.configureFor -FNDA:2,JBFundingCycleStore.configureFor -DA:307,2 -BRDA:307,13,0,- -BRDA:307,13,1,2 -DA:310,2 -BRDA:310,14,0,- -BRDA:310,14,1,2 +SF:contracts/JBBuybackDelegate.sol +FN:212,JBBuybackDelegate.payParams +FNDA:1026,JBBuybackDelegate.payParams +DA:219,1026 +DA:222,1026 +DA:224,1026 +DA:226,1026 +DA:227,1026 +DA:228,1026 +BRDA:228,0,0,514 +BRDA:228,0,1,1026 +DA:230,1026 +BRDA:230,1,0,514 +BRDA:230,1,1,512 +DA:233,514 +DA:235,512 +DA:239,1026 +BRDA:239,2,0,- +BRDA:239,2,1,239 +DA:241,239 +DA:242,239 +DA:248,239 +DA:252,787 +FN:265,JBBuybackDelegate.didPay +FNDA:770,JBBuybackDelegate.didPay +DA:267,770 +BRDA:267,3,0,256 +BRDA:267,3,1,514 +DA:268,256 +DA:271,514 +DA:274,514 +DA:277,514 +BRDA:277,4,0,256 +BRDA:277,4,1,514 +DA:280,514 +BRDA:280,5,0,258 +BRDA:280,5,1,514 +DA:281,258 +DA:283,258 +DA:285,258 +FN:294,JBBuybackDelegate.uniswapV3SwapCallback +FNDA:5,JBBuybackDelegate.uniswapV3SwapCallback +DA:296,5 +BRDA:296,6,0,1 +BRDA:296,6,1,4 +DA:299,4 +DA:302,4 +DA:303,4 +DA:304,4 +DA:307,4 +BRDA:307,7,0,2 +BRDA:307,7,1,2 +DA:308,2 +DA:312,2 DA:313,2 -BRDA:313,15,0,- -BRDA:313,15,1,2 -DA:316,2 -BRDA:316,16,0,2 -BRDA:316,16,1,2 -DA:319,2 -BRDA:319,17,0,- -BRDA:319,17,1,2 -DA:322,2 -BRDA:322,18,0,- -BRDA:322,18,1,- -DA:323,0 -DA:326,0 -BRDA:326,19,0,- -BRDA:326,19,1,- -DA:329,0 -DA:339,2 -DA:342,2 -DA:347,2 -BRDA:346,20,0,2 -BRDA:346,20,1,2 -DA:352,2 -DA:355,2 -DA:358,2 -DA:361,2 -DA:365,2 -BRDA:365,21,0,2 -BRDA:365,21,1,2 -DA:367,2 -DA:370,2 -FN:386,JBFundingCycleStore._configureIntrinsicPropertiesFor -FNDA:2,JBFundingCycleStore._configureIntrinsicPropertiesFor -DA:393,2 -BRDA:393,22,0,2 -BRDA:393,22,1,- -DA:395,2 -DA:399,0 -DA:402,0 -BRDA:402,23,0,- -BRDA:402,23,1,- -DA:404,0 -DA:407,0 -DA:409,0 -BRDA:409,24,0,- -BRDA:409,24,1,- -DA:412,0 -DA:415,0 -BRDA:415,25,0,- -BRDA:415,25,1,- -DA:419,0 -DA:423,0 -FN:443,JBFundingCycleStore._initFor -FNDA:2,JBFundingCycleStore._initFor -DA:451,2 -BRDA:451,26,0,2 -BRDA:451,26,1,- -DA:453,2 -DA:456,2 -DA:466,0 -DA:470,0 -DA:475,0 -DA:478,0 -DA:489,2 -DA:491,2 -FN:505,JBFundingCycleStore._packAndStoreIntrinsicPropertiesOf -FNDA:2,JBFundingCycleStore._packAndStoreIntrinsicPropertiesOf -DA:514,2 -DA:517,2 -DA:520,2 -DA:523,2 -DA:526,2 -FN:543,JBFundingCycleStore._standbyOf -FNDA:0,JBFundingCycleStore._standbyOf -DA:545,0 -DA:548,0 -DA:551,0 -BRDA:551,27,0,- -BRDA:551,27,1,- -DA:554,0 -BRDA:554,28,0,- -BRDA:554,28,1,- -DA:557,0 -DA:561,0 -BRDA:560,29,0,- -BRDA:560,29,1,- -DA:563,0 -FN:580,JBFundingCycleStore._eligibleOf -FNDA:15,JBFundingCycleStore._eligibleOf -DA:582,15 -DA:585,15 -DA:590,15 -BRDA:589,30,0,- -BRDA:589,30,1,15 -DA:591,0 -DA:594,15 -BRDA:594,31,0,15 -BRDA:594,31,1,- -DA:597,0 -DA:602,0 -BRDA:601,32,0,- -BRDA:601,32,1,- -DA:604,0 -DA:607,0 -FN:625,JBFundingCycleStore._mockFundingCycleBasedOn -FNDA:0,JBFundingCycleStore._mockFundingCycleBasedOn -DA:632,0 -DA:637,0 -DA:640,0 -DA:642,0 -FN:665,JBFundingCycleStore._deriveStartFrom -FNDA:0,JBFundingCycleStore._deriveStartFrom -DA:671,0 -BRDA:671,33,0,- -BRDA:671,33,1,- -DA:674,0 -DA:677,0 -BRDA:677,34,0,- -BRDA:677,34,1,- -DA:680,0 -DA:684,0 -DA:687,0 -FN:699,JBFundingCycleStore._deriveWeightFrom -FNDA:0,JBFundingCycleStore._deriveWeightFrom -DA:705,0 -BRDA:705,35,0,- -BRDA:705,35,1,- -DA:706,0 -DA:714,0 -DA:717,0 -BRDA:717,36,0,- -BRDA:717,36,1,- -DA:720,0 -DA:723,0 -DA:725,0 -DA:728,0 -DA:731,0 -DA:738,0 -BRDA:738,37,0,- -BRDA:738,37,1,- -DA:741,0 -FN:755,JBFundingCycleStore._deriveNumberFrom -FNDA:0,JBFundingCycleStore._deriveNumberFrom -DA:761,0 -BRDA:761,38,0,- -BRDA:761,38,1,- -DA:764,0 -DA:767,0 -FN:779,JBFundingCycleStore._isApproved -FNDA:15,JBFundingCycleStore._isApproved -DA:784,15 -FN:804,JBFundingCycleStore._ballotStateOf -FNDA:15,JBFundingCycleStore._ballotStateOf -DA:811,15 -BRDA:811,39,0,15 -BRDA:811,39,1,- -DA:814,0 -DA:820,0 -BRDA:820,40,0,- -BRDA:820,40,1,- -DA:821,0 -DA:824,0 -FN:836,JBFundingCycleStore._getStructFor -FNDA:36,JBFundingCycleStore._getStructFor -DA:842,36 -BRDA:842,41,0,32 -BRDA:842,41,1,36 -DA:844,32 -DA:846,32 -DA:849,32 -DA:851,32 -DA:853,32 -DA:855,32 -DA:857,32 -DA:860,32 -DA:862,32 -DA:864,32 -DA:866,32 -FNF:18 +FN:323,JBBuybackDelegate.redeemParams +FNDA:0,JBBuybackDelegate.redeemParams +DA:333,0 +FN:341,JBBuybackDelegate.increaseSecondsAgo +FNDA:768,JBBuybackDelegate.increaseSecondsAgo +DA:342,512 +DA:344,512 +BRDA:344,8,0,256 +BRDA:344,8,1,256 +DA:346,256 +DA:348,256 +FN:356,JBBuybackDelegate.setTwapDelta +FNDA:512,JBBuybackDelegate.setTwapDelta +DA:357,256 +DA:359,256 +DA:361,256 +FN:367,JBBuybackDelegate.sweep +FNDA:257,JBBuybackDelegate.sweep +DA:369,257 +DA:372,257 +BRDA:372,9,0,22 +BRDA:372,9,1,235 +DA:375,235 +DA:378,235 +DA:381,235 +DA:382,235 +BRDA:382,10,0,1 +BRDA:382,10,1,234 +DA:384,234 +FN:398,JBBuybackDelegate._getQuote +FNDA:768,JBBuybackDelegate._getQuote +DA:400,768 +DA:409,512 +DA:412,512 +DA:416,512 +FN:432,JBBuybackDelegate._swap +FNDA:514,JBBuybackDelegate._swap +DA:437,514 +DA:451,258 +DA:459,258 +DA:468,258 +FN:477,JBBuybackDelegate._mint +FNDA:256,JBBuybackDelegate._mint +DA:479,256 +DA:489,256 +DA:493,256 +FN:500,JBBuybackDelegate.supportsInterface +FNDA:0,JBBuybackDelegate.supportsInterface +DA:501,0 +DA:502,0 +FNF:11 FNH:9 -LF:138 -LH:52 -BRF:84 +LF:60 +LH:57 +BRF:22 BRH:21 end_of_record TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/JBOperatorStore.sol -FN:57,JBOperatorStore.hasPermission -FNDA:0,JBOperatorStore.hasPermission -DA:63,0 -BRDA:63,0,0,- -BRDA:63,0,1,- -DA:65,0 -FN:79,JBOperatorStore.hasPermissions -FNDA:0,JBOperatorStore.hasPermissions -DA:85,0 -DA:86,0 -DA:88,0 -BRDA:88,1,0,- -BRDA:88,1,1,- -DA:90,0 -BRDA:90,2,0,- -BRDA:90,2,1,- -DA:91,0 -DA:94,0 -DA:97,0 -FN:113,JBOperatorStore.setOperator -FNDA:0,JBOperatorStore.setOperator -DA:115,0 -DA:118,0 -DA:120,0 -FN:138,JBOperatorStore.setOperators -FNDA:0,JBOperatorStore.setOperators -DA:139,0 -DA:141,0 -DA:144,0 -DA:146,0 -DA:155,0 -FN:172,JBOperatorStore._packedPermissions -FNDA:0,JBOperatorStore._packedPermissions -DA:173,0 -DA:174,0 -DA:176,0 -BRDA:176,3,0,- -BRDA:176,3,1,- -DA:179,0 -DA:182,0 -FNF:5 -FNH:0 -LF:22 -LH:0 -BRF:8 -BRH:0 -end_of_record -TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/JBPrices.sol -FN:57,JBPrices.priceFor -FNDA:0,JBPrices.priceFor -DA:63,0 -BRDA:63,0,0,- -BRDA:63,0,1,- -DA:66,0 -DA:69,0 -BRDA:69,1,0,- -BRDA:69,1,1,- -DA:72,0 -DA:75,0 -BRDA:75,2,0,- -BRDA:75,2,1,- -DA:76,0 -DA:79,0 -FN:109,JBPrices.addFeedFor -FNDA:0,JBPrices.addFeedFor -DA:116,0 -BRDA:115,3,0,- -BRDA:115,3,1,- -DA:118,0 -DA:121,0 -DA:123,0 -FNF:2 -FNH:0 -LF:11 -LH:0 -BRF:8 -BRH:0 -end_of_record -TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/JBProjects.sol -FN:69,JBProjects.tokenURI -FNDA:0,JBProjects.tokenURI -DA:71,0 -DA:74,0 -BRDA:74,0,0,- -BRDA:74,0,1,- -DA:77,0 -FN:89,JBProjects.supportsInterface -FNDA:0,JBProjects.supportsInterface -DA:96,0 -FN:134,JBProjects.createFor -FNDA:2,JBProjects.createFor -DA:140,2 -DA:143,2 -DA:146,2 -BRDA:146,1,0,2 -BRDA:146,1,1,2 -DA:147,2 -DA:149,2 -FN:165,JBProjects.setMetadataOf -FNDA:0,JBProjects.setMetadataOf -DA:171,0 -DA:173,0 -FN:182,JBProjects.setTokenUriResolver -FNDA:0,JBProjects.setTokenUriResolver -DA:184,0 -DA:186,0 -FNF:5 -FNH:1 -LF:13 -LH:5 -BRF:4 -BRH:2 -end_of_record -TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/JBSingleTokenPaymentTerminalStore.sol -FN:147,JBSingleTokenPaymentTerminalStore.currentOverflowOf -FNDA:0,JBSingleTokenPaymentTerminalStore.currentOverflowOf -DA:154,0 -FN:173,JBSingleTokenPaymentTerminalStore.currentTotalOverflowOf -FNDA:0,JBSingleTokenPaymentTerminalStore.currentTotalOverflowOf -DA:178,0 -FN:201,JBSingleTokenPaymentTerminalStore.currentReclaimableOverflowOf -FNDA:0,JBSingleTokenPaymentTerminalStore.currentReclaimableOverflowOf -DA:208,0 -DA:212,0 -DA:217,0 -BRDA:217,0,0,- -BRDA:217,0,1,- -DA:220,0 -DA:224,0 -BRDA:224,1,0,- -BRDA:224,1,1,- -DA:227,0 -FN:251,JBSingleTokenPaymentTerminalStore.currentReclaimableOverflowOf -FNDA:0,JBSingleTokenPaymentTerminalStore.currentReclaimableOverflowOf -DA:258,0 -BRDA:258,2,0,- -BRDA:258,2,1,- -DA:261,0 -BRDA:261,3,0,- -BRDA:261,3,1,- -DA:264,0 -DA:267,0 -FN:317,JBSingleTokenPaymentTerminalStore.recordPaymentFrom -FNDA:4,JBSingleTokenPaymentTerminalStore.recordPaymentFrom -DA:337,4 -DA:340,4 -BRDA:340,4,0,- -BRDA:340,4,1,4 -DA:343,4 -BRDA:343,5,0,- -BRDA:343,5,1,4 -DA:346,4 -DA:349,4 -BRDA:349,6,0,- -BRDA:349,6,1,4 -DA:351,4 -DA:363,4 -DA:368,0 -DA:369,0 -DA:375,4 -DA:378,4 -BRDA:378,7,0,- -BRDA:378,7,1,3 -DA:379,3 -DA:381,3 -DA:384,3 -BRDA:384,8,0,- -BRDA:384,8,1,3 -DA:386,3 -BRDA:386,9,0,- -BRDA:386,9,1,3 -DA:389,3 -DA:393,3 -DA:399,4 -BRDA:399,10,0,- -BRDA:399,10,1,4 -DA:402,4 -BRDA:402,11,0,1 -BRDA:402,11,1,4 -DA:403,1 -DA:409,4 -BRDA:409,12,0,3 -BRDA:409,12,1,1 -DA:412,1 -DA:416,1 -DA:421,1 -FN:445,JBSingleTokenPaymentTerminalStore.recordRedemptionFor -FNDA:0,JBSingleTokenPaymentTerminalStore.recordRedemptionFor -DA:463,0 -DA:466,0 -BRDA:466,13,0,- -BRDA:466,13,1,- -DA:471,0 -DA:472,0 -DA:473,0 -DA:478,0 -DA:481,0 -DA:484,0 -DA:488,0 -DA:498,0 -DA:504,0 -BRDA:504,14,0,- -BRDA:504,14,1,- -DA:506,0 -BRDA:506,15,0,- -BRDA:506,15,1,- -DA:508,0 -DA:516,0 -DA:520,0 -BRDA:520,16,0,- -BRDA:520,16,1,- -DA:524,0 -DA:527,0 -DA:543,0 -DA:548,0 -DA:553,0 -DA:555,0 -BRDA:555,17,0,- -BRDA:555,17,1,- -DA:557,0 -DA:559,0 -DA:562,0 -BRDA:562,18,0,- -BRDA:562,18,1,- -DA:564,0 -DA:567,0 -DA:573,0 -BRDA:573,19,0,- -BRDA:573,19,1,- -DA:574,0 -DA:577,0 -BRDA:577,20,0,- -BRDA:577,20,1,- -DA:579,0 -FN:600,JBSingleTokenPaymentTerminalStore.recordDistributionFor -FNDA:1,JBSingleTokenPaymentTerminalStore.recordDistributionFor -DA:611,1 -DA:614,1 -BRDA:614,21,0,- -BRDA:614,21,1,1 -DA:617,1 -DA:622,1 -DA:632,1 -BRDA:632,22,0,- -BRDA:632,22,1,1 -DA:633,0 -DA:636,1 -BRDA:636,23,0,- -BRDA:636,23,1,1 -DA:639,1 -DA:642,1 -DA:651,1 -BRDA:651,24,0,- -BRDA:651,24,1,1 -DA:652,0 -DA:655,1 -DA:661,1 -FN:681,JBSingleTokenPaymentTerminalStore.recordUsedAllowanceOf -FNDA:0,JBSingleTokenPaymentTerminalStore.recordUsedAllowanceOf -DA:692,0 -DA:695,0 -DA:700,0 -DA:710,0 -BRDA:710,25,0,- -BRDA:710,25,1,- -DA:711,0 -DA:714,0 -BRDA:714,26,0,- -BRDA:714,26,1,- -DA:717,0 -DA:720,0 -DA:730,0 -BRDA:729,27,0,- -BRDA:729,27,1,- -DA:737,0 -DA:740,0 -DA:745,0 -FN:760,JBSingleTokenPaymentTerminalStore.recordAddedBalanceFor -FNDA:3,JBSingleTokenPaymentTerminalStore.recordAddedBalanceFor -DA:762,3 -FN:778,JBSingleTokenPaymentTerminalStore.recordMigration -FNDA:0,JBSingleTokenPaymentTerminalStore.recordMigration -DA:785,0 -DA:788,0 -BRDA:788,28,0,- -BRDA:788,28,1,- -DA:791,0 -DA:794,0 -FN:816,JBSingleTokenPaymentTerminalStore._reclaimableOverflowDuring -FNDA:0,JBSingleTokenPaymentTerminalStore._reclaimableOverflowDuring -DA:824,0 -BRDA:824,29,0,- -BRDA:824,29,1,- -DA:827,0 -DA:833,0 -BRDA:833,30,0,- -BRDA:833,30,1,- -DA:836,0 -DA:839,0 -BRDA:839,31,0,- -BRDA:839,31,1,- -DA:841,0 -FN:868,JBSingleTokenPaymentTerminalStore._overflowDuring -FNDA:0,JBSingleTokenPaymentTerminalStore._overflowDuring -DA:875,0 -DA:878,0 -BRDA:878,32,0,- -BRDA:878,32,1,- -DA:881,0 -DA:886,0 -DA:890,0 -BRDA:890,33,0,- -BRDA:890,33,1,- -DA:891,0 -DA:899,0 -FN:917,JBSingleTokenPaymentTerminalStore._currentTotalOverflowOf -FNDA:0,JBSingleTokenPaymentTerminalStore._currentTotalOverflowOf -DA:923,0 -DA:926,0 -DA:929,0 -DA:930,0 -DA:932,0 -DA:937,0 -DA:942,0 +SF:contracts/JBGenericBuybackDelegate.sol +FN:139,JBGenericBuybackDelegate.payParams +FNDA:1026,JBGenericBuybackDelegate.payParams +DA:146,1026 +DA:149,1026 +DA:152,1026 +DA:156,1026 +DA:159,1026 +DA:160,1026 +BRDA:160,0,0,514 +BRDA:160,0,1,1026 +DA:164,1026 +BRDA:164,1,0,512 +BRDA:164,1,1,1026 +DA:167,1026 +DA:170,1026 +DA:173,1026 +DA:176,1026 +BRDA:176,2,0,512 +BRDA:176,2,1,1026 +DA:177,512 +DA:181,1026 +BRDA:181,3,0,- +BRDA:181,3,1,340 +DA:183,596 +BRDA:183,4,0,256 +BRDA:183,4,1,340 +DA:186,340 +DA:189,340 +DA:190,340 +DA:197,340 +DA:205,430 +FN:211,JBGenericBuybackDelegate.secondsAgoOf +FNDA:768,JBGenericBuybackDelegate.secondsAgoOf +DA:212,768 +FN:218,JBGenericBuybackDelegate.twapDeltaOf +FNDA:768,JBGenericBuybackDelegate.twapDeltaOf +DA:219,768 +FN:225,JBGenericBuybackDelegate.redeemParams +FNDA:256,JBGenericBuybackDelegate.redeemParams +DA:231,256 +FN:238,JBGenericBuybackDelegate.supportsInterface +FNDA:2816,JBGenericBuybackDelegate.supportsInterface +DA:239,2816 +DA:240,2560 +FN:251,JBGenericBuybackDelegate.didPay +FNDA:1537,JBGenericBuybackDelegate.didPay +DA:253,1537 +BRDA:253,5,0,256 +BRDA:253,5,1,1281 +DA:254,256 +DA:258,1281 +DA:264,1281 +DA:267,1281 +DA:270,1281 +BRDA:270,6,0,256 +BRDA:270,6,1,1025 +DA:273,1025 +DA:278,1025 +BRDA:278,7,0,513 +BRDA:278,7,1,1025 +FN:285,JBGenericBuybackDelegate.uniswapV3SwapCallback +FNDA:3,JBGenericBuybackDelegate.uniswapV3SwapCallback +DA:290,3 +DA:293,3 +DA:296,3 +BRDA:296,8,0,1 +BRDA:296,8,1,2 +DA:299,2 +DA:302,2 +BRDA:302,9,0,- +BRDA:302,9,1,1 +DA:305,2 +FN:316,JBGenericBuybackDelegate.setPoolFor +FNDA:2051,JBGenericBuybackDelegate.setPoolFor +DA:322,2050 +BRDA:322,10,0,512 +BRDA:322,10,1,1538 +DA:325,1538 +BRDA:325,11,0,512 +BRDA:325,11,1,1026 +DA:328,1026 +DA:331,1026 +BRDA:331,12,0,256 +BRDA:331,12,1,770 +DA:334,770 +BRDA:334,13,0,- +BRDA:334,13,1,770 +DA:337,770 +DA:340,770 +DA:365,770 +BRDA:365,14,0,256 +BRDA:365,14,1,514 +DA:368,514 +DA:371,514 +DA:372,514 +DA:374,514 +DA:375,514 +DA:376,514 +FN:383,JBGenericBuybackDelegate.changeSecondsAgo +FNDA:1024,JBGenericBuybackDelegate.changeSecondsAgo +DA:388,768 +BRDA:388,15,0,512 +BRDA:388,15,1,256 +DA:389,512 +DA:393,256 +DA:396,256 +DA:399,256 +DA:401,256 +FN:408,JBGenericBuybackDelegate.setTwapDelta +FNDA:1024,JBGenericBuybackDelegate.setTwapDelta +DA:413,768 +BRDA:413,16,0,512 +BRDA:413,16,1,256 +DA:416,256 +DA:419,256 +DA:422,256 +DA:424,256 +FN:437,JBGenericBuybackDelegate._getQuote +FNDA:768,JBGenericBuybackDelegate._getQuote +DA:443,768 +DA:446,768 +DA:455,512 +DA:456,512 +DA:457,512 +DA:460,512 +DA:463,512 +DA:471,512 +FN:480,JBGenericBuybackDelegate._swap +FNDA:1281,JBGenericBuybackDelegate._swap +DA:487,1281 +DA:490,1281 +DA:505,513 +DA:514,513 +DA:523,513 +FN:530,JBGenericBuybackDelegate._mint +FNDA:513,JBGenericBuybackDelegate._mint +DA:532,513 +DA:542,513 +BRDA:542,17,0,- +BRDA:542,17,1,256 +DA:543,256 +DA:547,513 +DA:551,513 FNF:13 -FNH:3 -LF:116 -LH:34 -BRF:68 -BRH:15 +FNH:13 +LF:81 +LH:81 +BRF:36 +BRH:32 end_of_record TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/JBSplitsStore.sol -FN:103,JBSplitsStore.splitsOf -FNDA:1,JBSplitsStore.splitsOf -DA:108,1 -FN:147,JBSplitsStore.set -FNDA:3,JBSplitsStore.set -DA:162,3 -DA:165,3 -DA:167,3 -DA:170,3 -DA:173,3 -FN:194,JBSplitsStore._set -FNDA:3,JBSplitsStore._set -DA:201,3 -DA:204,3 -DA:207,3 -DA:210,0 -BRDA:209,0,0,- -BRDA:209,0,1,- -DA:212,0 -DA:215,0 -DA:220,3 -DA:223,3 -DA:225,3 -DA:227,1 -BRDA:227,1,0,- -BRDA:227,1,1,1 -DA:230,1 -BRDA:230,2,0,- -BRDA:230,2,1,1 -DA:233,1 -DA:236,1 -BRDA:236,3,0,- -BRDA:236,3,1,1 -DA:238,1 -DA:241,1 -BRDA:241,4,0,- -BRDA:241,4,1,1 -DA:243,1 -BRDA:243,5,0,1 -BRDA:243,5,1,1 -DA:245,1 -DA:247,1 -DA:249,1 -DA:252,1 -DA:255,1 -BRDA:255,6,0,- -BRDA:255,6,1,1 -DA:257,1 -BRDA:257,7,0,- -BRDA:257,7,1,1 -DA:260,1 -DA:262,1 -DA:265,1 -DA:268,0 -BRDA:268,8,0,- -BRDA:268,8,1,- -DA:269,0 -DA:271,1 -DA:274,1 -DA:279,3 -FN:291,JBSplitsStore._includesLocked -FNDA:0,JBSplitsStore._includesLocked -DA:297,0 -DA:299,0 -DA:302,0 -BRDA:301,9,0,- -BRDA:301,9,1,- -DA:310,0 -DA:313,0 -DA:317,0 -FN:330,JBSplitsStore._getStructsFor -FNDA:4,JBSplitsStore._getStructsFor -DA:336,4 -DA:339,4 -DA:342,4 -DA:344,1 -DA:347,1 -DA:350,1 -DA:352,1 -DA:354,1 -DA:356,1 -DA:358,1 -DA:361,1 -DA:364,1 -BRDA:364,10,0,1 -BRDA:364,10,1,1 -DA:366,1 -DA:368,1 -DA:372,1 -DA:375,1 -DA:379,4 -FNF:5 -FNH:4 -LF:59 -LH:48 -BRF:22 -BRH:10 -end_of_record -TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/JBToken.sol -FN:49,JBToken.totalSupply -FNDA:0,JBToken.totalSupply -DA:52,0 -FN:64,JBToken.balanceOf -FNDA:0,JBToken.balanceOf -DA:73,0 -FN:86,JBToken.decimals -FNDA:0,JBToken.decimals -DA:87,0 -FN:122,JBToken.mint -FNDA:0,JBToken.mint -DA:128,0 -BRDA:128,0,0,- -BRDA:128,0,1,- -DA:130,0 -FN:144,JBToken.burn -FNDA:0,JBToken.burn -DA:150,0 -BRDA:150,1,0,- -BRDA:150,1,1,- -DA:152,0 -FN:163,JBToken.approve -FNDA:0,JBToken.approve -DA:169,0 -BRDA:169,2,0,- -BRDA:169,2,1,- -DA:171,0 -FN:182,JBToken.transfer -FNDA:0,JBToken.transfer -DA:188,0 -BRDA:188,3,0,- -BRDA:188,3,1,- -DA:190,0 -FN:202,JBToken.transferFrom -FNDA:0,JBToken.transferFrom -DA:209,0 -BRDA:209,4,0,- -BRDA:209,4,1,- -DA:211,0 -FNF:8 -FNH:0 -LF:13 -LH:0 -BRF:10 -BRH:0 -end_of_record -TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/JBTokenStore.sol -FN:112,JBTokenStore.balanceOf -FNDA:2,JBTokenStore.balanceOf -DA:119,2 -DA:122,2 -DA:125,2 -BRDA:125,0,0,- -BRDA:125,0,1,- -FN:140,JBTokenStore.totalSupplyOf -FNDA:3,JBTokenStore.totalSupplyOf -DA:142,8 -DA:145,8 -DA:148,8 -BRDA:148,1,0,- -BRDA:148,1,1,- -FN:191,JBTokenStore.issueFor -FNDA:0,JBTokenStore.issueFor -DA:202,0 -BRDA:202,2,0,- -BRDA:202,2,1,- -DA:205,0 -BRDA:205,3,0,- -BRDA:205,3,1,- -DA:208,0 -BRDA:208,4,0,- -BRDA:208,4,1,- -DA:211,0 -DA:214,0 -DA:216,0 -FN:229,JBTokenStore.setFor -FNDA:0,JBTokenStore.setFor -DA:235,0 -BRDA:235,5,0,- -BRDA:235,5,1,- -DA:238,0 -BRDA:238,6,0,- -BRDA:238,6,1,- -DA:241,0 -BRDA:241,7,0,- -BRDA:241,7,1,- -DA:244,0 -DA:246,0 -FN:261,JBTokenStore.mintFor -FNDA:5,JBTokenStore.mintFor -DA:268,5 -DA:271,5 -DA:273,5 -BRDA:273,8,0,- -BRDA:273,8,1,- -DA:275,0 -DA:278,5 -DA:279,5 -DA:283,5 -BRDA:283,9,0,- -BRDA:283,9,1,5 -DA:285,5 -FN:300,JBTokenStore.burnFrom -FNDA:2,JBTokenStore.burnFrom -DA:307,2 -DA:310,2 -DA:313,2 -DA:318,2 -BRDA:318,10,0,- -BRDA:318,10,1,2 -DA:321,2 -DA:324,2 -BRDA:324,11,0,- -BRDA:324,11,1,- -DA:325,0 -BRDA:325,12,0,- -BRDA:325,12,1,- -DA:327,0 -DA:331,0 -DA:336,2 -DA:338,2 -DA:342,2 -BRDA:342,13,0,2 -BRDA:342,13,1,2 -DA:344,2 -DA:347,2 -DA:353,2 -BRDA:353,14,0,- -BRDA:353,14,1,- -DA:355,2 -FN:377,JBTokenStore.claimFor -FNDA:0,JBTokenStore.claimFor -DA:383,0 -DA:386,0 -BRDA:386,15,0,- -BRDA:386,15,1,- -DA:389,0 -DA:392,0 -BRDA:392,16,0,- -BRDA:392,16,1,- -DA:396,0 -DA:399,0 -DA:403,0 -DA:405,0 -FN:420,JBTokenStore.transferFrom -FNDA:0,JBTokenStore.transferFrom -DA:427,0 -DA:430,0 -BRDA:430,17,0,- -BRDA:430,17,1,- -DA:433,0 -BRDA:433,18,0,- -BRDA:433,18,1,- -DA:436,0 -DA:439,0 -BRDA:439,19,0,- -BRDA:439,19,1,- -DA:443,0 -DA:447,0 -DA:451,0 -FNF:8 -FNH:4 -LF:57 -LH:26 -BRF:40 -BRH:4 -end_of_record -TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/abstract/JBOperatable.sol -FN:93,JBOperatable._requirePermission -FNDA:0,JBOperatable._requirePermission -DA:99,0 -BRDA:98,0,0,- -BRDA:98,0,1,- -DA:102,0 -FN:114,JBOperatable._requirePermissionAllowingOverride -FNDA:14,JBOperatable._requirePermissionAllowingOverride -DA:121,14 -BRDA:120,1,0,- -BRDA:120,1,1,14 -DA:125,0 +SF:contracts/mock/MockAllocator.sol +FN:18,MockAllocator.allocate +FNDA:0,MockAllocator.allocate +DA:21,0 +DA:35,0 +DA:36,0 +DA:37,0 +BRDA:37,0,0,- +BRDA:37,0,1,- +FN:40,MockAllocator.supportsInterface +FNDA:0,MockAllocator.supportsInterface +DA:41,0 FNF:2 -FNH:1 -LF:4 -LH:1 -BRF:4 -BRH:1 -end_of_record -TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol -FN:197,JBPayoutRedemptionPaymentTerminal.currentEthOverflowOf -FNDA:0,JBPayoutRedemptionPaymentTerminal.currentEthOverflowOf -DA:205,0 -DA:208,0 -DA:213,0 -FN:231,JBPayoutRedemptionPaymentTerminal.heldFeesOf -FNDA:0,JBPayoutRedemptionPaymentTerminal.heldFeesOf -DA:232,0 -FN:248,JBPayoutRedemptionPaymentTerminal.supportsInterface -FNDA:0,JBPayoutRedemptionPaymentTerminal.supportsInterface -DA:255,0 -FN:343,JBPayoutRedemptionPaymentTerminal.pay -FNDA:4,JBPayoutRedemptionPaymentTerminal.pay -DA:356,4 -BRDA:356,0,0,- -BRDA:356,0,1,- -DA:357,0 -BRDA:357,1,0,- -BRDA:357,1,1,- -DA:360,0 -DA:363,0 -DA:366,0 -DA:369,4 -DA:371,4 -FN:402,JBPayoutRedemptionPaymentTerminal.redeemTokensOf -FNDA:0,JBPayoutRedemptionPaymentTerminal.redeemTokensOf -DA:420,0 -FN:454,JBPayoutRedemptionPaymentTerminal.distributePayoutsOf -FNDA:1,JBPayoutRedemptionPaymentTerminal.distributePayoutsOf -DA:464,1 -FN:487,JBPayoutRedemptionPaymentTerminal.useAllowanceOf -FNDA:0,JBPayoutRedemptionPaymentTerminal.useAllowanceOf -DA:504,0 -FN:519,JBPayoutRedemptionPaymentTerminal.migrate -FNDA:0,JBPayoutRedemptionPaymentTerminal.migrate -DA:527,0 -BRDA:527,2,0,- -BRDA:527,2,1,- -DA:530,0 -DA:533,0 -BRDA:533,3,0,- -BRDA:533,3,1,- -DA:535,0 -DA:538,0 -DA:541,0 -DA:544,0 -FN:557,JBPayoutRedemptionPaymentTerminal.addToBalanceOf -FNDA:3,JBPayoutRedemptionPaymentTerminal.addToBalanceOf -DA:567,3 -BRDA:567,4,0,- -BRDA:567,4,1,- -DA:569,0 -BRDA:569,5,0,- -BRDA:569,5,1,- -DA:572,0 -DA:575,0 -DA:578,0 -DA:581,3 -DA:584,3 -FN:596,JBPayoutRedemptionPaymentTerminal.processFees -FNDA:0,JBPayoutRedemptionPaymentTerminal.processFees -DA:608,0 -DA:611,0 -DA:614,0 -DA:617,0 -DA:619,0 -DA:626,0 -DA:628,0 -DA:631,0 -FN:645,JBPayoutRedemptionPaymentTerminal.setFee -FNDA:0,JBPayoutRedemptionPaymentTerminal.setFee -DA:647,0 -BRDA:647,6,0,- -BRDA:647,6,1,- -DA:650,0 -DA:652,0 -FN:664,JBPayoutRedemptionPaymentTerminal.setFeeGauge -FNDA:0,JBPayoutRedemptionPaymentTerminal.setFeeGauge -DA:666,0 -DA:668,0 -FN:681,JBPayoutRedemptionPaymentTerminal.setFeelessAddress -FNDA:0,JBPayoutRedemptionPaymentTerminal.setFeelessAddress -DA:683,0 -DA:685,0 -FN:700,JBPayoutRedemptionPaymentTerminal._transferFrom -FNDA:0,JBPayoutRedemptionPaymentTerminal._transferFrom -FN:717,JBPayoutRedemptionPaymentTerminal._beforeTransferTo -FNDA:4,JBPayoutRedemptionPaymentTerminal._beforeTransferTo -FN:739,JBPayoutRedemptionPaymentTerminal._redeemTokensOf -FNDA:0,JBPayoutRedemptionPaymentTerminal._redeemTokensOf -DA:749,0 -BRDA:749,7,0,- -BRDA:749,7,1,- -DA:753,0 -DA:757,0 -DA:760,0 -DA:769,0 -BRDA:769,8,0,- -BRDA:769,8,1,- -DA:772,0 -BRDA:772,9,0,- -BRDA:772,9,1,- -DA:773,0 -DA:782,0 -BRDA:782,10,0,- -BRDA:782,10,1,- -DA:784,0 -DA:786,0 -DA:798,0 -DA:800,0 -DA:802,0 -DA:805,0 -DA:808,0 -DA:811,0 -BRDA:811,11,0,- -BRDA:811,11,1,- -DA:814,0 -DA:816,0 -DA:818,0 -DA:825,0 -DA:832,0 -BRDA:832,12,0,- -BRDA:832,12,1,- -DA:834,0 -FN:869,JBPayoutRedemptionPaymentTerminal._distributePayoutsOf -FNDA:1,JBPayoutRedemptionPaymentTerminal._distributePayoutsOf -DA:877,1 -DA:884,1 -BRDA:884,13,0,- -BRDA:884,13,1,1 -DA:888,1 -DA:892,1 -DA:898,1 -DA:903,1 -DA:906,1 -DA:910,1 -DA:918,0 -BRDA:918,14,0,- -BRDA:918,14,1,- -DA:921,0 -DA:926,0 -DA:937,0 -BRDA:937,15,0,- -BRDA:937,15,1,- -DA:939,0 -DA:944,0 -DA:948,0 -FN:981,JBPayoutRedemptionPaymentTerminal._useAllowanceOf -FNDA:0,JBPayoutRedemptionPaymentTerminal._useAllowanceOf -DA:990,0 -DA:997,0 -BRDA:997,16,0,- -BRDA:997,16,1,- -DA:1002,0 -DA:1005,0 -DA:1009,0 -DA:1014,0 -DA:1020,0 -DA:1024,0 -BRDA:1024,17,0,- -BRDA:1024,17,1,- -DA:1025,0 -DA:1028,0 -FN:1054,JBPayoutRedemptionPaymentTerminal._distributeToPayoutSplitsOf -FNDA:1,JBPayoutRedemptionPaymentTerminal._distributeToPayoutSplitsOf -DA:1062,1 -DA:1064,1 -DA:1067,1 -DA:1070,1 -DA:1072,1 -DA:1075,1 -DA:1080,1 -DA:1083,1 -DA:1085,1 -BRDA:1085,18,0,- -BRDA:1085,18,1,- -DA:1088,1 -BRDA:1088,19,0,1 -BRDA:1088,19,1,- -DA:1091,1 -BRDA:1090,20,0,- -BRDA:1090,20,1,1 -DA:1094,0 -DA:1098,1 -DA:1102,1 -DA:1106,1 -DA:1109,1 -DA:1120,1 -DA:1123,0 -BRDA:1123,21,0,- -BRDA:1123,21,1,- -DA:1125,0 -DA:1128,0 -BRDA:1128,22,0,- -BRDA:1128,22,1,- -DA:1131,0 -BRDA:1131,23,0,- -BRDA:1131,23,1,- -DA:1133,0 -DA:1136,0 -DA:1137,0 -DA:1140,0 -BRDA:1140,24,0,- -BRDA:1140,24,1,- -DA:1141,0 -DA:1143,0 -DA:1156,0 -BRDA:1155,25,0,- -BRDA:1155,25,1,- -DA:1158,0 -DA:1162,0 -DA:1165,0 -DA:1169,0 -DA:1172,0 -DA:1175,0 -DA:1176,0 -DA:1179,0 -BRDA:1179,26,0,- -BRDA:1179,26,1,- -DA:1180,0 -DA:1188,0 -DA:1201,0 -DA:1207,0 -BRDA:1207,27,0,- -BRDA:1207,27,1,- -DA:1208,0 -DA:1212,0 -DA:1215,0 -DA:1219,0 -DA:1224,0 -DA:1228,0 -DA:1238,0 -FN:1255,JBPayoutRedemptionPaymentTerminal._takeFeeFrom -FNDA:0,JBPayoutRedemptionPaymentTerminal._takeFeeFrom -DA:1262,0 -DA:1264,0 -BRDA:1264,28,0,- -BRDA:1264,28,1,- -DA:1266,0 -DA:1268,0 -DA:1271,0 -DA:1273,0 -FN:1284,JBPayoutRedemptionPaymentTerminal._processFee -FNDA:0,JBPayoutRedemptionPaymentTerminal._processFee -DA:1286,0 -DA:1289,0 -BRDA:1289,29,0,- -BRDA:1289,29,1,- -DA:1290,0 -DA:1302,0 -DA:1305,0 -DA:1308,0 -FN:1336,JBPayoutRedemptionPaymentTerminal._pay -FNDA:4,JBPayoutRedemptionPaymentTerminal._pay -DA:1347,4 -BRDA:1347,30,0,- -BRDA:1347,30,1,4 -DA:1351,4 -DA:1355,4 -DA:1356,4 -DA:1359,4 -DA:1362,4 -DA:1373,4 -BRDA:1373,31,0,- -BRDA:1373,31,1,1 -DA:1375,1 -DA:1385,4 -BRDA:1385,32,0,- -BRDA:1385,32,1,4 -DA:1388,4 -BRDA:1388,33,0,- -BRDA:1388,33,1,3 -DA:1390,3 -DA:1392,3 -DA:1406,3 -DA:1408,3 -DA:1410,3 -DA:1413,3 -DA:1416,3 -DA:1419,3 -BRDA:1419,34,0,3 -BRDA:1419,34,1,3 -DA:1422,3 -DA:1424,3 -DA:1426,3 -DA:1434,3 -DA:1440,4 -FN:1464,JBPayoutRedemptionPaymentTerminal._addToBalanceOf -FNDA:3,JBPayoutRedemptionPaymentTerminal._addToBalanceOf -DA:1472,3 -DA:1475,3 -DA:1477,3 -FN:1489,JBPayoutRedemptionPaymentTerminal._refundHeldFees -FNDA:3,JBPayoutRedemptionPaymentTerminal._refundHeldFees -DA:1494,3 -DA:1497,3 -DA:1500,3 -DA:1503,3 -DA:1506,3 -DA:1507,0 -BRDA:1507,35,0,- -BRDA:1507,35,1,- -DA:1508,0 -BRDA:1508,36,0,- -BRDA:1508,36,1,- -DA:1510,0 -DA:1511,0 -DA:1519,0 -DA:1527,0 -DA:1529,0 -DA:1533,0 -DA:1537,3 -FN:1550,JBPayoutRedemptionPaymentTerminal._feeAmount -FNDA:1,JBPayoutRedemptionPaymentTerminal._feeAmount -DA:1556,1 -DA:1560,1 -FN:1572,JBPayoutRedemptionPaymentTerminal._currentFeeDiscount -FNDA:1,JBPayoutRedemptionPaymentTerminal._currentFeeDiscount -DA:1575,1 -BRDA:1574,37,0,- -BRDA:1574,37,1,1 -DA:1577,0 -DA:1580,1 -BRDA:1580,38,0,- -BRDA:1580,38,1,- -DA:1582,0 -DA:1589,1 -FNF:26 -FNH:11 -LF:197 -LH:68 -BRF:78 -BRH:10 -end_of_record -TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/abstract/JBSingleTokenPaymentTerminal.sol -FN:55,JBSingleTokenPaymentTerminal.acceptsToken -FNDA:1,JBSingleTokenPaymentTerminal.acceptsToken -DA:58,1 -FN:69,JBSingleTokenPaymentTerminal.decimalsForToken -FNDA:0,JBSingleTokenPaymentTerminal.decimalsForToken -DA:72,0 -FN:83,JBSingleTokenPaymentTerminal.currencyForToken -FNDA:0,JBSingleTokenPaymentTerminal.currencyForToken -DA:86,0 -FN:102,JBSingleTokenPaymentTerminal.supportsInterface -FNDA:0,JBSingleTokenPaymentTerminal.supportsInterface -DA:109,0 -FNF:4 -FNH:1 -LF:4 -LH:1 -BRF:0 -BRH:0 -end_of_record -TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/libraries/JBFixedPointNumber.sol -FN:5,JBFixedPointNumber.adjustDecimals -FNDA:0,JBFixedPointNumber.adjustDecimals -DA:11,0 -BRDA:11,0,0,- -BRDA:11,0,1,- -DA:12,0 -BRDA:12,1,0,- -BRDA:12,1,1,- -DA:13,0 -FNF:1 FNH:0 -LF:3 -LH:0 -BRF:4 -BRH:0 -end_of_record -TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/libraries/JBFundingCycleMetadataResolver.sol -FN:11,JBFundingCycleMetadataResolver.global -FNDA:0,JBFundingCycleMetadataResolver.global -DA:16,0 -FN:19,JBFundingCycleMetadataResolver.reservedRate -FNDA:0,JBFundingCycleMetadataResolver.reservedRate -DA:20,0 -FN:23,JBFundingCycleMetadataResolver.redemptionRate -FNDA:0,JBFundingCycleMetadataResolver.redemptionRate -DA:25,0 -FN:28,JBFundingCycleMetadataResolver.ballotRedemptionRate -FNDA:0,JBFundingCycleMetadataResolver.ballotRedemptionRate -DA:34,0 -FN:37,JBFundingCycleMetadataResolver.payPaused -FNDA:0,JBFundingCycleMetadataResolver.payPaused -DA:38,0 -FN:41,JBFundingCycleMetadataResolver.distributionsPaused -FNDA:0,JBFundingCycleMetadataResolver.distributionsPaused -DA:42,0 -FN:45,JBFundingCycleMetadataResolver.redeemPaused -FNDA:0,JBFundingCycleMetadataResolver.redeemPaused -DA:46,0 -FN:49,JBFundingCycleMetadataResolver.burnPaused -FNDA:0,JBFundingCycleMetadataResolver.burnPaused -DA:50,0 -FN:53,JBFundingCycleMetadataResolver.mintingAllowed -FNDA:0,JBFundingCycleMetadataResolver.mintingAllowed -DA:54,0 -FN:57,JBFundingCycleMetadataResolver.terminalMigrationAllowed -FNDA:0,JBFundingCycleMetadataResolver.terminalMigrationAllowed -DA:62,0 -FN:65,JBFundingCycleMetadataResolver.controllerMigrationAllowed -FNDA:0,JBFundingCycleMetadataResolver.controllerMigrationAllowed -DA:70,0 -FN:73,JBFundingCycleMetadataResolver.shouldHoldFees -FNDA:0,JBFundingCycleMetadataResolver.shouldHoldFees -DA:74,0 -FN:77,JBFundingCycleMetadataResolver.preferClaimedTokenOverride -FNDA:0,JBFundingCycleMetadataResolver.preferClaimedTokenOverride -DA:82,0 -FN:85,JBFundingCycleMetadataResolver.useTotalOverflowForRedemptions -FNDA:0,JBFundingCycleMetadataResolver.useTotalOverflowForRedemptions -DA:90,0 -FN:93,JBFundingCycleMetadataResolver.useDataSourceForPay -FNDA:0,JBFundingCycleMetadataResolver.useDataSourceForPay -DA:94,0 -FN:97,JBFundingCycleMetadataResolver.useDataSourceForRedeem -FNDA:0,JBFundingCycleMetadataResolver.useDataSourceForRedeem -DA:102,0 -FN:105,JBFundingCycleMetadataResolver.dataSource -FNDA:0,JBFundingCycleMetadataResolver.dataSource -DA:106,0 -FN:109,JBFundingCycleMetadataResolver.metadata -FNDA:0,JBFundingCycleMetadataResolver.metadata -DA:110,0 -FN:121,JBFundingCycleMetadataResolver.packFundingCycleMetadata -FNDA:0,JBFundingCycleMetadataResolver.packFundingCycleMetadata -DA:127,0 -DA:129,0 -DA:133,0 -DA:136,0 -DA:139,0 -DA:141,0 -BRDA:141,0,0,- -BRDA:141,0,1,- -DA:143,0 -BRDA:143,1,0,- -BRDA:143,1,1,- -DA:145,0 -BRDA:145,2,0,- -BRDA:145,2,1,- -DA:147,0 -BRDA:147,3,0,- -BRDA:147,3,1,- -DA:149,0 -BRDA:149,4,0,- -BRDA:149,4,1,- -DA:151,0 -BRDA:151,5,0,- -BRDA:151,5,1,- -DA:153,0 -BRDA:153,6,0,- -BRDA:153,6,1,- -DA:155,0 -BRDA:155,7,0,- -BRDA:155,7,1,- -DA:157,0 -BRDA:157,8,0,- -BRDA:157,8,1,- -DA:159,0 -BRDA:159,9,0,- -BRDA:159,9,1,- -DA:161,0 -BRDA:161,10,0,- -BRDA:161,10,1,- -DA:163,0 -BRDA:163,11,0,- -BRDA:163,11,1,- -DA:165,0 -DA:167,0 -FN:178,JBFundingCycleMetadataResolver.expandMetadata -FNDA:0,JBFundingCycleMetadataResolver.expandMetadata -DA:183,0 -FNF:20 -FNH:0 -LF:38 +LF:5 LH:0 -BRF:24 +BRF:2 BRH:0 end_of_record TN: -SF:node_modules/@jbx-protocol/juice-contracts-v3/contracts/libraries/JBGlobalFundingCycleMetadataResolver.sol -FN:7,JBGlobalFundingCycleMetadataResolver.setTerminalsAllowed -FNDA:0,JBGlobalFundingCycleMetadataResolver.setTerminalsAllowed -DA:8,0 -FN:11,JBGlobalFundingCycleMetadataResolver.setControllerAllowed -FNDA:0,JBGlobalFundingCycleMetadataResolver.setControllerAllowed -DA:12,0 -FN:15,JBGlobalFundingCycleMetadataResolver.transfersPaused -FNDA:0,JBGlobalFundingCycleMetadataResolver.transfersPaused -DA:16,0 -FN:27,JBGlobalFundingCycleMetadataResolver.packFundingCycleGlobalMetadata -FNDA:0,JBGlobalFundingCycleMetadataResolver.packFundingCycleGlobalMetadata +SF:contracts/scripts/Deploy.s.sol +FN:27,DeployGeneric.setUp +FNDA:0,DeployGeneric.setUp +DA:28,0 +BRDA:28,0,0,- +BRDA:28,0,1,- +DA:29,0 +DA:30,0 +DA:31,0 +DA:32,0 +BRDA:32,1,0,- +BRDA:32,1,1,- DA:33,0 -BRDA:33,0,0,- -BRDA:33,0,1,- +DA:34,0 DA:35,0 -BRDA:35,1,0,- -BRDA:35,1,1,- +DA:36,0 +BRDA:36,2,0,- +BRDA:36,2,1,- DA:37,0 -BRDA:37,2,0,- -BRDA:37,2,1,- -FN:48,JBGlobalFundingCycleMetadataResolver.expandMetadata -FNDA:0,JBGlobalFundingCycleMetadataResolver.expandMetadata -DA:53,0 -FNF:5 -FNH:0 -LF:7 -LH:0 -BRF:6 -BRH:0 -end_of_record -TN: -SF:node_modules/@openzeppelin/contracts/access/Ownable.sol -FN:43,Ownable.owner -FNDA:0,Ownable.owner +DA:38,0 +DA:39,0 +DA:41,0 DA:44,0 -FN:50,Ownable._checkOwner -FNDA:0,Ownable._checkOwner -DA:51,0 -BRDA:51,0,0,- -BRDA:51,0,1,- -FN:61,Ownable.renounceOwnership -FNDA:0,Ownable.renounceOwnership -DA:62,0 -FN:69,Ownable.transferOwnership -FNDA:0,Ownable.transferOwnership +DA:55,0 +FN:67,DeployGeneric.run +FNDA:0,DeployGeneric.run +DA:68,0 +DA:69,0 DA:70,0 -BRDA:70,1,0,- -BRDA:70,1,1,- DA:71,0 -FN:78,Ownable._transferOwnership -FNDA:0,Ownable._transferOwnership -DA:79,0 -DA:80,0 -DA:81,0 -FNF:5 -FNH:0 -LF:8 -LH:0 -BRF:4 -BRH:0 -end_of_record -TN: -SF:node_modules/@openzeppelin/contracts/governance/utils/Votes.sol -FN:47,Votes.getVotes -FNDA:0,Votes.getVotes -DA:48,0 -FN:58,Votes.getPastVotes -FNDA:0,Votes.getPastVotes -DA:59,0 -FN:73,Votes.getPastTotalSupply -FNDA:0,Votes.getPastTotalSupply +DA:72,0 +DA:73,0 DA:74,0 -BRDA:74,0,0,- -BRDA:74,0,1,- DA:75,0 -FN:81,Votes._getTotalSupply -FNDA:0,Votes._getTotalSupply -DA:82,0 -FN:88,Votes.delegates -FNDA:0,Votes.delegates -DA:89,4 -FN:95,Votes.delegate -FNDA:0,Votes.delegate -DA:96,0 -DA:97,0 -FN:103,Votes.delegateBySig -FNDA:0,Votes.delegateBySig -DA:111,0 -BRDA:111,1,0,- -BRDA:111,1,1,- -DA:112,0 -DA:118,0 -BRDA:118,2,0,- -BRDA:118,2,1,- -DA:119,0 -FN:127,Votes._delegate -FNDA:0,Votes._delegate -DA:128,0 -DA:129,0 -DA:131,0 -DA:132,0 -FN:139,Votes._transferVotingUnits -FNDA:2,Votes._transferVotingUnits -DA:144,2 -BRDA:144,3,0,2 -BRDA:144,3,1,2 -DA:145,2 -DA:147,2 -BRDA:147,4,0,- -BRDA:147,4,1,2 -DA:148,0 -DA:150,2 -FN:156,Votes._moveDelegateVotes -FNDA:2,Votes._moveDelegateVotes -DA:161,2 -BRDA:161,5,0,- -BRDA:161,5,1,- -DA:162,0 -BRDA:162,6,0,- -BRDA:162,6,1,- -DA:163,0 -DA:164,0 -DA:166,0 -BRDA:166,7,0,- -BRDA:166,7,1,- -DA:167,0 -DA:168,0 -FN:173,Votes._add -FNDA:2,Votes._add -DA:174,2 -FN:177,Votes._subtract -FNDA:0,Votes._subtract -DA:178,0 -FN:186,Votes._useNonce -FNDA:0,Votes._useNonce -DA:187,0 -DA:188,0 -DA:189,0 -FN:195,Votes.nonces -FNDA:0,Votes.nonces -DA:196,0 -FN:203,Votes.DOMAIN_SEPARATOR -FNDA:0,Votes.DOMAIN_SEPARATOR -DA:204,0 -FNF:15 -FNH:3 -LF:35 -LH:7 -BRF:16 -BRH:3 -end_of_record -TN: -SF:node_modules/@openzeppelin/contracts/security/ReentrancyGuard.sol -FN:56,ReentrancyGuard._nonReentrantBefore -FNDA:5,ReentrancyGuard._nonReentrantBefore -DA:58,5 -BRDA:58,0,0,- -BRDA:58,0,1,5 -DA:61,5 -FN:64,ReentrancyGuard._nonReentrantAfter -FNDA:5,ReentrancyGuard._nonReentrantAfter -DA:67,5 -FNF:2 -FNH:2 -LF:3 -LH:3 -BRF:2 -BRH:1 -end_of_record -TN: -SF:node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol -FN:62,ERC20.name -FNDA:0,ERC20.name -DA:63,0 -FN:70,ERC20.symbol -FNDA:0,ERC20.symbol -DA:71,0 -FN:87,ERC20.decimals -FNDA:0,ERC20.decimals -DA:88,0 -FN:94,ERC20.totalSupply -FNDA:0,ERC20.totalSupply -DA:95,0 -FN:101,ERC20.balanceOf -FNDA:0,ERC20.balanceOf -DA:102,0 -FN:113,ERC20.transfer -FNDA:0,ERC20.transfer -DA:114,0 -DA:115,0 -DA:116,0 -FN:122,ERC20.allowance -FNDA:0,ERC20.allowance -DA:123,0 -FN:136,ERC20.approve -FNDA:0,ERC20.approve -DA:137,0 -DA:138,0 -DA:139,0 -FN:158,ERC20.transferFrom -FNDA:0,ERC20.transferFrom -DA:163,0 -DA:164,0 -DA:165,0 -DA:166,0 -FN:181,ERC20.increaseAllowance -FNDA:0,ERC20.increaseAllowance -DA:182,0 -DA:183,0 -DA:184,0 -FN:201,ERC20.decreaseAllowance -FNDA:0,ERC20.decreaseAllowance -DA:202,0 -DA:203,0 -DA:204,0 -BRDA:204,0,0,- -BRDA:204,0,1,- -DA:206,0 -DA:209,0 -FN:226,ERC20._transfer -FNDA:0,ERC20._transfer -DA:231,0 -BRDA:231,1,0,- -BRDA:231,1,1,- -DA:232,0 -BRDA:232,2,0,- -BRDA:232,2,1,- -DA:234,0 -DA:236,0 -DA:237,0 -BRDA:237,3,0,- -BRDA:237,3,1,- -DA:239,0 -DA:242,0 -DA:245,0 -DA:247,0 -FN:259,ERC20._mint -FNDA:0,ERC20._mint -DA:260,0 -BRDA:260,4,0,- -BRDA:260,4,1,- -DA:262,0 -DA:264,0 -DA:267,0 -DA:269,0 -DA:271,0 -FN:285,ERC20._burn -FNDA:0,ERC20._burn -DA:286,0 -BRDA:286,5,0,- -BRDA:286,5,1,- -DA:288,0 -DA:290,0 -DA:291,0 -BRDA:291,6,0,- -BRDA:291,6,1,- -DA:293,0 -DA:295,0 -DA:298,0 -DA:300,0 -FN:316,ERC20._approve -FNDA:0,ERC20._approve -DA:321,0 -BRDA:321,7,0,- -BRDA:321,7,1,- -DA:322,0 -BRDA:322,8,0,- -BRDA:322,8,1,- -DA:324,0 -DA:325,0 -FN:336,ERC20._spendAllowance -FNDA:0,ERC20._spendAllowance -DA:341,0 -DA:342,0 -BRDA:342,9,0,- -BRDA:342,9,1,- -DA:343,0 -BRDA:343,10,0,- -BRDA:343,10,1,- -DA:345,0 -FN:364,ERC20._beforeTokenTransfer -FNDA:0,ERC20._beforeTokenTransfer -FN:384,ERC20._afterTokenTransfer -FNDA:0,ERC20._afterTokenTransfer -FNF:18 -FNH:0 -LF:55 -LH:0 -BRF:22 -BRH:0 -end_of_record -TN: -SF:node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol -FN:43,ERC20Votes.checkpoints -FNDA:0,ERC20Votes.checkpoints -DA:44,0 -FN:50,ERC20Votes.numCheckpoints -FNDA:0,ERC20Votes.numCheckpoints -DA:51,0 -FN:57,ERC20Votes.delegates -FNDA:0,ERC20Votes.delegates -DA:58,0 -FN:64,ERC20Votes.getVotes -FNDA:0,ERC20Votes.getVotes -DA:65,0 -DA:66,0 -FN:76,ERC20Votes.getPastVotes -FNDA:0,ERC20Votes.getPastVotes -DA:77,0 -BRDA:77,0,0,- -BRDA:77,0,1,- +DA:76,0 DA:78,0 -FN:89,ERC20Votes.getPastTotalSupply -FNDA:0,ERC20Votes.getPastTotalSupply -DA:90,0 -BRDA:90,1,0,- -BRDA:90,1,1,- -DA:91,0 -FN:97,ERC20Votes._checkpointsLookup -FNDA:0,ERC20Votes._checkpointsLookup -DA:110,0 -DA:112,0 -DA:113,0 -DA:115,0 -BRDA:115,2,0,- -BRDA:115,2,1,- -DA:116,0 -DA:117,0 -BRDA:117,3,0,- -BRDA:117,3,1,- -DA:118,0 -DA:120,0 -DA:124,0 -DA:125,0 -DA:126,0 -BRDA:126,4,0,- -BRDA:126,4,1,- -DA:127,0 -DA:129,0 -DA:133,0 -FN:139,ERC20Votes.delegate -FNDA:0,ERC20Votes.delegate -DA:140,0 -FN:146,ERC20Votes.delegateBySig -FNDA:0,ERC20Votes.delegateBySig -DA:154,0 -BRDA:154,5,0,- -BRDA:154,5,1,- -DA:155,0 -DA:161,0 -BRDA:161,6,0,- -BRDA:161,6,1,- -DA:162,0 -FN:168,ERC20Votes._maxSupply -FNDA:0,ERC20Votes._maxSupply -DA:169,0 -FN:175,ERC20Votes._mint -FNDA:0,ERC20Votes._mint -DA:176,0 -DA:177,0 -BRDA:177,7,0,- -BRDA:177,7,1,- -DA:179,0 -FN:185,ERC20Votes._burn -FNDA:0,ERC20Votes._burn -DA:186,0 -DA:188,0 -FN:196,ERC20Votes._afterTokenTransfer -FNDA:0,ERC20Votes._afterTokenTransfer -DA:201,0 -DA:203,0 -FN:211,ERC20Votes._delegate -FNDA:0,ERC20Votes._delegate -DA:212,0 -DA:213,0 -DA:214,0 -DA:216,0 -DA:218,0 -FN:221,ERC20Votes._moveVotingPower -FNDA:0,ERC20Votes._moveVotingPower -DA:226,0 -BRDA:226,8,0,- -BRDA:226,8,1,- -DA:227,0 -BRDA:227,9,0,- -BRDA:227,9,1,- -DA:228,0 -DA:229,0 -DA:232,0 -BRDA:232,10,0,- -BRDA:232,10,1,- -DA:233,0 -DA:234,0 -FN:239,ERC20Votes._writeCheckpoint -FNDA:0,ERC20Votes._writeCheckpoint -DA:244,0 -DA:246,0 -DA:248,0 -DA:249,0 -DA:251,0 -BRDA:251,11,0,- -BRDA:251,11,1,- -DA:252,0 -DA:254,0 -FN:258,ERC20Votes._add -FNDA:0,ERC20Votes._add -DA:259,0 -FN:262,ERC20Votes._subtract -FNDA:0,ERC20Votes._subtract -DA:263,0 -FN:266,ERC20Votes._unsafeAccess -FNDA:0,ERC20Votes._unsafeAccess -DA:269,0 -FNF:19 -FNH:0 -LF:58 -LH:0 -BRF:24 -BRH:0 -end_of_record -TN: -SF:node_modules/@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol -FN:49,ERC20Permit.permit -FNDA:0,ERC20Permit.permit -DA:58,0 -BRDA:58,0,0,- -BRDA:58,0,1,- -DA:60,0 -DA:62,0 -DA:64,0 -DA:65,0 -BRDA:65,1,0,- -BRDA:65,1,1,- -DA:67,0 -FN:73,ERC20Permit.nonces -FNDA:0,ERC20Permit.nonces -DA:74,0 -FN:81,ERC20Permit.DOMAIN_SEPARATOR -FNDA:0,ERC20Permit.DOMAIN_SEPARATOR -DA:82,0 -FN:90,ERC20Permit._useNonce -FNDA:0,ERC20Permit._useNonce -DA:91,0 -DA:92,0 -DA:93,0 -FNF:4 -FNH:0 -LF:11 -LH:0 -BRF:4 -BRH:0 -end_of_record -TN: -SF:node_modules/@openzeppelin/contracts/token/ERC721/ERC721.sol -FN:52,ERC721.supportsInterface -FNDA:0,ERC721.supportsInterface -DA:53,0 -FN:62,ERC721.balanceOf -FNDA:0,ERC721.balanceOf -DA:63,0 -BRDA:63,0,0,- -BRDA:63,0,1,- -DA:64,0 -FN:70,ERC721.ownerOf -FNDA:13,ERC721.ownerOf -DA:71,13 -DA:72,13 -BRDA:72,1,0,- -BRDA:72,1,1,13 -DA:73,13 -FN:79,ERC721.name -FNDA:0,ERC721.name -DA:80,0 -FN:86,ERC721.symbol -FNDA:0,ERC721.symbol +DA:79,0 DA:87,0 -FN:93,ERC721.tokenURI -FNDA:0,ERC721.tokenURI -DA:94,0 -DA:96,0 -DA:97,0 -FN:105,ERC721._baseURI -FNDA:0,ERC721._baseURI -DA:106,0 -FN:112,ERC721.approve -FNDA:0,ERC721.approve -DA:113,0 -DA:114,0 -BRDA:114,2,0,- -BRDA:114,2,1,- -DA:116,0 -BRDA:116,3,0,- -BRDA:116,3,1,- -DA:121,0 -FN:127,ERC721.getApproved -FNDA:0,ERC721.getApproved -DA:128,0 -DA:130,0 -FN:136,ERC721.setApprovalForAll -FNDA:0,ERC721.setApprovalForAll -DA:137,0 -FN:143,ERC721.isApprovedForAll -FNDA:0,ERC721.isApprovedForAll -DA:144,0 -FN:150,ERC721.transferFrom -FNDA:0,ERC721.transferFrom -DA:156,0 -BRDA:156,4,0,- -BRDA:156,4,1,- -DA:158,0 -FN:164,ERC721.safeTransferFrom -FNDA:0,ERC721.safeTransferFrom -DA:169,0 -FN:175,ERC721.safeTransferFrom -FNDA:0,ERC721.safeTransferFrom -DA:181,0 -BRDA:181,5,0,- -BRDA:181,5,1,- -DA:182,0 -FN:203,ERC721._safeTransfer -FNDA:0,ERC721._safeTransfer -DA:209,0 -DA:210,0 -BRDA:210,6,0,- -BRDA:210,6,1,- -FN:216,ERC721._ownerOf -FNDA:17,ERC721._ownerOf -DA:217,17 -FN:228,ERC721._exists -FNDA:4,ERC721._exists -DA:229,4 -FN:239,ERC721._isApprovedOrOwner -FNDA:0,ERC721._isApprovedOrOwner -DA:240,0 -DA:241,0 -FN:254,ERC721._safeMint -FNDA:2,ERC721._safeMint -DA:255,2 -FN:262,ERC721._safeMint -FNDA:2,ERC721._safeMint -DA:267,2 -DA:268,2 -BRDA:268,7,0,- -BRDA:268,7,1,2 -FN:286,ERC721._mint -FNDA:2,ERC721._mint -DA:287,2 -BRDA:287,8,0,- -BRDA:287,8,1,2 -DA:288,2 -BRDA:288,9,0,- -BRDA:288,9,1,2 -DA:290,2 -DA:293,2 -BRDA:293,10,0,- -BRDA:293,10,1,2 -DA:300,2 -DA:303,2 -DA:305,2 -DA:307,2 -FN:321,ERC721._burn -FNDA:0,ERC721._burn -DA:322,0 -DA:324,0 -DA:327,0 -DA:330,0 -DA:335,0 -DA:337,0 -DA:339,0 -DA:341,0 -FN:355,ERC721._transfer -FNDA:0,ERC721._transfer -DA:360,0 -BRDA:360,11,0,- -BRDA:360,11,1,- -DA:361,0 -BRDA:361,12,0,- -BRDA:361,12,1,- -DA:363,0 -DA:366,0 -BRDA:366,13,0,- -BRDA:366,13,1,- -DA:369,0 -DA:377,0 -DA:378,0 -DA:380,0 -DA:382,0 -DA:384,0 -FN:392,ERC721._approve -FNDA:0,ERC721._approve -DA:393,0 -DA:394,0 -FN:402,ERC721._setApprovalForAll -FNDA:0,ERC721._setApprovalForAll -DA:407,0 -BRDA:407,14,0,- -BRDA:407,14,1,- -DA:408,0 -DA:409,0 -FN:415,ERC721._requireMinted -FNDA:0,ERC721._requireMinted -DA:416,0 -BRDA:416,15,0,- -BRDA:416,15,1,- -FN:429,ERC721._checkOnERC721Received -FNDA:2,ERC721._checkOnERC721Received -DA:435,2 -BRDA:435,16,0,- -BRDA:435,16,1,- -DA:436,0 -DA:449,2 -FN:467,ERC721._beforeTokenTransfer -FNDA:2,ERC721._beforeTokenTransfer -DA:473,2 -BRDA:473,17,0,- -BRDA:473,17,1,- -DA:474,0 -BRDA:474,18,0,- -BRDA:474,18,1,- -DA:475,0 -DA:477,0 -BRDA:477,19,0,- -BRDA:477,19,1,- -DA:478,0 -FN:497,ERC721._afterTokenTransfer -FNDA:2,ERC721._afterTokenTransfer -FNF:29 -FNH:9 -LF:74 -LH:19 -BRF:40 -BRH:5 -end_of_record -TN: -SF:node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol -FN:25,ERC721Votes._afterTokenTransfer -FNDA:2,ERC721Votes._afterTokenTransfer -DA:31,2 -DA:32,2 -FN:38,ERC721Votes._getVotingUnits -FNDA:0,ERC721Votes._getVotingUnits -DA:39,0 +DA:88,0 FNF:2 -FNH:1 -LF:3 -LH:2 -BRF:0 -BRH:0 -end_of_record -TN: -SF:node_modules/@openzeppelin/contracts/utils/Address.sol -FN:36,Address.isContract -FNDA:0,Address.isContract -DA:41,0 -FN:60,Address.sendValue -FNDA:0,Address.sendValue -DA:61,0 -BRDA:61,0,0,- -BRDA:61,0,1,- -DA:63,0 -DA:64,0 -BRDA:64,1,0,- -BRDA:64,1,1,- -FN:85,Address.functionCall -FNDA:0,Address.functionCall -DA:86,0 -FN:95,Address.functionCall -FNDA:0,Address.functionCall -DA:100,0 -FN:114,Address.functionCallWithValue -FNDA:0,Address.functionCallWithValue -DA:119,0 -FN:128,Address.functionCallWithValue -FNDA:0,Address.functionCallWithValue -DA:134,0 -BRDA:134,2,0,- -BRDA:134,2,1,- -DA:135,0 -DA:136,0 -FN:145,Address.functionStaticCall -FNDA:0,Address.functionStaticCall -DA:146,0 -FN:155,Address.functionStaticCall -FNDA:0,Address.functionStaticCall -DA:160,0 -DA:161,0 -FN:170,Address.functionDelegateCall -FNDA:0,Address.functionDelegateCall -DA:171,0 -FN:180,Address.functionDelegateCall -FNDA:0,Address.functionDelegateCall -DA:185,0 -DA:186,0 -FN:195,Address.verifyCallResultFromTarget -FNDA:0,Address.verifyCallResultFromTarget -DA:201,0 -BRDA:201,3,0,- -BRDA:201,3,1,- -DA:202,0 -BRDA:202,4,0,- -BRDA:202,4,1,- -DA:205,0 -BRDA:205,5,0,- -BRDA:205,5,1,- -DA:207,0 -DA:209,0 -FN:219,Address.verifyCallResult -FNDA:0,Address.verifyCallResult -DA:224,0 -BRDA:224,6,0,- -BRDA:224,6,1,- -DA:225,0 -DA:227,0 -FN:231,Address._revert -FNDA:0,Address._revert -DA:233,0 -BRDA:233,7,0,- -BRDA:233,7,1,- -DA:241,0 -FNF:13 -FNH:0 -LF:26 -LH:0 -BRF:16 -BRH:0 -end_of_record -TN: -SF:node_modules/@openzeppelin/contracts/utils/Checkpoints.sol -FN:33,Checkpoints.getAtBlock -FNDA:0,Checkpoints.getAtBlock -DA:34,0 -BRDA:34,0,0,- -BRDA:34,0,1,- -DA:35,0 -DA:37,0 -DA:38,0 -DA:39,0 -FN:48,Checkpoints.getAtProbablyRecentBlock -FNDA:0,Checkpoints.getAtProbablyRecentBlock -DA:49,0 -BRDA:49,1,0,- -BRDA:49,1,1,- -DA:50,0 -DA:52,0 -DA:54,0 -DA:55,0 -DA:57,0 -BRDA:57,2,0,- -BRDA:57,2,1,- -DA:58,0 -DA:59,0 -BRDA:59,3,0,- -BRDA:59,3,1,- -DA:60,0 -DA:62,0 -DA:66,0 -DA:68,0 -FN:76,Checkpoints.push -FNDA:0,Checkpoints.push -DA:77,0 -FN:86,Checkpoints.push -FNDA:0,Checkpoints.push -DA:91,0 -FN:97,Checkpoints.latest -FNDA:0,Checkpoints.latest -DA:98,0 -DA:99,0 -FN:106,Checkpoints.latestCheckpoint -FNDA:0,Checkpoints.latestCheckpoint -DA:115,0 -DA:116,0 -BRDA:116,4,0,- -BRDA:116,4,1,- -DA:117,0 -DA:119,0 -DA:120,0 -FN:127,Checkpoints.length -FNDA:0,Checkpoints.length -DA:128,0 -FN:135,Checkpoints._insert -FNDA:0,Checkpoints._insert -DA:140,0 -DA:142,0 -BRDA:142,5,0,- -BRDA:142,5,1,- -DA:144,0 -DA:147,0 -BRDA:147,6,0,- -BRDA:147,6,1,- -DA:150,0 -BRDA:150,7,0,- -BRDA:150,7,1,- -DA:151,0 -DA:153,0 -DA:155,0 -DA:157,0 -DA:158,0 -FN:168,Checkpoints._upperBinaryLookup -FNDA:0,Checkpoints._upperBinaryLookup -DA:174,0 -DA:175,0 -DA:176,0 -BRDA:176,8,0,- -BRDA:176,8,1,- -DA:177,0 -DA:179,0 -DA:182,0 -FN:191,Checkpoints._lowerBinaryLookup -FNDA:0,Checkpoints._lowerBinaryLookup -DA:197,0 -DA:198,0 -DA:199,0 -BRDA:199,9,0,- -BRDA:199,9,1,- -DA:200,0 -DA:202,0 -DA:205,0 -FN:208,Checkpoints._unsafeAccess -FNDA:0,Checkpoints._unsafeAccess -DA:211,0 -FN:229,Checkpoints.push -FNDA:0,Checkpoints.push -DA:234,0 -FN:240,Checkpoints.lowerLookup -FNDA:0,Checkpoints.lowerLookup -DA:241,0 -DA:242,0 -DA:243,0 -FN:249,Checkpoints.upperLookup -FNDA:0,Checkpoints.upperLookup -DA:250,0 -DA:251,0 -DA:252,0 -FN:258,Checkpoints.latest -FNDA:0,Checkpoints.latest -DA:259,0 -DA:260,0 -FN:267,Checkpoints.latestCheckpoint -FNDA:0,Checkpoints.latestCheckpoint -DA:276,0 -DA:277,0 -BRDA:277,10,0,- -BRDA:277,10,1,- -DA:278,0 -DA:280,0 -DA:281,0 -FN:288,Checkpoints.length -FNDA:0,Checkpoints.length -DA:289,0 -FN:296,Checkpoints._insert -FNDA:0,Checkpoints._insert -DA:301,0 -DA:303,0 -BRDA:303,11,0,- -BRDA:303,11,1,- -DA:305,0 -DA:308,0 -BRDA:308,12,0,- -BRDA:308,12,1,- -DA:311,0 -BRDA:311,13,0,- -BRDA:311,13,1,- -DA:312,0 -DA:314,0 -DA:316,0 -DA:318,0 -DA:319,0 -FN:329,Checkpoints._upperBinaryLookup -FNDA:0,Checkpoints._upperBinaryLookup -DA:335,0 -DA:336,0 -DA:337,0 -BRDA:337,14,0,- -BRDA:337,14,1,- -DA:338,0 -DA:340,0 -DA:343,0 -FN:352,Checkpoints._lowerBinaryLookup -FNDA:0,Checkpoints._lowerBinaryLookup -DA:358,0 -DA:359,0 -DA:360,0 -BRDA:360,15,0,- -BRDA:360,15,1,- -DA:361,0 -DA:363,0 -DA:366,0 -FN:369,Checkpoints._unsafeAccess -FNDA:0,Checkpoints._unsafeAccess -DA:376,0 -FN:394,Checkpoints.push -FNDA:0,Checkpoints.push -DA:399,0 -FN:405,Checkpoints.lowerLookup -FNDA:0,Checkpoints.lowerLookup -DA:406,0 -DA:407,0 -DA:408,0 -FN:414,Checkpoints.upperLookup -FNDA:0,Checkpoints.upperLookup -DA:415,0 -DA:416,0 -DA:417,0 -FN:423,Checkpoints.latest -FNDA:0,Checkpoints.latest -DA:424,0 -DA:425,0 -FN:432,Checkpoints.latestCheckpoint -FNDA:0,Checkpoints.latestCheckpoint -DA:441,0 -DA:442,0 -BRDA:442,16,0,- -BRDA:442,16,1,- -DA:443,0 -DA:445,0 -DA:446,0 -FN:453,Checkpoints.length -FNDA:0,Checkpoints.length -DA:454,0 -FN:461,Checkpoints._insert -FNDA:0,Checkpoints._insert -DA:466,0 -DA:468,0 -BRDA:468,17,0,- -BRDA:468,17,1,- -DA:470,0 -DA:473,0 -BRDA:473,18,0,- -BRDA:473,18,1,- -DA:476,0 -BRDA:476,19,0,- -BRDA:476,19,1,- -DA:477,0 -DA:479,0 -DA:481,0 -DA:483,0 -DA:484,0 -FN:494,Checkpoints._upperBinaryLookup -FNDA:0,Checkpoints._upperBinaryLookup -DA:500,0 -DA:501,0 -DA:502,0 -BRDA:502,20,0,- -BRDA:502,20,1,- -DA:503,0 -DA:505,0 -DA:508,0 -FN:517,Checkpoints._lowerBinaryLookup -FNDA:0,Checkpoints._lowerBinaryLookup -DA:523,0 -DA:524,0 -DA:525,0 -BRDA:525,21,0,- -BRDA:525,21,1,- -DA:526,0 -DA:528,0 -DA:531,0 -FN:534,Checkpoints._unsafeAccess -FNDA:0,Checkpoints._unsafeAccess -DA:541,0 -FNF:31 FNH:0 -LF:126 +LF:28 LH:0 -BRF:44 +BRF:6 BRH:0 end_of_record TN: -SF:node_modules/@openzeppelin/contracts/utils/Context.sol -FN:17,Context._msgSender -FNDA:0,Context._msgSender -DA:18,0 -FN:21,Context._msgData -FNDA:0,Context._msgData -DA:22,0 +SF:contracts/test/JBBuybackDelegate_Unit.t.sol +FN:804,ForTest_BuybackDelegate.ForTest_getQuote +FNDA:256,ForTest_BuybackDelegate.ForTest_getQuote +DA:805,256 +FN:808,ForTest_BuybackDelegate.ForTest_setSecondsAgo +FNDA:512,ForTest_BuybackDelegate.ForTest_setSecondsAgo +DA:809,512 FNF:2 -FNH:0 +FNH:2 LF:2 -LH:0 +LH:2 BRF:0 BRH:0 end_of_record TN: -SF:node_modules/@openzeppelin/contracts/utils/Counters.sol -FN:22,Counters.current -FNDA:0,Counters.current -DA:23,0 -FN:26,Counters.increment -FNDA:0,Counters.increment -DA:28,0 -FN:32,Counters.decrement -FNDA:0,Counters.decrement -DA:33,0 -DA:34,0 -BRDA:34,0,0,- -BRDA:34,0,1,- -DA:36,0 -FN:40,Counters.reset -FNDA:0,Counters.reset -DA:41,0 -FNF:4 -FNH:0 -LF:6 -LH:0 -BRF:2 +SF:contracts/test/JBGenericBuybackDelegate_Unit.t.sol +FN:1381,ForTest_JBGenericBuybackDelegate.ForTest_getQuote +FNDA:256,ForTest_JBGenericBuybackDelegate.ForTest_getQuote +DA:1386,256 +FN:1389,ForTest_JBGenericBuybackDelegate.ForTest_initPool +FNDA:2,ForTest_JBGenericBuybackDelegate.ForTest_initPool +DA:1397,2 +DA:1398,2 +DA:1399,2 +FNF:2 +FNH:2 +LF:4 +LH:4 +BRF:0 BRH:0 end_of_record TN: -SF:node_modules/@openzeppelin/contracts/utils/Strings.sol -FN:18,Strings.toString -FNDA:0,Strings.toString -DA:20,0 -DA:21,0 +SF:contracts/test/helpers/AccessJBLib.sol +FN:9,AccessJBLib.ETH +FNDA:0,AccessJBLib.ETH +DA:10,0 +FN:13,AccessJBLib.USD +FNDA:0,AccessJBLib.USD +DA:14,0 +FN:17,AccessJBLib.ETHToken +FNDA:0,AccessJBLib.ETHToken +DA:18,0 +FN:21,AccessJBLib.MAX_FEE +FNDA:0,AccessJBLib.MAX_FEE DA:22,0 -DA:25,0 -DA:28,0 -DA:33,0 -DA:34,0 -BRDA:34,0,0,- -BRDA:34,0,1,- -DA:36,0 -FN:43,Strings.toHexString -FNDA:0,Strings.toHexString -DA:45,0 -FN:52,Strings.toHexString -FNDA:0,Strings.toHexString -DA:53,0 -DA:54,0 -DA:55,0 -DA:56,0 -DA:57,0 -DA:58,0 -DA:60,0 -BRDA:60,1,0,- -BRDA:60,1,1,- -DA:61,0 -FN:67,Strings.toHexString -FNDA:0,Strings.toHexString -DA:68,0 -FNF:4 -FNH:0 -LF:18 -LH:0 -BRF:4 -BRH:0 -end_of_record -TN: -SF:node_modules/@openzeppelin/contracts/utils/cryptography/ECDSA.sol -FN:23,ECDSA._throwError -FNDA:0,ECDSA._throwError -DA:24,0 -BRDA:24,0,0,- -BRDA:24,0,1,- -DA:25,0 +FN:25,AccessJBLib.SPLITS_TOTAL_PERCENT +FNDA:0,AccessJBLib.SPLITS_TOTAL_PERCENT DA:26,0 -BRDA:26,1,0,- -BRDA:26,1,1,- -DA:27,0 -DA:28,0 -BRDA:28,2,0,- -BRDA:28,2,1,- -DA:29,0 -DA:30,0 -BRDA:30,3,0,- -BRDA:30,3,1,- -DA:31,0 -FN:55,ECDSA.tryRecover -FNDA:0,ECDSA.tryRecover -DA:56,0 -BRDA:56,4,0,- -BRDA:56,4,1,- -DA:57,0 -DA:58,0 -DA:59,0 -DA:64,0 -DA:65,0 -DA:66,0 -DA:68,0 -DA:70,0 -FN:88,ECDSA.recover -FNDA:0,ECDSA.recover -DA:89,0 -DA:90,0 -DA:91,0 -FN:101,ECDSA.tryRecover -FNDA:0,ECDSA.tryRecover -DA:106,0 -DA:107,0 -DA:108,0 -FN:116,ECDSA.recover -FNDA:0,ECDSA.recover -DA:121,0 -DA:122,0 -DA:123,0 -FN:132,ECDSA.tryRecover -FNDA:0,ECDSA.tryRecover -DA:147,0 -BRDA:147,5,0,- -BRDA:147,5,1,- -DA:148,0 -DA:152,0 -DA:153,0 -BRDA:153,6,0,- -BRDA:153,6,1,- -DA:154,0 -DA:157,0 -FN:164,ECDSA.recover -FNDA:0,ECDSA.recover -DA:170,0 -DA:171,0 -DA:172,0 -FN:183,ECDSA.toEthSignedMessageHash -FNDA:0,ECDSA.toEthSignedMessageHash -DA:186,0 -FN:197,ECDSA.toEthSignedMessageHash -FNDA:0,ECDSA.toEthSignedMessageHash -DA:198,0 -FN:210,ECDSA.toTypedDataHash -FNDA:0,ECDSA.toTypedDataHash -DA:211,0 -FNF:10 -FNH:0 -LF:38 -LH:0 -BRF:14 -BRH:0 -end_of_record -TN: -SF:node_modules/@openzeppelin/contracts/utils/cryptography/EIP712.sol -FN:70,EIP712._domainSeparatorV4 -FNDA:0,EIP712._domainSeparatorV4 -DA:71,0 -BRDA:71,0,0,- -BRDA:71,0,1,- -DA:72,0 -DA:74,0 -FN:78,EIP712._buildDomainSeparator -FNDA:0,EIP712._buildDomainSeparator -DA:83,0 -FN:101,EIP712._hashTypedDataV4 -FNDA:0,EIP712._hashTypedDataV4 -DA:102,0 -FNF:3 +FNF:5 FNH:0 LF:5 LH:0 -BRF:2 +BRF:0 BRH:0 end_of_record TN: -SF:node_modules/@openzeppelin/contracts/utils/introspection/ERC165.sol -FN:26,ERC165.supportsInterface -FNDA:0,ERC165.supportsInterface -DA:27,0 -FNF:1 +SF:contracts/test/helpers/PoolAddress.sol +FN:21,PoolAddress.getPoolKey +FNDA:0,PoolAddress.getPoolKey +DA:22,0 +BRDA:22,0,0,- +BRDA:22,0,1,- +DA:23,0 +FN:30,PoolAddress.computeAddress +FNDA:0,PoolAddress.computeAddress +DA:31,0 +BRDA:31,1,0,- +BRDA:31,1,1,- +DA:32,0 +FNF:2 FNH:0 -LF:1 +LF:4 LH:0 -BRF:0 +BRF:4 BRH:0 end_of_record TN: -SF:node_modules/@openzeppelin/contracts/utils/math/Math.sol -FN:19,Math.max -FNDA:0,Math.max -DA:20,0 -FN:26,Math.min -FNDA:0,Math.min -DA:27,0 -FN:34,Math.average -FNDA:0,Math.average -DA:36,0 -FN:45,Math.ceilDiv -FNDA:0,Math.ceilDiv -DA:47,0 -FN:55,Math.mulDiv -FNDA:0,Math.mulDiv -DA:64,0 -DA:65,0 -DA:68,0 +SF:contracts/test/helpers/TestBaseWorkflowV3.sol +FN:68,TestBaseWorkflowV3.jbLibraries +FNDA:0,TestBaseWorkflowV3.jbLibraries DA:69,0 -DA:73,0 -BRDA:73,0,0,- -BRDA:73,0,1,- -DA:74,0 -DA:78,0 -BRDA:78,1,0,- -BRDA:78,1,1,- -DA:85,0 +FN:77,TestBaseWorkflowV3.setUp +FNDA:0,TestBaseWorkflowV3.setUp +DA:79,0 +DA:80,0 +DA:83,0 +DA:84,0 +DA:87,0 DA:88,0 DA:91,0 DA:92,0 -DA:99,0 +DA:94,0 +DA:97,0 +DA:98,0 +DA:101,0 DA:102,0 DA:105,0 -DA:108,0 +DA:106,0 +DA:109,0 +DA:110,0 DA:112,0 -DA:117,0 -DA:121,0 -DA:122,0 -DA:123,0 -DA:124,0 +DA:113,0 +DA:116,0 DA:125,0 -DA:126,0 -DA:132,0 -DA:133,0 -FN:140,Math.mulDiv -FNDA:0,Math.mulDiv -DA:146,0 -DA:147,0 -BRDA:147,2,0,- -BRDA:147,2,1,- -DA:148,0 -DA:150,0 -FN:158,Math.sqrt -FNDA:0,Math.sqrt -DA:159,0 -BRDA:159,3,0,- -BRDA:159,3,1,- -DA:160,0 -DA:173,0 -DA:180,0 -DA:181,0 -DA:182,0 -DA:183,0 -DA:184,0 -DA:185,0 -DA:186,0 -DA:187,0 -FN:194,Math.sqrt -FNDA:0,Math.sqrt -DA:196,0 -DA:197,0 -FN:205,Math.log2 -FNDA:0,Math.log2 -DA:206,0 -DA:208,0 -BRDA:208,4,0,- -BRDA:208,4,1,- -DA:209,0 -DA:210,0 -DA:212,0 -BRDA:212,5,0,- -BRDA:212,5,1,- -DA:213,0 -DA:214,0 -DA:216,0 -BRDA:216,6,0,- -BRDA:216,6,1,- -DA:217,0 -DA:218,0 -DA:220,0 -BRDA:220,7,0,- -BRDA:220,7,1,- -DA:221,0 -DA:222,0 -DA:224,0 -BRDA:224,8,0,- -BRDA:224,8,1,- -DA:225,0 -DA:226,0 -DA:228,0 -BRDA:228,9,0,- -BRDA:228,9,1,- -DA:229,0 -DA:230,0 -DA:232,0 -BRDA:232,10,0,- -BRDA:232,10,1,- -DA:233,0 -DA:234,0 -DA:236,0 -BRDA:236,11,0,- -BRDA:236,11,1,- -DA:237,0 -DA:240,0 -FN:247,Math.log2 -FNDA:0,Math.log2 -DA:249,0 -DA:250,0 -FN:258,Math.log10 -FNDA:0,Math.log10 -DA:259,0 -DA:261,0 -BRDA:261,12,0,- -BRDA:261,12,1,- -DA:262,0 -DA:263,0 -DA:265,0 -BRDA:265,13,0,- -BRDA:265,13,1,- -DA:266,0 -DA:267,0 -DA:269,0 -BRDA:269,14,0,- -BRDA:269,14,1,- -DA:270,0 -DA:271,0 -DA:273,0 -BRDA:273,15,0,- -BRDA:273,15,1,- -DA:274,0 -DA:275,0 -DA:277,0 -BRDA:277,16,0,- -BRDA:277,16,1,- -DA:278,0 -DA:279,0 -DA:281,0 -BRDA:281,17,0,- -BRDA:281,17,1,- -DA:282,0 -DA:283,0 -DA:285,0 -BRDA:285,18,0,- -BRDA:285,18,1,- -DA:286,0 -DA:289,0 -FN:296,Math.log10 -FNDA:0,Math.log10 -DA:298,0 -DA:299,0 -FN:309,Math.log256 -FNDA:0,Math.log256 -DA:310,0 -DA:312,0 -BRDA:312,19,0,- -BRDA:312,19,1,- -DA:313,0 -DA:314,0 -DA:316,0 -BRDA:316,20,0,- -BRDA:316,20,1,- -DA:317,0 -DA:318,0 -DA:320,0 -BRDA:320,21,0,- -BRDA:320,21,1,- -DA:321,0 -DA:322,0 -DA:324,0 -BRDA:324,22,0,- -BRDA:324,22,1,- -DA:325,0 -DA:326,0 -DA:328,0 -BRDA:328,23,0,- -BRDA:328,23,1,- -DA:329,0 -DA:332,0 -FN:339,Math.log256 -FNDA:0,Math.log256 -DA:341,0 -DA:342,0 -FNF:14 -FNH:0 -LF:115 -LH:0 -BRF:48 -BRH:0 -end_of_record -TN: -SF:node_modules/@openzeppelin/contracts/utils/math/SafeCast.sol -FN:35,SafeCast.toUint248 -FNDA:0,SafeCast.toUint248 -DA:36,0 -BRDA:36,0,0,- -BRDA:36,0,1,- -DA:37,0 -FN:52,SafeCast.toUint240 -FNDA:0,SafeCast.toUint240 -DA:53,0 -BRDA:53,1,0,- -BRDA:53,1,1,- -DA:54,0 -FN:69,SafeCast.toUint232 -FNDA:0,SafeCast.toUint232 -DA:70,0 -BRDA:70,2,0,- -BRDA:70,2,1,- -DA:71,0 -FN:86,SafeCast.toUint224 -FNDA:0,SafeCast.toUint224 -DA:87,0 -BRDA:87,3,0,- -BRDA:87,3,1,- -DA:88,0 -FN:103,SafeCast.toUint216 -FNDA:0,SafeCast.toUint216 -DA:104,0 -BRDA:104,4,0,- -BRDA:104,4,1,- -DA:105,0 -FN:120,SafeCast.toUint208 -FNDA:0,SafeCast.toUint208 -DA:121,0 -BRDA:121,5,0,- -BRDA:121,5,1,- -DA:122,0 -FN:137,SafeCast.toUint200 -FNDA:0,SafeCast.toUint200 -DA:138,0 -BRDA:138,6,0,- -BRDA:138,6,1,- +DA:127,0 +DA:128,0 +DA:131,0 +DA:136,0 DA:139,0 -FN:154,SafeCast.toUint192 -FNDA:0,SafeCast.toUint192 -DA:155,0 -BRDA:155,7,0,- -BRDA:155,7,1,- -DA:156,0 -FN:171,SafeCast.toUint184 -FNDA:0,SafeCast.toUint184 -DA:172,0 -BRDA:172,8,0,- -BRDA:172,8,1,- -DA:173,0 -FN:188,SafeCast.toUint176 -FNDA:0,SafeCast.toUint176 -DA:189,0 -BRDA:189,9,0,- -BRDA:189,9,1,- -DA:190,0 -FN:205,SafeCast.toUint168 -FNDA:0,SafeCast.toUint168 -DA:206,0 -BRDA:206,10,0,- -BRDA:206,10,1,- -DA:207,0 -FN:222,SafeCast.toUint160 -FNDA:0,SafeCast.toUint160 -DA:223,0 -BRDA:223,11,0,- -BRDA:223,11,1,- -DA:224,0 -FN:239,SafeCast.toUint152 -FNDA:0,SafeCast.toUint152 -DA:240,0 -BRDA:240,12,0,- -BRDA:240,12,1,- -DA:241,0 -FN:256,SafeCast.toUint144 -FNDA:0,SafeCast.toUint144 -DA:257,0 -BRDA:257,13,0,- -BRDA:257,13,1,- -DA:258,0 -FN:273,SafeCast.toUint136 -FNDA:0,SafeCast.toUint136 -DA:274,0 -BRDA:274,14,0,- -BRDA:274,14,1,- -DA:275,0 -FN:290,SafeCast.toUint128 -FNDA:0,SafeCast.toUint128 -DA:291,0 -BRDA:291,15,0,- -BRDA:291,15,1,- -DA:292,0 -FN:307,SafeCast.toUint120 -FNDA:0,SafeCast.toUint120 -DA:308,0 -BRDA:308,16,0,- -BRDA:308,16,1,- -DA:309,0 -FN:324,SafeCast.toUint112 -FNDA:0,SafeCast.toUint112 -DA:325,0 -BRDA:325,17,0,- -BRDA:325,17,1,- -DA:326,0 -FN:341,SafeCast.toUint104 -FNDA:0,SafeCast.toUint104 -DA:342,0 -BRDA:342,18,0,- -BRDA:342,18,1,- -DA:343,0 -FN:358,SafeCast.toUint96 -FNDA:0,SafeCast.toUint96 -DA:359,0 -BRDA:359,19,0,- -BRDA:359,19,1,- -DA:360,0 -FN:375,SafeCast.toUint88 -FNDA:0,SafeCast.toUint88 -DA:376,0 -BRDA:376,20,0,- -BRDA:376,20,1,- -DA:377,0 -FN:392,SafeCast.toUint80 -FNDA:0,SafeCast.toUint80 -DA:393,0 -BRDA:393,21,0,- -BRDA:393,21,1,- -DA:394,0 -FN:409,SafeCast.toUint72 -FNDA:0,SafeCast.toUint72 -DA:410,0 -BRDA:410,22,0,- -BRDA:410,22,1,- -DA:411,0 -FN:426,SafeCast.toUint64 -FNDA:0,SafeCast.toUint64 -DA:427,0 -BRDA:427,23,0,- -BRDA:427,23,1,- -DA:428,0 -FN:443,SafeCast.toUint56 -FNDA:0,SafeCast.toUint56 -DA:444,0 -BRDA:444,24,0,- -BRDA:444,24,1,- -DA:445,0 -FN:460,SafeCast.toUint48 -FNDA:0,SafeCast.toUint48 -DA:461,0 -BRDA:461,25,0,- -BRDA:461,25,1,- -DA:462,0 -FN:477,SafeCast.toUint40 -FNDA:0,SafeCast.toUint40 -DA:478,0 -BRDA:478,26,0,- -BRDA:478,26,1,- -DA:479,0 -FN:494,SafeCast.toUint32 -FNDA:0,SafeCast.toUint32 -DA:495,0 -BRDA:495,27,0,- -BRDA:495,27,1,- -DA:496,0 -FN:511,SafeCast.toUint24 -FNDA:0,SafeCast.toUint24 -DA:512,0 -BRDA:512,28,0,- -BRDA:512,28,1,- -DA:513,0 -FN:528,SafeCast.toUint16 -FNDA:0,SafeCast.toUint16 -DA:529,0 -BRDA:529,29,0,- -BRDA:529,29,1,- -DA:530,0 -FN:545,SafeCast.toUint8 -FNDA:0,SafeCast.toUint8 -DA:546,0 -BRDA:546,30,0,- -BRDA:546,30,1,- -DA:547,0 -FN:559,SafeCast.toUint256 -FNDA:0,SafeCast.toUint256 -DA:560,0 -BRDA:560,31,0,- -BRDA:560,31,1,- -DA:561,0 -FN:577,SafeCast.toInt248 -FNDA:0,SafeCast.toInt248 -DA:578,0 -DA:579,0 -BRDA:579,32,0,- -BRDA:579,32,1,- -FN:595,SafeCast.toInt240 -FNDA:0,SafeCast.toInt240 -DA:596,0 -DA:597,0 -BRDA:597,33,0,- -BRDA:597,33,1,- -FN:613,SafeCast.toInt232 -FNDA:0,SafeCast.toInt232 -DA:614,0 -DA:615,0 -BRDA:615,34,0,- -BRDA:615,34,1,- -FN:631,SafeCast.toInt224 -FNDA:0,SafeCast.toInt224 -DA:632,0 -DA:633,0 -BRDA:633,35,0,- -BRDA:633,35,1,- -FN:649,SafeCast.toInt216 -FNDA:0,SafeCast.toInt216 -DA:650,0 -DA:651,0 -BRDA:651,36,0,- -BRDA:651,36,1,- -FN:667,SafeCast.toInt208 -FNDA:0,SafeCast.toInt208 -DA:668,0 -DA:669,0 -BRDA:669,37,0,- -BRDA:669,37,1,- -FN:685,SafeCast.toInt200 -FNDA:0,SafeCast.toInt200 -DA:686,0 -DA:687,0 -BRDA:687,38,0,- -BRDA:687,38,1,- -FN:703,SafeCast.toInt192 -FNDA:0,SafeCast.toInt192 -DA:704,0 -DA:705,0 -BRDA:705,39,0,- -BRDA:705,39,1,- -FN:721,SafeCast.toInt184 -FNDA:0,SafeCast.toInt184 -DA:722,0 -DA:723,0 -BRDA:723,40,0,- -BRDA:723,40,1,- -FN:739,SafeCast.toInt176 -FNDA:0,SafeCast.toInt176 -DA:740,0 -DA:741,0 -BRDA:741,41,0,- -BRDA:741,41,1,- -FN:757,SafeCast.toInt168 -FNDA:0,SafeCast.toInt168 -DA:758,0 -DA:759,0 -BRDA:759,42,0,- -BRDA:759,42,1,- -FN:775,SafeCast.toInt160 -FNDA:0,SafeCast.toInt160 -DA:776,0 -DA:777,0 -BRDA:777,43,0,- -BRDA:777,43,1,- -FN:793,SafeCast.toInt152 -FNDA:0,SafeCast.toInt152 -DA:794,0 -DA:795,0 -BRDA:795,44,0,- -BRDA:795,44,1,- -FN:811,SafeCast.toInt144 -FNDA:0,SafeCast.toInt144 -DA:812,0 -DA:813,0 -BRDA:813,45,0,- -BRDA:813,45,1,- -FN:829,SafeCast.toInt136 -FNDA:0,SafeCast.toInt136 -DA:830,0 -DA:831,0 -BRDA:831,46,0,- -BRDA:831,46,1,- -FN:847,SafeCast.toInt128 -FNDA:0,SafeCast.toInt128 -DA:848,0 -DA:849,0 -BRDA:849,47,0,- -BRDA:849,47,1,- -FN:865,SafeCast.toInt120 -FNDA:0,SafeCast.toInt120 -DA:866,0 -DA:867,0 -BRDA:867,48,0,- -BRDA:867,48,1,- -FN:883,SafeCast.toInt112 -FNDA:0,SafeCast.toInt112 -DA:884,0 -DA:885,0 -BRDA:885,49,0,- -BRDA:885,49,1,- -FN:901,SafeCast.toInt104 -FNDA:0,SafeCast.toInt104 -DA:902,0 -DA:903,0 -BRDA:903,50,0,- -BRDA:903,50,1,- -FN:919,SafeCast.toInt96 -FNDA:0,SafeCast.toInt96 -DA:920,0 -DA:921,0 -BRDA:921,51,0,- -BRDA:921,51,1,- -FN:937,SafeCast.toInt88 -FNDA:0,SafeCast.toInt88 -DA:938,0 -DA:939,0 -BRDA:939,52,0,- -BRDA:939,52,1,- -FN:955,SafeCast.toInt80 -FNDA:0,SafeCast.toInt80 -DA:956,0 -DA:957,0 -BRDA:957,53,0,- -BRDA:957,53,1,- -FN:973,SafeCast.toInt72 -FNDA:0,SafeCast.toInt72 -DA:974,0 -DA:975,0 -BRDA:975,54,0,- -BRDA:975,54,1,- -FN:991,SafeCast.toInt64 -FNDA:0,SafeCast.toInt64 -DA:992,0 -DA:993,0 -BRDA:993,55,0,- -BRDA:993,55,1,- -FN:1009,SafeCast.toInt56 -FNDA:0,SafeCast.toInt56 -DA:1010,0 -DA:1011,0 -BRDA:1011,56,0,- -BRDA:1011,56,1,- -FN:1027,SafeCast.toInt48 -FNDA:0,SafeCast.toInt48 -DA:1028,0 -DA:1029,0 -BRDA:1029,57,0,- -BRDA:1029,57,1,- -FN:1045,SafeCast.toInt40 -FNDA:0,SafeCast.toInt40 -DA:1046,0 -DA:1047,0 -BRDA:1047,58,0,- -BRDA:1047,58,1,- -FN:1063,SafeCast.toInt32 -FNDA:0,SafeCast.toInt32 -DA:1064,0 -DA:1065,0 -BRDA:1065,59,0,- -BRDA:1065,59,1,- -FN:1081,SafeCast.toInt24 -FNDA:0,SafeCast.toInt24 -DA:1082,0 -DA:1083,0 -BRDA:1083,60,0,- -BRDA:1083,60,1,- -FN:1099,SafeCast.toInt16 -FNDA:0,SafeCast.toInt16 -DA:1100,0 -DA:1101,0 -BRDA:1101,61,0,- -BRDA:1101,61,1,- -FN:1117,SafeCast.toInt8 -FNDA:0,SafeCast.toInt8 -DA:1118,0 -DA:1119,0 -BRDA:1119,62,0,- -BRDA:1119,62,1,- -FN:1131,SafeCast.toInt256 -FNDA:0,SafeCast.toInt256 -DA:1133,0 -BRDA:1133,63,0,- -BRDA:1133,63,1,- -DA:1134,0 -FNF:64 -FNH:0 -LF:128 -LH:0 -BRF:128 -BRH:0 -end_of_record -TN: -SF:node_modules/prb-math/contracts/PRBMath.sol -FN:127,PRBMath.exp2 -FNDA:0,PRBMath.exp2 -DA:130,0 -DA:134,0 -BRDA:134,0,0,- -BRDA:134,0,1,- -DA:135,0 -DA:137,0 -BRDA:137,1,0,- -BRDA:137,1,1,- -DA:138,0 -DA:140,0 -BRDA:140,2,0,- -BRDA:140,2,1,- -DA:141,0 -DA:143,0 -BRDA:143,3,0,- -BRDA:143,3,1,- -DA:144,0 -DA:146,0 -BRDA:146,4,0,- -BRDA:146,4,1,- -DA:147,0 -DA:149,0 -BRDA:149,5,0,- -BRDA:149,5,1,- -DA:150,0 +DA:142,0 DA:152,0 -BRDA:152,6,0,- -BRDA:152,6,1,- -DA:153,0 -DA:155,0 -BRDA:155,7,0,- -BRDA:155,7,1,- -DA:156,0 +FN:156,TestBaseWorkflowV3.addressFrom +FNDA:0,TestBaseWorkflowV3.addressFrom +DA:157,0 DA:158,0 -BRDA:158,8,0,- -BRDA:158,8,1,- +BRDA:158,0,0,- +BRDA:158,0,1,- DA:159,0 +DA:160,0 +BRDA:160,1,0,- +BRDA:160,1,1,- DA:161,0 -BRDA:161,9,0,- -BRDA:161,9,1,- DA:162,0 +BRDA:162,2,0,- +BRDA:162,2,1,- +DA:163,0 DA:164,0 -BRDA:164,10,0,- -BRDA:164,10,1,- +BRDA:164,3,0,- +BRDA:164,3,1,- DA:165,0 +DA:166,0 +BRDA:166,4,0,- +BRDA:166,4,1,- DA:167,0 -BRDA:167,11,0,- -BRDA:167,11,1,- -DA:168,0 -DA:170,0 -BRDA:170,12,0,- -BRDA:170,12,1,- +DA:169,0 DA:171,0 -DA:173,0 -BRDA:173,13,0,- -BRDA:173,13,1,- DA:174,0 -DA:176,0 -BRDA:176,14,0,- -BRDA:176,14,1,- -DA:177,0 -DA:179,0 -BRDA:179,15,0,- -BRDA:179,15,1,- -DA:180,0 -DA:182,0 -BRDA:182,16,0,- -BRDA:182,16,1,- -DA:183,0 -DA:185,0 -BRDA:185,17,0,- -BRDA:185,17,1,- -DA:186,0 -DA:188,0 -BRDA:188,18,0,- -BRDA:188,18,1,- -DA:189,0 -DA:191,0 -BRDA:191,19,0,- -BRDA:191,19,1,- -DA:192,0 -DA:194,0 -BRDA:194,20,0,- -BRDA:194,20,1,- -DA:195,0 -DA:197,0 -BRDA:197,21,0,- -BRDA:197,21,1,- -DA:198,0 -DA:200,0 -BRDA:200,22,0,- -BRDA:200,22,1,- -DA:201,0 -DA:203,0 -BRDA:203,23,0,- -BRDA:203,23,1,- -DA:204,0 -DA:206,0 -BRDA:206,24,0,- -BRDA:206,24,1,- -DA:207,0 -DA:209,0 -BRDA:209,25,0,- -BRDA:209,25,1,- -DA:210,0 -DA:212,0 -BRDA:212,26,0,- -BRDA:212,26,1,- -DA:213,0 -DA:215,0 -BRDA:215,27,0,- -BRDA:215,27,1,- -DA:216,0 -DA:218,0 -BRDA:218,28,0,- -BRDA:218,28,1,- -DA:219,0 -DA:221,0 -BRDA:221,29,0,- -BRDA:221,29,1,- -DA:222,0 -DA:224,0 -BRDA:224,30,0,- -BRDA:224,30,1,- -DA:225,0 -DA:227,0 -BRDA:227,31,0,- -BRDA:227,31,1,- -DA:228,0 -DA:230,0 -BRDA:230,32,0,- -BRDA:230,32,1,- -DA:231,0 -DA:233,0 -BRDA:233,33,0,- -BRDA:233,33,1,- -DA:234,0 -DA:236,0 -BRDA:236,34,0,- -BRDA:236,34,1,- -DA:237,0 -DA:239,0 -BRDA:239,35,0,- -BRDA:239,35,1,- -DA:240,0 -DA:242,0 -BRDA:242,36,0,- -BRDA:242,36,1,- -DA:243,0 -DA:245,0 -BRDA:245,37,0,- -BRDA:245,37,1,- -DA:246,0 -DA:248,0 -BRDA:248,38,0,- -BRDA:248,38,1,- -DA:249,0 -DA:251,0 -BRDA:251,39,0,- -BRDA:251,39,1,- -DA:252,0 -DA:254,0 -BRDA:254,40,0,- -BRDA:254,40,1,- -DA:255,0 -DA:257,0 -BRDA:257,41,0,- -BRDA:257,41,1,- -DA:258,0 -DA:260,0 -BRDA:260,42,0,- -BRDA:260,42,1,- -DA:261,0 -DA:263,0 -BRDA:263,43,0,- -BRDA:263,43,1,- -DA:264,0 -DA:266,0 -BRDA:266,44,0,- -BRDA:266,44,1,- -DA:267,0 -DA:269,0 -BRDA:269,45,0,- -BRDA:269,45,1,- -DA:270,0 -DA:272,0 -BRDA:272,46,0,- -BRDA:272,46,1,- -DA:273,0 -DA:275,0 -BRDA:275,47,0,- -BRDA:275,47,1,- -DA:276,0 -DA:278,0 -BRDA:278,48,0,- -BRDA:278,48,1,- -DA:279,0 -DA:281,0 -BRDA:281,49,0,- -BRDA:281,49,1,- -DA:282,0 -DA:284,0 -BRDA:284,50,0,- -BRDA:284,50,1,- -DA:285,0 -DA:287,0 -BRDA:287,51,0,- -BRDA:287,51,1,- -DA:288,0 -DA:290,0 -BRDA:290,52,0,- -BRDA:290,52,1,- -DA:291,0 -DA:293,0 -BRDA:293,53,0,- -BRDA:293,53,1,- -DA:294,0 -DA:296,0 -BRDA:296,54,0,- -BRDA:296,54,1,- -DA:297,0 -DA:299,0 -BRDA:299,55,0,- -BRDA:299,55,1,- -DA:300,0 -DA:302,0 -BRDA:302,56,0,- -BRDA:302,56,1,- -DA:303,0 -DA:305,0 -BRDA:305,57,0,- -BRDA:305,57,1,- -DA:306,0 -DA:308,0 -BRDA:308,58,0,- -BRDA:308,58,1,- -DA:309,0 -DA:311,0 -BRDA:311,59,0,- -BRDA:311,59,1,- -DA:312,0 -DA:314,0 -BRDA:314,60,0,- -BRDA:314,60,1,- -DA:315,0 -DA:317,0 -BRDA:317,61,0,- -BRDA:317,61,1,- -DA:318,0 -DA:320,0 -BRDA:320,62,0,- -BRDA:320,62,1,- -DA:321,0 -DA:323,0 -BRDA:323,63,0,- -BRDA:323,63,1,- -DA:324,0 -DA:335,0 -DA:336,0 -FN:344,PRBMath.mostSignificantBit -FNDA:0,PRBMath.mostSignificantBit -DA:345,0 -BRDA:345,64,0,- -BRDA:345,64,1,- -DA:346,0 -DA:347,0 -DA:349,0 -BRDA:349,65,0,- -BRDA:349,65,1,- -DA:350,0 -DA:351,0 -DA:353,0 -BRDA:353,66,0,- -BRDA:353,66,1,- -DA:354,0 -DA:355,0 -DA:357,0 -BRDA:357,67,0,- -BRDA:357,67,1,- -DA:358,0 -DA:359,0 -DA:361,0 -BRDA:361,68,0,- -BRDA:361,68,1,- -DA:362,0 -DA:363,0 -DA:365,0 -BRDA:365,69,0,- -BRDA:365,69,1,- -DA:366,0 -DA:367,0 -DA:369,0 -BRDA:369,70,0,- -BRDA:369,70,1,- -DA:370,0 -DA:371,0 -DA:373,0 -BRDA:373,71,0,- -BRDA:373,71,1,- -DA:375,0 -FN:394,PRBMath.mulDiv -FNDA:14,PRBMath.mulDiv -DA:402,14 -DA:403,14 -DA:406,14 -DA:407,14 -DA:411,14 -BRDA:411,72,0,- -BRDA:411,72,1,14 -DA:413,14 -DA:415,14 -DA:419,0 -BRDA:419,73,0,- -BRDA:419,73,1,- -DA:420,0 -DA:428,0 -DA:431,0 -DA:434,0 -DA:435,0 -DA:442,0 -DA:445,0 -DA:448,0 -DA:451,0 -DA:455,0 -DA:460,0 -DA:464,0 -DA:465,0 -DA:466,0 -DA:467,0 -DA:468,0 -DA:469,0 -DA:475,0 -DA:476,0 -FN:498,PRBMath.mulDivFixedPoint -FNDA:0,PRBMath.mulDivFixedPoint -DA:499,0 -DA:500,0 -DA:503,0 -DA:504,0 -DA:507,0 -BRDA:507,74,0,- -BRDA:507,74,1,- -DA:508,0 -DA:511,0 -DA:512,0 -DA:514,0 -DA:515,0 -DA:518,0 -BRDA:518,75,0,- -BRDA:518,75,1,- -DA:520,0 -DA:521,0 -DA:526,0 -FN:551,PRBMath.mulDivSigned -FNDA:0,PRBMath.mulDivSigned -DA:556,0 -BRDA:556,76,0,- -BRDA:556,76,1,- -DA:557,0 -DA:561,0 -DA:562,0 -DA:563,0 -DA:565,0 -DA:566,0 -DA:567,0 -DA:571,0 -DA:572,0 -BRDA:572,77,0,- -BRDA:572,77,1,- -DA:573,0 -DA:577,0 -DA:578,0 -DA:579,0 -DA:581,0 -DA:582,0 -DA:583,0 -DA:588,0 -FN:599,PRBMath.sqrt -FNDA:0,PRBMath.sqrt -DA:600,0 -BRDA:600,78,0,- -BRDA:600,78,1,- -DA:601,0 -DA:605,0 -DA:606,0 -DA:607,0 -BRDA:607,79,0,- -BRDA:607,79,1,- -DA:608,0 -DA:609,0 -DA:611,0 -BRDA:611,80,0,- -BRDA:611,80,1,- -DA:612,0 -DA:613,0 -DA:615,0 -BRDA:615,81,0,- -BRDA:615,81,1,- -DA:616,0 -DA:617,0 -DA:619,0 -BRDA:619,82,0,- -BRDA:619,82,1,- -DA:620,0 -DA:621,0 -DA:623,0 -BRDA:623,83,0,- -BRDA:623,83,1,- -DA:624,0 -DA:625,0 -DA:627,0 -BRDA:627,84,0,- -BRDA:627,84,1,- -DA:628,0 -DA:629,0 -DA:631,0 -BRDA:631,85,0,- -BRDA:631,85,1,- -DA:632,0 -DA:637,0 -DA:638,0 -DA:639,0 -DA:640,0 -DA:641,0 -DA:642,0 -DA:643,0 -DA:644,0 -DA:645,0 -FNF:6 -FNH:1 -LF:246 -LH:7 -BRF:172 -BRH:1 +FNF:3 +FNH:0 +LF:43 +LH:0 +BRF:10 +BRH:0 end_of_record diff --git a/package.json b/package.json index c733690..ff11c50 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "license": "MIT", "devDependencies": { "@exhausted-pigeon/uniswap-v3-forge-quoter": "^1.0.1", + "@exhausted-pigeon/uniswap-v3-foundry-pool": "^1.0.3", "prettier": "^2.4.0", "prettier-plugin-solidity": "^1.0.0-beta.19", "solhint": "^3.3.6", @@ -23,9 +24,9 @@ "dependencies": { "@jbx-protocol/juice-contracts-v3": "^5.0.0", "@jbx-protocol/juice-delegate-metadata-lib": "https://github.com/jbx-protocol/juice-delegate-metadata-lib.git", - "@openzeppelin/contracts": "^4.9.2", + "@openzeppelin/contracts": "^4.9.3", "@prb/math": "^4.0.1", - "@uniswap/v3-core": "1.0.2-solc-0.8-simulate", + "@uniswap/v3-core": "github:uniswap/v3-core#0.8", "@uniswap/v3-periphery": "https://github.com/uniswap/v3-periphery.git#b325bb0905d922ae61fcc7df85ee802e8df5e96c", "ds-test": "https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0", "forge-std": "https://github.com/foundry-rs/forge-std.git#06c5a8f760f7d2392697cb092eda80c864e4fc06" diff --git a/yarn.lock b/yarn.lock index b87e865..f709668 100644 --- a/yarn.lock +++ b/yarn.lock @@ -454,6 +454,16 @@ dependencies: "@uniswap/v3-core" "^1.0.2-solc-0.8-simulate" +"@exhausted-pigeon/uniswap-v3-foundry-pool@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@exhausted-pigeon/uniswap-v3-foundry-pool/-/uniswap-v3-foundry-pool-1.0.3.tgz#7a990f896350cf00fd04820b2b1596655e3743b3" + integrity sha512-5X6qTVMhwtVO8ZZsOF9VqM0hJf3eca3qIgyuhIHx7UJ3+AgeuyVwpnkqcmDSKjB0RrVriGFCe4tztDLIyjy0QQ== + dependencies: + "@openzeppelin/contracts" "^4.9.2" + "@prb/math" "^4.0.1" + ds-test "https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0" + forge-std "https://github.com/foundry-rs/forge-std.git#06c5a8f760f7d2392697cb092eda80c864e4fc06" + "@jbx-protocol/juice-contracts-v3@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@jbx-protocol/juice-contracts-v3/-/juice-contracts-v3-5.0.0.tgz#019139c6c6a461e7b7093f58ae1c0ceb10220acd" @@ -475,11 +485,16 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.6.0.tgz#c91cf64bc27f573836dba4122758b4743418c1b3" integrity sha512-8vi4d50NNya/bQqCmaVzvHNmwHvS0OBKb7HNtuNwEE3scXWrP31fKQoGxNMT+KbzmrNZzatE3QK5p2gFONI/hg== -"@openzeppelin/contracts@^4.5.0-rc.0", "@openzeppelin/contracts@^4.9.2": +"@openzeppelin/contracts@^4.5.0-rc.0": version "4.9.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.2.tgz#1cb2d5e4d3360141a17dbc45094a8cad6aac16c1" integrity sha512-mO+y6JaqXjWeMh9glYVzVu8HYPGknAAnWyxTRhGeckOruyXQMNnlcW6w/Dx9ftLeIQk6N+ZJFuVmTwF7lEIFrg== +"@openzeppelin/contracts@^4.9.2", "@openzeppelin/contracts@^4.9.3": + version "4.9.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.3.tgz#00d7a8cf35a475b160b3f0293a6403c511099364" + integrity sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg== + "@paulrberg/contracts@^3.4.0": version "3.7.0" resolved "https://registry.yarnpkg.com/@paulrberg/contracts/-/contracts-3.7.0.tgz#d7fb09d3d75f47f979a666bba19727571e419d98" @@ -694,6 +709,10 @@ resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.2-solc-0.8-simulate.tgz#77fb42f2b502b4fec81844736d039fc059e8688c" integrity sha512-ALAZbsb3wvUrRzeAjrTKjv1fH7UrueJ/+D8uX4yintXHxxzbnnp78Kis2pa4D26cFQ72rwM3DrZpUES9rhsEuQ== +"@uniswap/v3-core@github:uniswap/v3-core#0.8": + version "1.0.1-solc-0.8" + resolved "https://codeload.github.com/uniswap/v3-core/tar.gz/6562c52e8f75f0c10f9deaf44861847585fc8129" + "@uniswap/v3-periphery@https://github.com/uniswap/v3-periphery.git#b325bb0905d922ae61fcc7df85ee802e8df5e96c": version "1.4.2-solc-0.8" resolved "https://github.com/uniswap/v3-periphery.git#b325bb0905d922ae61fcc7df85ee802e8df5e96c" @@ -1564,6 +1583,11 @@ dot-case@^2.1.0: dependencies: no-case "^2.2.0" +"ds-test@git+https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0": + version "1.0.0" + uid e282159d5170298eb2455a6c05280ab5a73a4ef0 + resolved "git+https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0" + "ds-test@https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0": version "1.0.0" resolved "https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0" @@ -1943,6 +1967,11 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== +"forge-std@git+https://github.com/foundry-rs/forge-std.git#06c5a8f760f7d2392697cb092eda80c864e4fc06": + version "1.5.3" + uid "06c5a8f760f7d2392697cb092eda80c864e4fc06" + resolved "git+https://github.com/foundry-rs/forge-std.git#06c5a8f760f7d2392697cb092eda80c864e4fc06" + "forge-std@https://github.com/foundry-rs/forge-std.git#06c5a8f760f7d2392697cb092eda80c864e4fc06": version "1.5.3" resolved "https://github.com/foundry-rs/forge-std.git#06c5a8f760f7d2392697cb092eda80c864e4fc06"