From f350bcf0057c7ac71a35c0953a06f5e07ccf0ed3 Mon Sep 17 00:00:00 2001 From: Mathieu <85969303+Mathieu-Be@users.noreply.github.com> Date: Mon, 23 Jan 2023 12:09:56 +0100 Subject: [PATCH 01/47] Improve tests structure (#72) * update dependencies * fix typo in remappings * add forge as default formatter * cleanup mocks * refactor test helpers and mocks * use makeAddr for alice and bob * fix test market config and label deployed contracts * make flashloan fee constant * remove underscores in function arguments * refactor ERC20TransferTaxMock * temporarily move old tests to a separate folder * bug fix in address declarations * use foundry's formatting --- .gitignore | 3 +- .vscode/settings.json | 3 + foundry.toml | 2 + lib/forge-std | 2 +- lib/openzeppelin-contracts | 2 +- remappings.txt | 2 +- src/LBErrors.sol | 66 +- src/LBFactory.sol | 103 ++- src/LBPair.sol | 244 +++-- src/LBQuoter.sol | 74 +- src/LBRouter.sol | 166 ++-- src/LBToken.sol | 52 +- src/interfaces/IJoePair.sol | 33 +- src/interfaces/IJoeRouter01.sol | 71 +- src/interfaces/ILBFactory.sol | 31 +- src/interfaces/ILBFlashLoanCallback.sol | 10 +- src/interfaces/ILBPair.sol | 69 +- src/interfaces/ILBRouter.sol | 43 +- src/interfaces/ILBToken.sol | 23 +- src/libraries/BitMath.sol | 6 +- src/libraries/Buffer.sol | 8 +- src/libraries/Decoder.sol | 6 +- src/libraries/Encoder.sol | 6 +- src/libraries/FeeHelper.sol | 9 +- src/libraries/JoeLibrary.sol | 26 +- src/libraries/Math128x128.sol | 80 +- src/libraries/Math512Bits.sol | 42 +- src/libraries/Oracle.sol | 27 +- src/libraries/Samples.sol | 31 +- src/libraries/SwapHelper.sol | 10 +- src/libraries/TokenHelper.sol | 24 +- src/libraries/TreeMath.sol | 10 +- test/BinHelper.T.sol | 14 +- test/Faucet.t.sol | 17 +- test/LBRouter.Swaps.t.sol | 835 ------------------ test/TestHelper.sol | 340 ------- test/helpers/TestHelper.sol | 280 ++++++ test/integration/Addresses.sol | 10 + test/mocks/ERC20.sol | 17 +- test/mocks/ERC20MockDecimals.sol | 31 - test/mocks/ERC20MockDecimalsOwnable.sol | 38 - test/mocks/ERC20TransferTax.sol | 19 + test/mocks/ERC20WithTransferTax.sol | 40 - test/mocks/Faucet.sol | 15 +- test/mocks/FlashloanBorrower.sol | 20 +- test/mocks/WAVAX.sol | 13 +- {test => test_old}/LBFactory.MultiPools.t.sol | 37 +- {test => test_old}/LBFactory.t.sol | 168 ++-- {test => test_old}/LBPair.Fees.t.sol | 110 ++- {test => test_old}/LBPair.FlashLoans.t.sol | 20 +- {test => test_old}/LBPair.Liquidity.t.sol | 66 +- {test => test_old}/LBPair.Oracle.t.sol | 87 +- {test => test_old}/LBPair.Swaps.t.sol | 123 ++- {test => test_old}/LBPair.t.sol | 22 +- {test => test_old}/LBQuoter.t.sol | 34 +- .../LBRouter.FeesOnLiquidityAdd.t.sol | 64 +- test_old/LBRouter.Fork.Swaps.t.sol | 268 ++++++ {test => test_old}/LBRouter.Liquidity.t.sol | 283 ++---- test_old/LBRouter.Swaps.t.sol | 474 ++++++++++ {test => test_old}/LBRouter.t.sol | 203 ++--- {test => test_old}/LBToken.t.sol | 32 +- {test => test_old}/LBTokenInternal.t.sol | 20 +- 62 files changed, 2076 insertions(+), 2908 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 test/LBRouter.Swaps.t.sol delete mode 100644 test/TestHelper.sol create mode 100644 test/helpers/TestHelper.sol create mode 100644 test/integration/Addresses.sol delete mode 100644 test/mocks/ERC20MockDecimals.sol delete mode 100644 test/mocks/ERC20MockDecimalsOwnable.sol create mode 100644 test/mocks/ERC20TransferTax.sol delete mode 100644 test/mocks/ERC20WithTransferTax.sol rename {test => test_old}/LBFactory.MultiPools.t.sol (84%) rename {test => test_old}/LBFactory.t.sol (75%) rename {test => test_old}/LBPair.Fees.t.sol (75%) rename {test => test_old}/LBPair.FlashLoans.t.sol (75%) rename {test => test_old}/LBPair.Liquidity.t.sol (82%) rename {test => test_old}/LBPair.Oracle.t.sol (73%) rename {test => test_old}/LBPair.Swaps.t.sol (65%) rename {test => test_old}/LBPair.t.sol (87%) rename {test => test_old}/LBQuoter.t.sol (90%) rename {test => test_old}/LBRouter.FeesOnLiquidityAdd.t.sol (78%) create mode 100644 test_old/LBRouter.Fork.Swaps.t.sol rename {test => test_old}/LBRouter.Liquidity.t.sol (67%) create mode 100644 test_old/LBRouter.Swaps.t.sol rename {test => test_old}/LBRouter.t.sol (74%) rename {test => test_old}/LBToken.t.sol (89%) rename {test => test_old}/LBTokenInternal.t.sol (88%) diff --git a/.gitignore b/.gitignore index 17fe2ff1..6aa8a770 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .env -.vscode cache out -broadcast \ No newline at end of file +broadcast diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..b7e2454b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "solidity.formatter": "forge" +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 4f8acd4d..e8dae739 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,6 +5,8 @@ libs = ['lib'] optimizer = true optimizer_runs = 800 +no_match_contract = "TODO" + [fuzz] runs = 1024 diff --git a/lib/forge-std b/lib/forge-std index 2da2f4a3..066ff16c 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 2da2f4a3470b87c94e5616c4297f3651179950ed +Subproject commit 066ff16c5c03e6f931cd041fd366bc4be1fae82a diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 109778c1..c8b466b7 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 109778c17c7020618ea4e035efb9f0f9b82d43ca +Subproject commit c8b466b7b55aee3967af21cb3c659ec07e93021d diff --git a/remappings.txt b/remappings.txt index 1c410dff..da0a050f 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1,3 @@ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ -openzeppelin/=lib/openzeppelin-contracts/contracts \ No newline at end of file +openzeppelin/=lib/openzeppelin-contracts/contracts/ \ No newline at end of file diff --git a/src/LBErrors.sol b/src/LBErrors.sol index 712b27d1..e0cdfb3f 100644 --- a/src/LBErrors.sol +++ b/src/LBErrors.sol @@ -4,7 +4,9 @@ pragma solidity 0.8.10; import "./interfaces/ILBPair.sol"; -/** LBRouter errors */ +/** + * LBRouter errors + */ error LBRouter__SenderIsNotWAVAX(); error LBRouter__PairNotCreated(address tokenX, address tokenY, uint256 binStep); @@ -28,14 +30,12 @@ error LBRouter__MaxAmountInExceeded(uint256 amountInMax, uint256 amountIn); error LBRouter__InvalidTokenPath(address wrongToken); error LBRouter__InvalidVersion(uint256 version); error LBRouter__WrongAvaxLiquidityParameters( - address tokenX, - address tokenY, - uint256 amountX, - uint256 amountY, - uint256 msgValue + address tokenX, address tokenY, uint256 amountX, uint256 amountY, uint256 msgValue ); -/** LBToken errors */ +/** + * LBToken errors + */ error LBToken__SpenderNotApproved(address owner, address spender); error LBToken__TransferFromOrToAddress0(); @@ -47,7 +47,9 @@ error LBToken__SelfApproval(address owner); error LBToken__TransferExceedsBalance(address from, uint256 id, uint256 amount); error LBToken__TransferToSelf(); -/** LBFactory errors */ +/** + * LBFactory errors + */ error LBFactory__IdenticalAddresses(IERC20 token); error LBFactory__QuoteAssetNotWhitelisted(IERC20 quoteAsset); @@ -73,7 +75,9 @@ error LBFactory__LBPairSafetyCheckFailed(address LBPairImplementation); error LBFactory__SameImplementation(address LBPairImplementation); error LBFactory__ImplementationNotSet(); -/** LBPair errors */ +/** + * LBPair errors + */ error LBPair__InsufficientAmounts(); error LBPair__AddressZero(); @@ -93,30 +97,40 @@ error LBPair__FlashLoanCallbackFailed(); error LBPair__FlashLoanInvalidBalance(); error LBPair__FlashLoanInvalidToken(); -/** BinHelper errors */ +/** + * BinHelper errors + */ error BinHelper__BinStepOverflows(uint256 bp); error BinHelper__IdOverflows(); -/** Math128x128 errors */ +/** + * Math128x128 errors + */ error Math128x128__PowerUnderflow(uint256 x, int256 y); error Math128x128__LogUnderflow(); -/** Math512Bits errors */ +/** + * Math512Bits errors + */ error Math512Bits__MulDivOverflow(uint256 prod1, uint256 denominator); error Math512Bits__ShiftDivOverflow(uint256 prod1, uint256 denominator); error Math512Bits__MulShiftOverflow(uint256 prod1, uint256 offset); error Math512Bits__OffsetOverflows(uint256 offset); -/** Oracle errors */ +/** + * Oracle errors + */ error Oracle__AlreadyInitialized(uint256 _index); error Oracle__LookUpTimestampTooOld(uint256 _minTimestamp, uint256 _lookUpTimestamp); error Oracle__NotInitialized(); -/** PendingOwnable errors */ +/** + * PendingOwnable errors + */ error PendingOwnable__NotOwner(); error PendingOwnable__NotPendingOwner(); @@ -124,12 +138,16 @@ error PendingOwnable__PendingOwnerAlreadySet(); error PendingOwnable__NoPendingOwner(); error PendingOwnable__AddressZero(); -/** ReentrancyGuardUpgradeable errors */ +/** + * ReentrancyGuardUpgradeable errors + */ error ReentrancyGuardUpgradeable__ReentrantCall(); error ReentrancyGuardUpgradeable__AlreadyInitialized(); -/** SafeCast errors */ +/** + * SafeCast errors + */ error SafeCast__Exceeds256Bits(uint256 x); error SafeCast__Exceeds248Bits(uint256 x); @@ -164,23 +182,31 @@ error SafeCast__Exceeds24Bits(uint256 x); error SafeCast__Exceeds16Bits(uint256 x); error SafeCast__Exceeds8Bits(uint256 x); -/** TreeMath errors */ +/** + * TreeMath errors + */ error TreeMath__ErrorDepthSearch(); -/** JoeLibrary errors */ +/** + * JoeLibrary errors + */ error JoeLibrary__IdenticalAddresses(); error JoeLibrary__AddressZero(); error JoeLibrary__InsufficientAmount(); error JoeLibrary__InsufficientLiquidity(); -/** TokenHelper errors */ +/** + * TokenHelper errors + */ error TokenHelper__NonContract(); error TokenHelper__CallFailed(); error TokenHelper__TransferFailed(); -/** LBQuoter errors */ +/** + * LBQuoter errors + */ error LBQuoter_InvalidLength(); diff --git a/src/LBFactory.sol b/src/LBFactory.sol index 864f8b8a..7a74f750 100644 --- a/src/LBFactory.sol +++ b/src/LBFactory.sol @@ -102,11 +102,12 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @param _tokenB The address of the second token of the pair /// @param _binStep The bin step of the LBPair /// @return The LBPairInformation - function getLBPairInformation( - IERC20 _tokenA, - IERC20 _tokenB, - uint256 _binStep - ) external view override returns (LBPairInformation memory) { + function getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep) + external + view + override + returns (LBPairInformation memory) + { return _getLBPairInformation(_tokenA, _tokenB, _binStep); } @@ -216,12 +217,14 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @dev Needs to be called by the owner /// @param _LBPairImplementation The address of the implementation function setLBPairImplementation(address _LBPairImplementation) external override onlyOwner { - if (ILBPair(_LBPairImplementation).factory() != this) + if (ILBPair(_LBPairImplementation).factory() != this) { revert LBFactory__LBPairSafetyCheckFailed(_LBPairImplementation); + } address _oldLBPairImplementation = LBPairImplementation; - if (_oldLBPairImplementation == _LBPairImplementation) + if (_oldLBPairImplementation == _LBPairImplementation) { revert LBFactory__SameImplementation(_LBPairImplementation); + } LBPairImplementation = _LBPairImplementation; @@ -234,12 +237,11 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @param _activeId The active id of the pair /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) /// @return _LBPair The address of the newly created LBPair - function createLBPair( - IERC20 _tokenX, - IERC20 _tokenY, - uint24 _activeId, - uint16 _binStep - ) external override returns (ILBPair _LBPair) { + function createLBPair(IERC20 _tokenX, IERC20 _tokenY, uint24 _activeId, uint16 _binStep) + external + override + returns (ILBPair _LBPair) + { address _owner = owner(); if (!creationUnlocked && msg.sender != _owner) revert LBFactory__FunctionIsLockedForUsers(msg.sender); @@ -258,8 +260,9 @@ contract LBFactory is PendingOwnable, ILBFactory { (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); // single check is sufficient if (address(_tokenA) == address(0)) revert LBFactory__AddressZero(); - if (address(_LBPairsInfo[_tokenA][_tokenB][_binStep].LBPair) != address(0)) + if (address(_LBPairsInfo[_tokenA][_tokenB][_binStep].LBPair) != address(0)) { revert LBFactory__LBPairAlreadyExists(_tokenX, _tokenY, _binStep); + } bytes32 _preset = _presets[_binStep]; if (_preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); @@ -307,7 +310,7 @@ contract LBFactory is PendingOwnable, ILBFactory { _preset.decode(type(uint24).max, 80), _preset.decode(type(uint16).max, 104), _preset.decode(type(uint24).max, 120) - ); + ); } /// @notice Function to set whether the pair is ignored or not for routing, it will make the pair unusable by the router @@ -315,12 +318,11 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @param _tokenY The address of the second token of the pair /// @param _binStep The bin step in basis point of the pair /// @param _ignored Whether to ignore (true) or not (false) the pair for routing - function setLBPairIgnored( - IERC20 _tokenX, - IERC20 _tokenY, - uint256 _binStep, - bool _ignored - ) external override onlyOwner { + function setLBPairIgnored(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep, bool _ignored) + external + override + onlyOwner + { (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); LBPairInformation memory _LBPairInformation = _LBPairsInfo[_tokenA][_tokenB][_binStep]; @@ -366,9 +368,8 @@ contract LBFactory is PendingOwnable, ILBFactory { ); // The last 16 bits are reserved for sampleLifetime - bytes32 _preset = bytes32( - (uint256(_packedFeeParameters) & type(uint144).max) | (uint256(_sampleLifetime) << 240) - ); + bytes32 _preset = + bytes32((uint256(_packedFeeParameters) & type(uint144).max) | (uint256(_sampleLifetime) << 240)); _presets[_binStep] = _preset; @@ -394,7 +395,7 @@ contract LBFactory is PendingOwnable, ILBFactory { _protocolShare, _maxVolatilityAccumulated, _sampleLifetime - ); + ); } /// @notice Remove the preset linked to a binStep @@ -466,7 +467,7 @@ contract LBFactory is PendingOwnable, ILBFactory { _variableFeeControl, _protocolShare, _maxVolatilityAccumulated - ); + ); } /// @notice Function to set the recipient of the fees. This address needs to be able to receive ERC20s @@ -498,8 +499,9 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @notice Function to add an asset to the whitelist of quote assets /// @param _quoteAsset The quote asset (e.g: AVAX, USDC...) function addQuoteAsset(IERC20 _quoteAsset) external override onlyOwner { - if (!_quoteAssetWhitelist.add(address(_quoteAsset))) + if (!_quoteAssetWhitelist.add(address(_quoteAsset))) { revert LBFactory__QuoteAssetAlreadyWhitelisted(_quoteAsset); + } emit QuoteAssetAdded(_quoteAsset); } @@ -547,16 +549,19 @@ contract LBFactory is PendingOwnable, ILBFactory { uint16 _protocolShare, uint24 _maxVolatilityAccumulated ) private pure returns (bytes32) { - if (_binStep < MIN_BIN_STEP || _binStep > MAX_BIN_STEP) + if (_binStep < MIN_BIN_STEP || _binStep > MAX_BIN_STEP) { revert LBFactory__BinStepRequirementsBreached(MIN_BIN_STEP, _binStep, MAX_BIN_STEP); + } if (_filterPeriod >= _decayPeriod) revert LBFactory__DecreasingPeriods(_filterPeriod, _decayPeriod); - if (_reductionFactor > Constants.BASIS_POINT_MAX) + if (_reductionFactor > Constants.BASIS_POINT_MAX) { revert LBFactory__ReductionFactorOverflows(_reductionFactor, Constants.BASIS_POINT_MAX); + } - if (_protocolShare > MAX_PROTOCOL_SHARE) + if (_protocolShare > MAX_PROTOCOL_SHARE) { revert LBFactory__ProtocolShareOverflows(_protocolShare, MAX_PROTOCOL_SHARE); + } { uint256 _baseFee = (uint256(_baseFactor) * _binStep) * 1e10; @@ -567,25 +572,25 @@ contract LBFactory is PendingOwnable, ILBFactory { uint256 _prod = uint256(_maxVolatilityAccumulated) * _binStep; uint256 _maxVariableFee = (_prod * _prod * _variableFeeControl) / 100; - if (_baseFee + _maxVariableFee > MAX_FEE) + if (_baseFee + _maxVariableFee > MAX_FEE) { revert LBFactory__FeesAboveMax(_baseFee + _maxVariableFee, MAX_FEE); + } } /// @dev It's very important that the sum of the sizes of those values is exactly 256 bits /// here, (112 + 24) + 16 + 24 + 16 + 16 + 16 + 16 + 16 = 256 - return - bytes32( - abi.encodePacked( - uint136(_maxVolatilityAccumulated), // The first 112 bits are reserved for the dynamic parameters - _protocolShare, - _variableFeeControl, - _reductionFactor, - _decayPeriod, - _filterPeriod, - _baseFactor, - _binStep - ) - ); + return bytes32( + abi.encodePacked( + uint136(_maxVolatilityAccumulated), // The first 112 bits are reserved for the dynamic parameters + _protocolShare, + _variableFeeControl, + _reductionFactor, + _decayPeriod, + _filterPeriod, + _baseFactor, + _binStep + ) + ); } /// @notice Returns the LBPairInformation if it exists, @@ -594,11 +599,11 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @param _tokenB The address of the second token of the pair /// @param _binStep The bin step of the LBPair /// @return The LBPairInformation - function _getLBPairInformation( - IERC20 _tokenA, - IERC20 _tokenB, - uint256 _binStep - ) private view returns (LBPairInformation memory) { + function _getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep) + private + view + returns (LBPairInformation memory) + { (_tokenA, _tokenB) = _sortTokens(_tokenA, _tokenB); return _LBPairsInfo[_tokenA][_tokenB][_binStep]; } diff --git a/src/LBPair.sol b/src/LBPair.sol index a5a815f2..fb5b5cbb 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -2,7 +2,9 @@ pragma solidity 0.8.10; -/** Imports **/ +/** + * Imports * + */ import "./LBErrors.sol"; import "./LBToken.sol"; @@ -24,7 +26,9 @@ import "./interfaces/ILBPair.sol"; /// @author Trader Joe /// @notice This contract is the implementation of Liquidity Book Pair that also acts as the receipt token for liquidity positions contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { - /** Libraries **/ + /** + * Libraries * + */ using Math512Bits for uint256; using TreeMath for mapping(uint256 => uint256)[3]; @@ -37,7 +41,9 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { using FeeDistributionHelper for FeeHelper.FeesDistribution; using Oracle for bytes32[65_535]; - /** Modifiers **/ + /** + * Modifiers * + */ /// @notice Checks if the caller is the factory modifier onlyFactory() { @@ -45,12 +51,16 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { _; } - /** Public immutable variables **/ + /** + * Public immutable variables * + */ /// @notice The factory contract that created this pair ILBFactory public immutable override factory; - /** Public variables **/ + /** + * Public variables * + */ /// @notice The token that is used as the base currency for the pair IERC20 public override tokenX; @@ -58,7 +68,9 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { /// @notice The token that is used as the quote currency for the pair IERC20 public override tokenY; - /** Private variables **/ + /** + * Private variables * + */ /// @dev The pair information that is used to track reserves, active ids, /// fees and oracle parameters @@ -86,7 +98,9 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { /// @dev The oracle samples that are used to calculate the time weighted average data bytes32[65_535] private _oracle; - /** OffSets */ + /** + * OffSets + */ uint256 private constant _OFFSET_PAIR_RESERVE_X = 24; uint256 private constant _OFFSET_PROTOCOL_FEE = 128; @@ -98,7 +112,9 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { uint256 private constant _OFFSET_ORACLE_LAST_TIMESTAMP = 184; uint256 private constant _OFFSET_ORACLE_ID = 224; - /** Constructor **/ + /** + * Constructor * + */ /// @notice Set the factory address /// @param _factory The address of the factory @@ -138,22 +154,15 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { _increaseOracle(2); } - /** External View Functions **/ + /** + * External View Functions * + */ /// @notice View function to get the reserves and active id /// @return reserveX The reserve of asset X /// @return reserveY The reserve of asset Y /// @return activeId The active id of the pair - function getReservesAndId() - external - view - override - returns ( - uint256 reserveX, - uint256 reserveY, - uint256 activeId - ) - { + function getReservesAndId() external view override returns (uint256 reserveX, uint256 reserveY, uint256 activeId) { return _getReservesAndId(); } @@ -166,12 +175,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { external view override - returns ( - uint128 feesXTotal, - uint128 feesYTotal, - uint128 feesXProtocol, - uint128 feesYProtocol - ) + returns (uint128 feesXTotal, uint128 feesYTotal, uint128 feesXProtocol, uint128 feesYProtocol) { return _getGlobalFees(); } @@ -213,22 +217,15 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { external view override - returns ( - uint256 cumulativeId, - uint256 cumulativeVolatilityAccumulated, - uint256 cumulativeBinCrossed - ) + returns (uint256 cumulativeId, uint256 cumulativeVolatilityAccumulated, uint256 cumulativeBinCrossed) { uint256 _lookUpTimestamp = block.timestamp - _timeDelta; - (, , uint256 _oracleActiveSize, , uint256 _oracleId) = _getOracleParameters(); + (,, uint256 _oracleActiveSize,, uint256 _oracleId) = _getOracleParameters(); uint256 timestamp; - (timestamp, cumulativeId, cumulativeVolatilityAccumulated, cumulativeBinCrossed) = _oracle.getSampleAt( - _oracleActiveSize, - _oracleId, - _lookUpTimestamp - ); + (timestamp, cumulativeId, cumulativeVolatilityAccumulated, cumulativeBinCrossed) = + _oracle.getSampleAt(_oracleActiveSize, _oracleId, _lookUpTimestamp); if (timestamp < _lookUpTimestamp) { FeeHelper.FeeParameters memory _fp = _feeParameters; @@ -318,7 +315,9 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { return super.supportsInterface(_interfaceId) || _interfaceId == type(ILBPair).interfaceId; } - /** External Functions **/ + /** + * External Functions * + */ /// @notice Swap tokens iterating over the bins until the entire amount is swapped. /// Will swap token X for token Y if `_swapForY` is true, and token Y for token X if `_swapForY` is false. @@ -358,8 +357,8 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { while (true) { Bin memory _bin = _bins[_pair.activeId]; if ((!_swapForY && _bin.reserveX != 0) || (_swapForY && _bin.reserveY != 0)) { - (uint256 _amountInToBin, uint256 _amountOutOfBin, FeeHelper.FeesDistribution memory _fees) = _bin - .getAmounts(_fp, _pair.activeId, _swapForY, _amountIn); + (uint256 _amountInToBin, uint256 _amountOutOfBin, FeeHelper.FeesDistribution memory _fees) = + _bin.getAmounts(_fp, _pair.activeId, _swapForY, _amountIn); _bin.updateFees(_swapForY ? _pair.feesX : _pair.feesY, _fees, _swapForY, totalSupply(_pair.activeId)); @@ -435,12 +434,11 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { /// @param _token The address of the token to flashloan /// @param _amount The amount of token to flashloan /// @param _data The call data that will be forwarded to the `_receiver` contract during the callback - function flashLoan( - ILBFlashLoanCallback _receiver, - IERC20 _token, - uint256 _amount, - bytes calldata _data - ) external override nonReentrant { + function flashLoan(ILBFlashLoanCallback _receiver, IERC20 _token, uint256 _amount, bytes calldata _data) + external + override + nonReentrant + { IERC20 _tokenX = tokenX; if ((_token != _tokenX && _token != tokenY)) revert LBPair__FlashLoanInvalidToken(); @@ -468,12 +466,12 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { if (_totalFee > 0) { if (_token == _tokenX) { - (uint128 _feesXTotal, , uint128 _feesXProtocol, ) = _getGlobalFees(); + (uint128 _feesXTotal,, uint128 _feesXProtocol,) = _getGlobalFees(); _setFees(_pairInformation.feesX, _feesXTotal + _fees.total, _feesXProtocol + _fees.protocol); _bins[_activeId].accTokenXPerShare += _fees.getTokenPerShare(_totalSupply); } else { - (, uint128 _feesYTotal, , uint128 _feesYProtocol) = _getGlobalFees(); + (, uint128 _feesYTotal,, uint128 _feesYProtocol) = _getGlobalFees(); _setFees(_pairInformation.feesY, _feesYTotal + _fees.total, _feesYProtocol + _fees.protocol); _bins[_activeId].accTokenYPerShare += _fees.getTokenPerShare(_totalSupply); @@ -503,18 +501,10 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { uint256[] calldata _distributionX, uint256[] calldata _distributionY, address _to - ) - external - override - nonReentrant - returns ( - uint256, - uint256, - uint256[] memory liquidityMinted - ) - { - if (_ids.length == 0 || _ids.length != _distributionX.length || _ids.length != _distributionY.length) + ) external override nonReentrant returns (uint256, uint256, uint256[] memory liquidityMinted) { + if (_ids.length == 0 || _ids.length != _distributionX.length || _ids.length != _distributionY.length) { revert LBPair__WrongLengths(); + } PairInformation memory _pair = _pairInformation; @@ -528,7 +518,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { liquidityMinted = new uint256[](_ids.length); // Iterate over the ids to calculate the amount of LB tokens to mint for each bin - for (uint256 i; i < _ids.length; ) { + for (uint256 i; i < _ids.length;) { _mintInfo.id = _ids[i].safe24(); Bin memory _bin = _bins[_mintInfo.id]; @@ -556,8 +546,8 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { uint256 _receivedY; { - uint256 _userL = _price.mulShiftRoundDown(_mintInfo.amountX, Constants.SCALE_OFFSET) + - _mintInfo.amountY; + uint256 _userL = + _price.mulShiftRoundDown(_mintInfo.amountX, Constants.SCALE_OFFSET) + _mintInfo.amountY; uint256 _supply = _totalSupply + _userL; @@ -575,9 +565,8 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { // tokens sent by the user. If it is, we add a composition fee of the difference between the two amounts. if (_mintInfo.amountX > _receivedX) { unchecked { - _fees = _fp.getFeeAmountDistribution( - _fp.getFeeAmountForC(_mintInfo.amountX - _receivedX) - ); + _fees = + _fp.getFeeAmountDistribution(_fp.getFeeAmountForC(_mintInfo.amountX - _receivedX)); } _mintInfo.amountX -= _fees.total; @@ -587,9 +576,8 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { } if (_mintInfo.amountY > _receivedY) { unchecked { - _fees = _fp.getFeeAmountDistribution( - _fp.getFeeAmountForC(_mintInfo.amountY - _receivedY) - ); + _fees = + _fp.getFeeAmountDistribution(_fp.getFeeAmountForC(_mintInfo.amountY - _receivedY)); } _mintInfo.amountY -= _fees.total; @@ -598,21 +586,21 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { _bin.updateFees(_pair.feesY, _fees, false, _totalSupply); } - if (_mintInfo.activeFeeX > 0 || _mintInfo.activeFeeY > 0) + if (_mintInfo.activeFeeX > 0 || _mintInfo.activeFeeY > 0) { emit CompositionFee( - msg.sender, - _to, - _mintInfo.id, - _mintInfo.activeFeeX, - _mintInfo.activeFeeY - ); + msg.sender, _to, _mintInfo.id, _mintInfo.activeFeeX, _mintInfo.activeFeeY + ); + } } - } else if (_mintInfo.amountY != 0) revert LBPair__CompositionFactorFlawed(_mintInfo.id); - } else if (_mintInfo.amountX != 0) revert LBPair__CompositionFactorFlawed(_mintInfo.id); + } else if (_mintInfo.amountY != 0) { + revert LBPair__CompositionFactorFlawed(_mintInfo.id); + } + } else if (_mintInfo.amountX != 0) { + revert LBPair__CompositionFactorFlawed(_mintInfo.id); + } // Calculate the amount of LB tokens to mint for this bin - uint256 _liquidity = _price.mulShiftRoundDown(_mintInfo.amountX, Constants.SCALE_OFFSET) + - _mintInfo.amountY; + uint256 _liquidity = _price.mulShiftRoundDown(_mintInfo.amountX, Constants.SCALE_OFFSET) + _mintInfo.amountY; if (_liquidity == 0) revert LBPair__InsufficientLiquidityMinted(_mintInfo.id); @@ -644,8 +632,9 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { } // Assert that the distributions don't exceed 100% - if (_mintInfo.totalDistributionX > Constants.PRECISION || _mintInfo.totalDistributionY > Constants.PRECISION) + if (_mintInfo.totalDistributionX > Constants.PRECISION || _mintInfo.totalDistributionY > Constants.PRECISION) { revert LBPair__DistributionsOverflow(); + } _pairInformation = _pair; @@ -676,11 +665,12 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { /// @param _to The address that will receive the tokens /// @return amountX The amount of token X sent to `_to` /// @return amountY The amount of token Y sent to `_to` - function burn( - uint256[] calldata _ids, - uint256[] calldata _amounts, - address _to - ) external override nonReentrant returns (uint256 amountX, uint256 amountY) { + function burn(uint256[] calldata _ids, uint256[] calldata _amounts, address _to) + external + override + nonReentrant + returns (uint256 amountX, uint256 amountY) + { if (_ids.length == 0 || _ids.length != _amounts.length) revert LBPair__WrongLengths(); (uint256 _pairReserveX, uint256 _pairReserveY, uint256 _activeId) = _getReservesAndId(); @@ -768,7 +758,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { amountY = _unclaimedData.decode(type(uint128).max, 128); // Iterate over the ids to collect the fees - for (uint256 i; i < _ids.length; ) { + for (uint256 i; i < _ids.length;) { uint256 _id = _ids[i]; uint256 _balance = balanceOf(_account, _id); @@ -851,7 +841,9 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { _feeParameters.indexRef = _pairInformation.activeId; } - /** Internal Functions **/ + /** + * Internal Functions * + */ /// @notice Cache the accrued fees for a user before any transfer, mint or burn of LB tokens. /// The tokens are not transferred to reduce the gas cost and to avoid reentrancy. @@ -859,12 +851,10 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { /// @param _to The address of the receiver of the tokens /// @param _id The id of the bin /// @param _amount The amount of LB tokens transferred - function _beforeTokenTransfer( - address _from, - address _to, - uint256 _id, - uint256 _amount - ) internal override(LBToken) { + function _beforeTokenTransfer(address _from, address _to, uint256 _id, uint256 _amount) + internal + override(LBToken) + { super._beforeTokenTransfer(_from, _to, _id, _amount); if (_from != _to) { @@ -883,7 +873,9 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { } } - /** Private Functions **/ + /** + * Private Functions * + */ /// @notice View function to get the pending fees of an account on a given bin /// @param _bin The bin data where the user is collecting fees @@ -892,12 +884,11 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { /// @param _balance The previous balance of the user /// @return amountX The amount of token X not collected yet by `_account` /// @return amountY The amount of token Y not collected yet by `_account` - function _getPendingFees( - Bin memory _bin, - address _account, - uint256 _id, - uint256 _balance - ) private view returns (uint128 amountX, uint128 amountY) { + function _getPendingFees(Bin memory _bin, address _account, uint256 _id, uint256 _balance) + private + view + returns (uint128 amountX, uint128 amountY) + { Debts memory _debts = _accruedDebts[_account][_id]; amountX = (_bin.accTokenXPerShare.mulShiftRoundDown(_balance, Constants.SCALE_OFFSET) - _debts.debtX).safe128(); @@ -909,12 +900,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { /// @param _account The address of the user /// @param _id The id where the user has collected fees /// @param _balance The new balance of the user - function _updateUserDebts( - Bin memory _bin, - address _account, - uint256 _id, - uint256 _balance - ) private { + function _updateUserDebts(Bin memory _bin, address _account, uint256 _id, uint256 _balance) private { uint256 _debtX = _bin.accTokenXPerShare.mulShiftRoundDown(_balance, Constants.SCALE_OFFSET); uint256 _debtY = _bin.accTokenYPerShare.mulShiftRoundDown(_balance, Constants.SCALE_OFFSET); @@ -928,13 +914,9 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { /// @param _id The id where the user is receiving LB tokens /// @param _previousBalance The previous balance of the user /// @param _newBalance The new balance of the user - function _cacheFees( - Bin memory _bin, - address _user, - uint256 _id, - uint256 _previousBalance, - uint256 _newBalance - ) private { + function _cacheFees(Bin memory _bin, address _user, uint256 _id, uint256 _previousBalance, uint256 _newBalance) + private + { bytes32 _unclaimedData = _unclaimedFees[_user]; uint128 amountX = uint128(_unclaimedData.decode(type(uint128).max, 0)); @@ -977,7 +959,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { _pairInformation.oracleSize = _newSize; // Iterate over the uninitialized oracle samples and initialize them - for (uint256 _id = _oracleSize; _id < _newSize; ) { + for (uint256 _id = _oracleSize; _id < _newSize;) { _oracle.initialize(_id); unchecked { @@ -1020,15 +1002,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { /// @return reserveX The reserve of token X /// @return reserveY The reserve of token Y /// @return activeId The active id of the pair - function _getReservesAndId() - private - view - returns ( - uint256 reserveX, - uint256 reserveY, - uint256 activeId - ) - { + function _getReservesAndId() private view returns (uint256 reserveX, uint256 reserveY, uint256 activeId) { uint256 _mask24 = type(uint24).max; uint256 _mask136 = type(uint136).max; assembly { @@ -1070,12 +1044,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { function _getGlobalFees() private view - returns ( - uint128 feesXTotal, - uint128 feesYTotal, - uint128 feesXProtocol, - uint128 feesYProtocol - ) + returns (uint128 feesXTotal, uint128 feesYTotal, uint128 feesXProtocol, uint128 feesYProtocol) { bytes32 _slotX; bytes32 _slotY; @@ -1106,11 +1075,9 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { /// @param _pairFees The storage slot of the fees /// @param _totalFees The new total fees /// @param _protocolFees The new protocol fees - function _setFees( - FeeHelper.FeesDistribution storage _pairFees, - uint128 _totalFees, - uint128 _protocolFees - ) private { + function _setFees(FeeHelper.FeesDistribution storage _pairFees, uint128 _totalFees, uint128 _protocolFees) + private + { assembly { sstore(_pairFees.slot, or(shl(_OFFSET_PROTOCOL_FEE, _protocolFees), _totalFees)) } @@ -1136,15 +1103,6 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { uint256 _volatilityAccumulated, uint256 _fees ) private { - emit Swap( - msg.sender, - _to, - _activeId, - _swapForY, - _amountInToBin, - _amountOutOfBin, - _volatilityAccumulated, - _fees - ); + emit Swap(msg.sender, _to, _activeId, _swapForY, _amountInToBin, _amountOutOfBin, _volatilityAccumulated, _fees); } } diff --git a/src/LBQuoter.sol b/src/LBQuoter.sol index 5c5985c9..387b1a0a 100644 --- a/src/LBQuoter.sol +++ b/src/LBQuoter.sol @@ -37,11 +37,7 @@ contract LBQuoter { /// @param _routerV2 Dex V2 router address /// @param _factoryV1 Dex V1 factory address /// @param _factoryV2 Dex V2 factory address - constructor( - address _routerV2, - address _factoryV1, - address _factoryV2 - ) { + constructor(address _routerV2, address _factoryV1, address _factoryV2) { routerV2 = _routerV2; factoryV1 = _factoryV1; factoryV2 = _factoryV2; @@ -88,18 +84,15 @@ contract LBQuoter { } // Fetch swaps for V2 - ILBFactory.LBPairInformation[] memory LBPairsAvailable = ILBFactory(factoryV2).getAllLBPairs( - IERC20(_route[i]), - IERC20(_route[i + 1]) - ); + ILBFactory.LBPairInformation[] memory LBPairsAvailable = + ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i]), IERC20(_route[i + 1])); if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { for (uint256 j; j < LBPairsAvailable.length; j++) { if (!LBPairsAvailable[j].ignoredForRouting) { bool swapForY = address(LBPairsAvailable[j].LBPair.tokenY()) == _route[i + 1]; - try - ILBRouter(routerV2).getSwapOut(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) + try ILBRouter(routerV2).getSwapOut(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) returns (uint256 swapAmountOut, uint256 fees) { if (swapAmountOut > quote.amounts[i + 1]) { quote.amounts[i + 1] = swapAmountOut; @@ -107,12 +100,9 @@ contract LBQuoter { quote.binSteps[i] = LBPairsAvailable[j].binStep; // Getting current price - (, , uint256 activeId) = LBPairsAvailable[j].LBPair.getReservesAndId(); + (,, uint256 activeId) = LBPairsAvailable[j].LBPair.getReservesAndId(); quote.virtualAmountsWithoutSlippage[i + 1] = _getV2Quote( - quote.virtualAmountsWithoutSlippage[i] - fees, - activeId, - quote.binSteps[i], - swapForY + quote.virtualAmountsWithoutSlippage[i] - fees, activeId, quote.binSteps[i], swapForY ); quote.fees[i] = (fees * 1e18) / quote.amounts[i]; // fee percentage in amountIn @@ -164,35 +154,26 @@ contract LBQuoter { } // Fetch swaps for V2 - ILBFactory.LBPairInformation[] memory LBPairsAvailable = ILBFactory(factoryV2).getAllLBPairs( - IERC20(_route[i - 1]), - IERC20(_route[i]) - ); + ILBFactory.LBPairInformation[] memory LBPairsAvailable = + ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i - 1]), IERC20(_route[i])); if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { for (uint256 j; j < LBPairsAvailable.length; j++) { if (!LBPairsAvailable[j].ignoredForRouting) { bool swapForY = address(LBPairsAvailable[j].LBPair.tokenY()) == _route[i]; - try - ILBRouter(routerV2).getSwapIn(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) + try ILBRouter(routerV2).getSwapIn(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) returns (uint256 swapAmountIn, uint256 fees) { - if ( - swapAmountIn != 0 && (swapAmountIn < quote.amounts[i - 1] || quote.amounts[i - 1] == 0) - ) { + if (swapAmountIn != 0 && (swapAmountIn < quote.amounts[i - 1] || quote.amounts[i - 1] == 0)) + { quote.amounts[i - 1] = swapAmountIn; quote.pairs[i - 1] = address(LBPairsAvailable[j].LBPair); quote.binSteps[i - 1] = LBPairsAvailable[j].binStep; // Getting current price - (, , uint256 activeId) = LBPairsAvailable[j].LBPair.getReservesAndId(); - quote.virtualAmountsWithoutSlippage[i - 1] = - _getV2Quote( - quote.virtualAmountsWithoutSlippage[i], - activeId, - quote.binSteps[i - 1], - !swapForY - ) + - fees; + (,, uint256 activeId) = LBPairsAvailable[j].LBPair.getReservesAndId(); + quote.virtualAmountsWithoutSlippage[i - 1] = _getV2Quote( + quote.virtualAmountsWithoutSlippage[i], activeId, quote.binSteps[i - 1], !swapForY + ) + fees; quote.fees[i - 1] = (fees * 1e18) / quote.amounts[i - 1]; // fee percentage in amountIn } @@ -210,13 +191,13 @@ contract LBQuoter { /// @param _tokenB Address of token B /// @return reserveA Reserve of token A in the pair /// @return reserveB Reserve of token B in the pair - function _getReserves( - address _pair, - address _tokenA, - address _tokenB - ) internal view returns (uint256 reserveA, uint256 reserveB) { - (address token0, ) = JoeLibrary.sortTokens(_tokenA, _tokenB); - (uint256 reserve0, uint256 reserve1, ) = IJoePair(_pair).getReserves(); + function _getReserves(address _pair, address _tokenA, address _tokenB) + internal + view + returns (uint256 reserveA, uint256 reserveB) + { + (address token0,) = JoeLibrary.sortTokens(_tokenA, _tokenB); + (uint256 reserve0, uint256 reserve1,) = IJoePair(_pair).getReserves(); (reserveA, reserveB) = _tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); } @@ -226,12 +207,11 @@ contract LBQuoter { /// @param _binStep Bin step of the considered pair /// @param _swapForY Boolean describing if we are swapping from X to Y or the opposite /// @return quote Amount Out if _amount was swapped with no slippage and no fees - function _getV2Quote( - uint256 _amount, - uint256 _activeId, - uint256 _binStep, - bool _swapForY - ) internal pure returns (uint256 quote) { + function _getV2Quote(uint256 _amount, uint256 _activeId, uint256 _binStep, bool _swapForY) + internal + pure + returns (uint256 quote) + { if (_swapForY) { quote = BinHelper.getPriceFromId(_activeId, _binStep).mulShiftRoundDown(_amount, Constants.SCALE_OFFSET); } else { diff --git a/src/LBRouter.sol b/src/LBRouter.sol index 1e9fc52d..d784e147 100644 --- a/src/LBRouter.sol +++ b/src/LBRouter.sol @@ -42,8 +42,9 @@ contract LBRouter is ILBRouter { } modifier verifyInputs(uint256[] memory _pairBinSteps, IERC20[] memory _tokenPath) { - if (_pairBinSteps.length == 0 || _pairBinSteps.length + 1 != _tokenPath.length) + if (_pairBinSteps.length == 0 || _pairBinSteps.length + 1 != _tokenPath.length) { revert LBRouter__LengthsMismatch(); + } _; } @@ -51,11 +52,7 @@ contract LBRouter is ILBRouter { /// @param _factory LBFactory address /// @param _oldFactory Address of old factory (Joe V1) /// @param _wavax Address of WAVAX - constructor( - ILBFactory _factory, - IJoeFactory _oldFactory, - IWAVAX _wavax - ) { + constructor(ILBFactory _factory, IJoeFactory _oldFactory, IWAVAX _wavax) { factory = _factory; oldFactory = _oldFactory; wavax = _wavax; @@ -89,15 +86,17 @@ contract LBRouter is ILBRouter { /// @param _swapForY Whether you swap X for Y (true), or Y for X (false) /// @return amountIn The amount of token to send in order to receive _amountOut token /// @return feesIn The amount of fees paid in token sent - function getSwapIn( - ILBPair _LBPair, - uint256 _amountOut, - bool _swapForY - ) public view override returns (uint256 amountIn, uint256 feesIn) { + function getSwapIn(ILBPair _LBPair, uint256 _amountOut, bool _swapForY) + public + view + override + returns (uint256 amountIn, uint256 feesIn) + { (uint256 _pairReserveX, uint256 _pairReserveY, uint256 _activeId) = _LBPair.getReservesAndId(); - if (_amountOut == 0 || (_swapForY ? _amountOut > _pairReserveY : _amountOut > _pairReserveX)) - revert LBRouter__WrongAmounts(_amountOut, _swapForY ? _pairReserveY : _pairReserveX); // If this is wrong, then we're sure the amounts sent are wrong + if (_amountOut == 0 || (_swapForY ? _amountOut > _pairReserveY : _amountOut > _pairReserveX)) { + revert LBRouter__WrongAmounts(_amountOut, _swapForY ? _pairReserveY : _pairReserveX); + } // If this is wrong, then we're sure the amounts sent are wrong FeeHelper.FeeParameters memory _fp = _LBPair.feeParameters(); _fp.updateVariableFeeParameters(_activeId); @@ -146,12 +145,13 @@ contract LBRouter is ILBRouter { /// @param _swapForY Whether you swap X for Y (true), or Y for X (false) /// @return amountOut The amount of token received if _amountIn tokenX are sent /// @return feesIn The amount of fees paid in token sent - function getSwapOut( - ILBPair _LBPair, - uint256 _amountIn, - bool _swapForY - ) external view override returns (uint256 amountOut, uint256 feesIn) { - (, , uint256 _activeId) = _LBPair.getReservesAndId(); + function getSwapOut(ILBPair _LBPair, uint256 _amountIn, bool _swapForY) + external + view + override + returns (uint256 amountOut, uint256 feesIn) + { + (,, uint256 _activeId) = _LBPair.getReservesAndId(); FeeHelper.FeeParameters memory _fp = _LBPair.feeParameters(); _fp.updateVariableFeeParameters(_activeId); @@ -166,8 +166,8 @@ contract LBRouter is ILBRouter { _bin = ILBPair.Bin(uint112(_reserveX), uint112(_reserveY), 0, 0); } if (_bin.reserveX != 0 || _bin.reserveY != 0) { - (uint256 _amountInToBin, uint256 _amountOutOfBin, FeeHelper.FeesDistribution memory _fees) = _bin - .getAmounts(_fp, _activeId, _swapForY, _amountIn); + (uint256 _amountInToBin, uint256 _amountOutOfBin, FeeHelper.FeesDistribution memory _fees) = + _bin.getAmounts(_fp, _activeId, _swapForY, _amountIn); if (_amountInToBin > type(uint112).max) revert LBRouter__BinReserveOverflows(_activeId); @@ -191,12 +191,11 @@ contract LBRouter is ILBRouter { /// @param _activeId The active id of the pair /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) /// @return pair The address of the newly created LBPair - function createLBPair( - IERC20 _tokenX, - IERC20 _tokenY, - uint24 _activeId, - uint16 _binStep - ) external override returns (ILBPair pair) { + function createLBPair(IERC20 _tokenX, IERC20 _tokenY, uint24 _activeId, uint16 _binStep) + external + override + returns (ILBPair pair) + { pair = factory.createLBPair(_tokenX, _tokenY, _activeId, _binStep); } @@ -211,9 +210,7 @@ contract LBRouter is ILBRouter { returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) { ILBPair _LBPair = _getLBPairInformation( - _liquidityParameters.tokenX, - _liquidityParameters.tokenY, - _liquidityParameters.binStep + _liquidityParameters.tokenX, _liquidityParameters.tokenY, _liquidityParameters.binStep ); if (_liquidityParameters.tokenX != _LBPair.tokenX()) revert LBRouter__WrongTokenOrder(); @@ -235,9 +232,7 @@ contract LBRouter is ILBRouter { returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) { ILBPair _LBPair = _getLBPairInformation( - _liquidityParameters.tokenX, - _liquidityParameters.tokenY, - _liquidityParameters.binStep + _liquidityParameters.tokenX, _liquidityParameters.tokenY, _liquidityParameters.binStep ); if (_liquidityParameters.tokenX != _LBPair.tokenX()) revert LBRouter__WrongTokenOrder(); @@ -247,7 +242,7 @@ contract LBRouter is ILBRouter { } else if (_liquidityParameters.tokenY == wavax && _liquidityParameters.amountY == msg.value) { _liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountX); _wavaxDepositAndTransfer(address(_LBPair), msg.value); - } else + } else { revert LBRouter__WrongAvaxLiquidityParameters( address(_liquidityParameters.tokenX), address(_liquidityParameters.tokenY), @@ -255,6 +250,7 @@ contract LBRouter is ILBRouter { _liquidityParameters.amountY, msg.value ); + } (depositIds, liquidityMinted) = _addLiquidity(_liquidityParameters, _LBPair); } @@ -325,14 +321,8 @@ contract LBRouter is ILBRouter { (_amountTokenMin, _amountAVAXMin) = (_amountAVAXMin, _amountTokenMin); } - (uint256 _amountX, uint256 _amountY) = _removeLiquidity( - _LBPair, - _amountTokenMin, - _amountAVAXMin, - _ids, - _amounts, - address(this) - ); + (uint256 _amountX, uint256 _amountY) = + _removeLiquidity(_LBPair, _amountTokenMin, _amountAVAXMin, _ids, _amounts, address(this)); (amountToken, amountAVAX) = _isAVAXTokenY ? (_amountX, _amountY) : (_amountY, _amountX); } @@ -384,8 +374,9 @@ contract LBRouter is ILBRouter { address payable _to, uint256 _deadline ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { - if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) + if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); + } address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); @@ -439,7 +430,13 @@ contract LBRouter is ILBRouter { IERC20[] memory _tokenPath, address _to, uint256 _deadline - ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256[] memory amountsIn) { + ) + external + override + ensure(_deadline) + verifyInputs(_pairBinSteps, _tokenPath) + returns (uint256[] memory amountsIn) + { address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountOut); @@ -467,9 +464,16 @@ contract LBRouter is ILBRouter { IERC20[] memory _tokenPath, address payable _to, uint256 _deadline - ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256[] memory amountsIn) { - if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) + ) + external + override + ensure(_deadline) + verifyInputs(_pairBinSteps, _tokenPath) + returns (uint256[] memory amountsIn) + { + if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); + } address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountAVAXOut); @@ -570,8 +574,9 @@ contract LBRouter is ILBRouter { address payable _to, uint256 _deadline ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { - if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) + if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); + } address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); @@ -623,11 +628,7 @@ contract LBRouter is ILBRouter { /// @param _token The address of the token /// @param _to The address of the user to send back the tokens /// @param _amount The amount to send - function sweep( - IERC20 _token, - address _to, - uint256 _amount - ) external override onlyFactoryOwner { + function sweep(IERC20 _token, address _to, uint256 _amount) external override onlyFactoryOwner { if (address(_token) == address(0)) { if (_amount == type(uint256).max) _amount = address(this).balance; _safeTransferAVAX(_to, _amount); @@ -643,12 +644,11 @@ contract LBRouter is ILBRouter { /// @param _to The address of the user to send back the tokens /// @param _ids The list of token ids /// @param _amounts The list of amounts to send - function sweepLBToken( - ILBToken _lbToken, - address _to, - uint256[] calldata _ids, - uint256[] calldata _amounts - ) external override onlyFactoryOwner { + function sweepLBToken(ILBToken _lbToken, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) + external + override + onlyFactoryOwner + { _lbToken.safeBatchTransferFrom(address(this), _to, _ids, _amounts); } @@ -664,12 +664,15 @@ contract LBRouter is ILBRouter { { unchecked { if (_liq.deltaIds.length != _liq.distributionX.length && _liq.deltaIds.length != _liq.distributionY.length) + { revert LBRouter__LengthsMismatch(); + } - if (_liq.activeIdDesired > type(uint24).max || _liq.idSlippage > type(uint24).max) + if (_liq.activeIdDesired > type(uint24).max || _liq.idSlippage > type(uint24).max) { revert LBRouter__IdDesiredOverflows(_liq.activeIdDesired, _liq.idSlippage); + } - (, , uint256 _activeId) = _LBPair.getReservesAndId(); + (,, uint256 _activeId) = _LBPair.getReservesAndId(); if ( _liq.activeIdDesired + _liq.idSlippage < _activeId || _activeId + _liq.idSlippage < _liq.activeIdDesired ) revert LBRouter__IdSlippageCaught(_liq.activeIdDesired, _liq.idSlippage, _activeId); @@ -684,15 +687,12 @@ contract LBRouter is ILBRouter { uint256 _amountXAdded; uint256 _amountYAdded; - (_amountXAdded, _amountYAdded, liquidityMinted) = _LBPair.mint( - depositIds, - _liq.distributionX, - _liq.distributionY, - _liq.to - ); + (_amountXAdded, _amountYAdded, liquidityMinted) = + _LBPair.mint(depositIds, _liq.distributionX, _liq.distributionY, _liq.to); - if (_amountXAdded < _liq.amountXMin || _amountYAdded < _liq.amountYMin) + if (_amountXAdded < _liq.amountXMin || _amountYAdded < _liq.amountYMin) { revert LBRouter__AmountSlippageCaught(_liq.amountXMin, _amountXAdded, _liq.amountYMin, _amountYAdded); + } } } @@ -719,7 +719,7 @@ contract LBRouter is ILBRouter { address _pair = _pairs[i - 1]; if (_binStep == 0) { - (uint256 _reserveIn, uint256 _reserveOut, ) = IJoePair(_pair).getReserves(); + (uint256 _reserveIn, uint256 _reserveOut,) = IJoePair(_pair).getReserves(); if (_token > _tokenPath[i]) { (_reserveIn, _reserveOut) = (_reserveOut, _reserveIn); } @@ -727,7 +727,7 @@ contract LBRouter is ILBRouter { uint256 amountOut_ = amountsIn[i]; amountsIn[i - 1] = amountOut_.getAmountIn(_reserveIn, _reserveOut); } else { - (amountsIn[i - 1], ) = getSwapIn(ILBPair(_pair), amountsIn[i], ILBPair(_pair).tokenX() == _token); + (amountsIn[i - 1],) = getSwapIn(ILBPair(_pair), amountsIn[i], ILBPair(_pair).tokenX() == _token); } } } @@ -751,8 +751,9 @@ contract LBRouter is ILBRouter { ) private returns (uint256 amountX, uint256 amountY) { ILBToken(address(_LBPair)).safeBatchTransferFrom(msg.sender, address(_LBPair), _ids, _amounts); (amountX, amountY) = _LBPair.burn(_ids, _amounts, _to); - if (amountX < _amountXMin || amountY < _amountYMin) + if (amountX < _amountXMin || amountY < _amountYMin) { revert LBRouter__AmountSlippageCaught(_amountXMin, amountX, _amountYMin, amountY); + } } /// @notice Helper function to swap exact tokens for tokens @@ -788,7 +789,7 @@ contract LBRouter is ILBRouter { _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; if (_binStep == 0) { - (uint256 _reserve0, uint256 _reserve1, ) = IJoePair(_pair).getReserves(); + (uint256 _reserve0, uint256 _reserve1,) = IJoePair(_pair).getReserves(); if (_token < _tokenNext) { amountOut = amountOut.getAmountOut(_reserve0, _reserve1); @@ -888,7 +889,7 @@ contract LBRouter is ILBRouter { _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; if (_binStep == 0) { - (uint256 _reserve0, uint256 _reserve1, ) = IJoePair(_pair).getReserves(); + (uint256 _reserve0, uint256 _reserve1,) = IJoePair(_pair).getReserves(); if (_token < _tokenNext) { uint256 _amountIn = _token.balanceOf(_pair) - _reserve0; uint256 _amountOut = _amountIn.getAmountOut(_reserve0, _reserve1); @@ -913,14 +914,11 @@ contract LBRouter is ILBRouter { /// @param _tokenY The address of the tokenY /// @param _binStep The bin step of the LBPair /// @return The address of the LBPair - function _getLBPairInformation( - IERC20 _tokenX, - IERC20 _tokenY, - uint256 _binStep - ) private view returns (ILBPair) { + function _getLBPairInformation(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep) private view returns (ILBPair) { ILBPair _LBPair = factory.getLBPairInformation(_tokenX, _tokenY, _binStep).LBPair; - if (address(_LBPair) == address(0)) + if (address(_LBPair) == address(0)) { revert LBRouter__PairNotCreated(address(_tokenX), address(_tokenY), _binStep); + } return _LBPair; } @@ -930,15 +928,13 @@ contract LBRouter is ILBRouter { /// @param _tokenX The address of the tokenX /// @param _tokenY The address of the tokenY /// @return _pair The address of the pair of binStep `_binStep` - function _getPair( - uint256 _binStep, - IERC20 _tokenX, - IERC20 _tokenY - ) private view returns (address _pair) { + function _getPair(uint256 _binStep, IERC20 _tokenX, IERC20 _tokenY) private view returns (address _pair) { if (_binStep == 0) { _pair = oldFactory.getPair(address(_tokenX), address(_tokenY)); if (_pair == address(0)) revert LBRouter__PairNotCreated(address(_tokenX), address(_tokenY), _binStep); - } else _pair = address(_getLBPairInformation(_tokenX, _tokenY, _binStep)); + } else { + _pair = address(_getLBPairInformation(_tokenX, _tokenY, _binStep)); + } } function _getPairs(uint256[] memory _pairBinSteps, IERC20[] memory _tokenPath) @@ -964,7 +960,7 @@ contract LBRouter is ILBRouter { /// @param _to The address of the recipient /// @param _amount The AVAX amount to send function _safeTransferAVAX(address _to, uint256 _amount) private { - (bool success, ) = _to.call{value: _amount}(""); + (bool success,) = _to.call{value: _amount}(""); if (!success) revert LBRouter__FailedToSendAVAX(_to, _amount); } diff --git a/src/LBToken.sol b/src/LBToken.sol index 032172fa..86aa1140 100644 --- a/src/LBToken.sol +++ b/src/LBToken.sol @@ -111,12 +111,13 @@ contract LBToken is ILBToken { /// @param _to The address of the recipient /// @param _id The token id /// @param _amount The amount to send - function safeTransferFrom( - address _from, - address _to, - uint256 _id, - uint256 _amount - ) public virtual override checkAddresses(_from, _to) checkApproval(_from, msg.sender) { + function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount) + public + virtual + override + checkAddresses(_from, _to) + checkApproval(_from, msg.sender) + { address _spender = msg.sender; _transfer(_from, _to, _id, _amount); @@ -129,12 +130,7 @@ contract LBToken is ILBToken { /// @param _to The address of the recipient /// @param _ids The list of token ids /// @param _amounts The list of amounts to send - function safeBatchTransferFrom( - address _from, - address _to, - uint256[] calldata _ids, - uint256[] calldata _amounts - ) + function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) public virtual override @@ -164,12 +160,7 @@ contract LBToken is ILBToken { /// @param _to The address of the recipient /// @param _id The token id /// @param _amount The amount to send - function _transfer( - address _from, - address _to, - uint256 _id, - uint256 _amount - ) internal virtual { + function _transfer(address _from, address _to, uint256 _id, uint256 _amount) internal virtual { uint256 _fromBalance = _balances[_id][_from]; if (_fromBalance < _amount) revert LBToken__TransferExceedsBalance(_from, _id, _amount); @@ -185,11 +176,7 @@ contract LBToken is ILBToken { /// @param _account The address of the recipient /// @param _id The token id /// @param _amount The amount to mint - function _mint( - address _account, - uint256 _id, - uint256 _amount - ) internal virtual { + function _mint(address _account, uint256 _id, uint256 _amount) internal virtual { if (_account == address(0)) revert LBToken__MintToAddress0(); _beforeTokenTransfer(address(0), _account, _id, _amount); @@ -207,11 +194,7 @@ contract LBToken is ILBToken { /// @param _account The address of the owner /// @param _id The token id /// @param _amount The amount to destroy - function _burn( - address _account, - uint256 _id, - uint256 _amount - ) internal virtual { + function _burn(address _account, uint256 _id, uint256 _amount) internal virtual { if (_account == address(0)) revert LBToken__BurnFromAddress0(); uint256 _accountBalance = _balances[_id][_account]; @@ -231,11 +214,7 @@ contract LBToken is ILBToken { /// @param _owner The address of the owner /// @param _spender The address of the spender /// @param _approved The boolean value to grant or revoke permission - function _setApprovalForAll( - address _owner, - address _spender, - bool _approved - ) internal virtual { + function _setApprovalForAll(address _owner, address _spender, bool _approved) internal virtual { if (_owner == _spender) revert LBToken__SelfApproval(_owner); _spenderApprovals[_owner][_spender] = _approved; @@ -267,10 +246,5 @@ contract LBToken is ILBToken { /// @param to The address of the recipient of the token /// @param id The id of the token /// @param amount The amount of token of type `id` - function _beforeTokenTransfer( - address from, - address to, - uint256 id, - uint256 amount - ) internal virtual {} + function _beforeTokenTransfer(address from, address to, uint256 id, uint256 amount) internal virtual {} } diff --git a/src/interfaces/IJoePair.sol b/src/interfaces/IJoePair.sol index 68996103..a5b44c74 100644 --- a/src/interfaces/IJoePair.sol +++ b/src/interfaces/IJoePair.sol @@ -24,11 +24,7 @@ interface IJoePair { function transfer(address to, uint256 value) external returns (bool); - function transferFrom( - address from, - address to, - uint256 value - ) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); function DOMAIN_SEPARATOR() external view returns (bytes32); @@ -36,15 +32,8 @@ interface IJoePair { function nonces(address owner) external view returns (uint256); - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; + function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + external; event Mint(address indexed sender, uint256 amount0, uint256 amount1); event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); @@ -66,14 +55,7 @@ interface IJoePair { function token1() external view returns (address); - function getReserves() - external - view - returns ( - uint112 reserve0, - uint112 reserve1, - uint32 blockTimestampLast - ); + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); function price0CumulativeLast() external view returns (uint256); @@ -85,12 +67,7 @@ interface IJoePair { function burn(address to) external returns (uint256 amount0, uint256 amount1); - function swap( - uint256 amount0Out, - uint256 amount1Out, - address to, - bytes calldata data - ) external; + function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external; function skim(address to) external; diff --git a/src/interfaces/IJoeRouter01.sol b/src/interfaces/IJoeRouter01.sol index 8b62b577..d251641a 100644 --- a/src/interfaces/IJoeRouter01.sol +++ b/src/interfaces/IJoeRouter01.sol @@ -18,13 +18,7 @@ interface IJoeRouter01 { uint256 amountBMin, address to, uint256 deadline - ) - external - returns ( - uint256 amountA, - uint256 amountB, - uint256 liquidity - ); + ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity); function addLiquidityAVAX( address token, @@ -33,14 +27,7 @@ interface IJoeRouter01 { uint256 amountAVAXMin, address to, uint256 deadline - ) - external - payable - returns ( - uint256 amountToken, - uint256 amountAVAX, - uint256 liquidity - ); + ) external payable returns (uint256 amountToken, uint256 amountAVAX, uint256 liquidity); function removeLiquidity( address tokenA, @@ -104,12 +91,10 @@ interface IJoeRouter01 { uint256 deadline ) external returns (uint256[] memory amounts); - function swapExactAVAXForTokens( - uint256 amountOutMin, - address[] calldata path, - address to, - uint256 deadline - ) external payable returns (uint256[] memory amounts); + function swapExactAVAXForTokens(uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) + external + payable + returns (uint256[] memory amounts); function swapTokensForExactAVAX( uint256 amountOut, @@ -127,32 +112,30 @@ interface IJoeRouter01 { uint256 deadline ) external returns (uint256[] memory amounts); - function swapAVAXForExactTokens( - uint256 amountOut, - address[] calldata path, - address to, - uint256 deadline - ) external payable returns (uint256[] memory amounts); + function swapAVAXForExactTokens(uint256 amountOut, address[] calldata path, address to, uint256 deadline) + external + payable + returns (uint256[] memory amounts); - function quote( - uint256 amountA, - uint256 reserveA, - uint256 reserveB - ) external pure returns (uint256 amountB); + function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) external pure returns (uint256 amountB); - function getAmountOut( - uint256 amountIn, - uint256 reserveIn, - uint256 reserveOut - ) external pure returns (uint256 amountOut); + function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) + external + pure + returns (uint256 amountOut); - function getAmountIn( - uint256 amountOut, - uint256 reserveIn, - uint256 reserveOut - ) external pure returns (uint256 amountIn); + function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) + external + pure + returns (uint256 amountIn); - function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts); + function getAmountsOut(uint256 amountIn, address[] calldata path) + external + view + returns (uint256[] memory amounts); - function getAmountsIn(uint256 amountOut, address[] calldata path) external view returns (uint256[] memory amounts); + function getAmountsIn(uint256 amountOut, address[] calldata path) + external + view + returns (uint256[] memory amounts); } diff --git a/src/interfaces/ILBFactory.sol b/src/interfaces/ILBFactory.sol index 79dd22e0..ef54c033 100644 --- a/src/interfaces/ILBFactory.sol +++ b/src/interfaces/ILBFactory.sol @@ -24,11 +24,7 @@ interface ILBFactory is IPendingOwnable { } event LBPairCreated( - IERC20 indexed tokenX, - IERC20 indexed tokenY, - uint256 indexed binStep, - ILBPair LBPair, - uint256 pid + IERC20 indexed tokenX, IERC20 indexed tokenY, uint256 indexed binStep, ILBPair LBPair, uint256 pid ); event FeeRecipientSet(address oldRecipient, address newRecipient); @@ -98,11 +94,10 @@ interface ILBFactory is IPendingOwnable { function getNumberOfLBPairs() external view returns (uint256); - function getLBPairInformation( - IERC20 tokenX, - IERC20 tokenY, - uint256 binStep - ) external view returns (LBPairInformation memory); + function getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep) + external + view + returns (LBPairInformation memory); function getPreset(uint16 binStep) external @@ -127,19 +122,11 @@ interface ILBFactory is IPendingOwnable { function setLBPairImplementation(address LBPairImplementation) external; - function createLBPair( - IERC20 tokenX, - IERC20 tokenY, - uint24 activeId, - uint16 binStep - ) external returns (ILBPair pair); + function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) + external + returns (ILBPair pair); - function setLBPairIgnored( - IERC20 tokenX, - IERC20 tokenY, - uint256 binStep, - bool ignored - ) external; + function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) external; function setPreset( uint16 binStep, diff --git a/src/interfaces/ILBFlashLoanCallback.sol b/src/interfaces/ILBFlashLoanCallback.sol index a54d29fc..cff9fa0e 100644 --- a/src/interfaces/ILBFlashLoanCallback.sol +++ b/src/interfaces/ILBFlashLoanCallback.sol @@ -8,11 +8,7 @@ import "openzeppelin/token/ERC20/IERC20.sol"; /// @author Trader Joe /// @notice Required interface to interact with LB flash loans interface ILBFlashLoanCallback { - function LBFlashLoanCallback( - address sender, - IERC20 token, - uint256 amount, - uint256 fee, - bytes calldata data - ) external returns (bytes32); + function LBFlashLoanCallback(address sender, IERC20 token, uint256 amount, uint256 fee, bytes calldata data) + external + returns (bytes32); } diff --git a/src/interfaces/ILBPair.sol b/src/interfaces/ILBPair.sol index 3ea9a637..35b5164a 100644 --- a/src/interfaces/ILBPair.sol +++ b/src/interfaces/ILBPair.sol @@ -108,35 +108,19 @@ interface ILBPair { ); event FlashLoan( - address indexed sender, - ILBFlashLoanCallback indexed receiver, - IERC20 token, - uint256 amount, - uint256 fee + address indexed sender, ILBFlashLoanCallback indexed receiver, IERC20 token, uint256 amount, uint256 fee ); event CompositionFee( - address indexed sender, - address indexed recipient, - uint256 indexed id, - uint256 feesX, - uint256 feesY + address indexed sender, address indexed recipient, uint256 indexed id, uint256 feesX, uint256 feesY ); event DepositedToBin( - address indexed sender, - address indexed recipient, - uint256 indexed id, - uint256 amountX, - uint256 amountY + address indexed sender, address indexed recipient, uint256 indexed id, uint256 amountX, uint256 amountY ); event WithdrawnFromBin( - address indexed sender, - address indexed recipient, - uint256 indexed id, - uint256 amountX, - uint256 amountY + address indexed sender, address indexed recipient, uint256 indexed id, uint256 amountX, uint256 amountY ); event FeesCollected(address indexed sender, address indexed recipient, uint256 amountX, uint256 amountY); @@ -151,24 +135,12 @@ interface ILBPair { function factory() external view returns (ILBFactory); - function getReservesAndId() - external - view - returns ( - uint256 reserveX, - uint256 reserveY, - uint256 activeId - ); + function getReservesAndId() external view returns (uint256 reserveX, uint256 reserveY, uint256 activeId); function getGlobalFees() external view - returns ( - uint128 feesXTotal, - uint128 feesYTotal, - uint128 feesXProtocol, - uint128 feesYProtocol - ); + returns (uint128 feesXTotal, uint128 feesYTotal, uint128 feesXProtocol, uint128 feesYProtocol); function getOracleParameters() external @@ -186,11 +158,7 @@ interface ILBPair { function getOracleSampleFrom(uint256 timeDelta) external view - returns ( - uint256 cumulativeId, - uint256 cumulativeAccumulator, - uint256 cumulativeBinCrossed - ); + returns (uint256 cumulativeId, uint256 cumulativeAccumulator, uint256 cumulativeBinCrossed); function feeParameters() external view returns (FeeHelper.FeeParameters memory); @@ -205,31 +173,18 @@ interface ILBPair { function swap(bool sentTokenY, address to) external returns (uint256 amountXOut, uint256 amountYOut); - function flashLoan( - ILBFlashLoanCallback receiver, - IERC20 token, - uint256 amount, - bytes calldata data - ) external; + function flashLoan(ILBFlashLoanCallback receiver, IERC20 token, uint256 amount, bytes calldata data) external; function mint( uint256[] calldata ids, uint256[] calldata distributionX, uint256[] calldata distributionY, address to - ) - external - returns ( - uint256 amountXAddedToPair, - uint256 amountYAddedToPair, - uint256[] memory liquidityMinted - ); + ) external returns (uint256 amountXAddedToPair, uint256 amountYAddedToPair, uint256[] memory liquidityMinted); - function burn( - uint256[] calldata ids, - uint256[] calldata amounts, - address to - ) external returns (uint256 amountX, uint256 amountY); + function burn(uint256[] calldata ids, uint256[] calldata amounts, address to) + external + returns (uint256 amountX, uint256 amountY); function increaseOracleLength(uint16 newSize) external; diff --git a/src/interfaces/ILBRouter.sol b/src/interfaces/ILBRouter.sol index 89387a3f..b6a4c833 100644 --- a/src/interfaces/ILBRouter.sol +++ b/src/interfaces/ILBRouter.sol @@ -53,24 +53,19 @@ interface ILBRouter { function getPriceFromId(ILBPair LBPair, uint24 id) external view returns (uint256); - function getSwapIn( - ILBPair LBPair, - uint256 amountOut, - bool swapForY - ) external view returns (uint256 amountIn, uint256 feesIn); + function getSwapIn(ILBPair LBPair, uint256 amountOut, bool swapForY) + external + view + returns (uint256 amountIn, uint256 feesIn); - function getSwapOut( - ILBPair LBPair, - uint256 amountIn, - bool swapForY - ) external view returns (uint256 amountOut, uint256 feesIn); + function getSwapOut(ILBPair LBPair, uint256 amountIn, bool swapForY) + external + view + returns (uint256 amountOut, uint256 feesIn); - function createLBPair( - IERC20 tokenX, - IERC20 tokenY, - uint24 activeId, - uint16 binStep - ) external returns (ILBPair pair); + function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) + external + returns (ILBPair pair); function addLiquidity(LiquidityParameters calldata liquidityParameters) external @@ -182,16 +177,8 @@ interface ILBRouter { uint256 deadline ) external payable returns (uint256 amountOut); - function sweep( - IERC20 token, - address to, - uint256 amount - ) external; - - function sweepLBToken( - ILBToken _lbToken, - address _to, - uint256[] calldata _ids, - uint256[] calldata _amounts - ) external; + function sweep(IERC20 token, address to, uint256 amount) external; + + function sweepLBToken(ILBToken _lbToken, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) + external; } diff --git a/src/interfaces/ILBToken.sol b/src/interfaces/ILBToken.sol index 6219c686..a94f2b65 100644 --- a/src/interfaces/ILBToken.sol +++ b/src/interfaces/ILBToken.sol @@ -11,11 +11,7 @@ interface ILBToken is IERC165 { event TransferSingle(address indexed sender, address indexed from, address indexed to, uint256 id, uint256 amount); event TransferBatch( - address indexed sender, - address indexed from, - address indexed to, - uint256[] ids, - uint256[] amounts + address indexed sender, address indexed from, address indexed to, uint256[] ids, uint256[] amounts ); event ApprovalForAll(address indexed account, address indexed sender, bool approved); @@ -37,17 +33,8 @@ interface ILBToken is IERC165 { function setApprovalForAll(address sender, bool approved) external; - function safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount - ) external; - - function safeBatchTransferFrom( - address from, - address to, - uint256[] calldata id, - uint256[] calldata amount - ) external; + function safeTransferFrom(address from, address to, uint256 id, uint256 amount) external; + + function safeBatchTransferFrom(address from, address to, uint256[] calldata id, uint256[] calldata amount) + external; } diff --git a/src/libraries/BitMath.sol b/src/libraries/BitMath.sol index da8c29b1..71202592 100644 --- a/src/libraries/BitMath.sol +++ b/src/libraries/BitMath.sol @@ -11,11 +11,7 @@ library BitMath { /// @param _bit The bit index /// @param _rightSide Whether we're searching in the right side of the tree (true) or the left side (false) /// @return The index of the closest non-zero bit. If there is no closest bit, it returns max(uint256) - function closestBit( - uint256 _integer, - uint8 _bit, - bool _rightSide - ) internal pure returns (uint256) { + function closestBit(uint256 _integer, uint8 _bit, bool _rightSide) internal pure returns (uint256) { return _rightSide ? closestBitRight(_integer, _bit - 1) : closestBitLeft(_integer, _bit + 1); } diff --git a/src/libraries/Buffer.sol b/src/libraries/Buffer.sol index b07be168..fbe99837 100644 --- a/src/libraries/Buffer.sol +++ b/src/libraries/Buffer.sol @@ -14,12 +14,8 @@ library Buffer { assembly { if gt(n, 0) { switch x - case 0 { - result := sub(n, 1) - } - default { - result := mod(sub(x, 1), n) - } + case 0 { result := sub(n, 1) } + default { result := mod(sub(x, 1), n) } } } } diff --git a/src/libraries/Decoder.sol b/src/libraries/Decoder.sol index 30038863..4bca46ed 100644 --- a/src/libraries/Decoder.sol +++ b/src/libraries/Decoder.sol @@ -12,11 +12,7 @@ library Decoder { /// @param _mask The mask /// @param _offset The offset /// @return value The decoded value - function decode( - bytes32 _sample, - uint256 _mask, - uint256 _offset - ) internal pure returns (uint256 value) { + function decode(bytes32 _sample, uint256 _mask, uint256 _offset) internal pure returns (uint256 value) { assembly { value := and(shr(_offset, _sample), _mask) } diff --git a/src/libraries/Encoder.sol b/src/libraries/Encoder.sol index 09024069..50da0973 100644 --- a/src/libraries/Encoder.sol +++ b/src/libraries/Encoder.sol @@ -12,11 +12,7 @@ library Encoder { /// @param _mask The mask /// @param _offset The offset /// @return sample The encoded bytes32 sample - function encode( - uint256 _value, - uint256 _mask, - uint256 _offset - ) internal pure returns (bytes32 sample) { + function encode(uint256 _value, uint256 _mask, uint256 _offset) internal pure returns (bytes32 sample) { assembly { sample := shl(_offset, and(_value, _mask)) } diff --git a/src/libraries/FeeHelper.sol b/src/libraries/FeeHelper.sol index 65513c57..6aa57677 100644 --- a/src/libraries/FeeHelper.sol +++ b/src/libraries/FeeHelper.sol @@ -62,9 +62,8 @@ library FeeHelper { if (_deltaT < _fp.decayPeriod) { unchecked { // This can't overflow as `reductionFactor <= BASIS_POINT_MAX` - _fp.volatilityReference = uint24( - (uint256(_fp.reductionFactor) * _fp.volatilityAccumulated) / Constants.BASIS_POINT_MAX - ); + _fp.volatilityReference = + uint24((uint256(_fp.reductionFactor) * _fp.volatilityAccumulated) / Constants.BASIS_POINT_MAX); } } else { _fp.volatilityReference = 0; @@ -80,8 +79,8 @@ library FeeHelper { /// @param _fp The fee parameter /// @param _activeId The current active id function updateVolatilityAccumulated(FeeParameters memory _fp, uint256 _activeId) internal pure { - uint256 volatilityAccumulated = (_activeId.absSub(_fp.indexRef) * Constants.BASIS_POINT_MAX) + - _fp.volatilityReference; + uint256 volatilityAccumulated = + (_activeId.absSub(_fp.indexRef) * Constants.BASIS_POINT_MAX) + _fp.volatilityReference; _fp.volatilityAccumulated = volatilityAccumulated > _fp.maxVolatilityAccumulated ? _fp.maxVolatilityAccumulated : uint24(volatilityAccumulated); diff --git a/src/libraries/JoeLibrary.sol b/src/libraries/JoeLibrary.sol index d2157d4b..d09e67a9 100644 --- a/src/libraries/JoeLibrary.sol +++ b/src/libraries/JoeLibrary.sol @@ -16,22 +16,18 @@ library JoeLibrary { } // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset - function quote( - uint256 amountA, - uint256 reserveA, - uint256 reserveB - ) internal pure returns (uint256 amountB) { + function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) internal pure returns (uint256 amountB) { if (amountA == 0) revert JoeLibrary__InsufficientAmount(); if (reserveA == 0 || reserveB == 0) revert JoeLibrary__InsufficientLiquidity(); amountB = (amountA * reserveB) / reserveA; } // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset - function getAmountOut( - uint256 amountIn, - uint256 reserveIn, - uint256 reserveOut - ) internal pure returns (uint256 amountOut) { + function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) + internal + pure + returns (uint256 amountOut) + { if (amountIn == 0) revert JoeLibrary__InsufficientAmount(); if (reserveIn == 0 || reserveOut == 0) revert JoeLibrary__InsufficientLiquidity(); uint256 amountInWithFee = amountIn * 997; @@ -41,11 +37,11 @@ library JoeLibrary { } // given an output amount of an asset and pair reserves, returns a required input amount of the other asset - function getAmountIn( - uint256 amountOut, - uint256 reserveIn, - uint256 reserveOut - ) internal pure returns (uint256 amountIn) { + function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) + internal + pure + returns (uint256 amountIn) + { if (amountOut == 0) revert JoeLibrary__InsufficientAmount(); if (reserveIn == 0 || reserveOut == 0) revert JoeLibrary__InsufficientLiquidity(); uint256 numerator = reserveIn * amountOut * 1000; diff --git a/src/libraries/Math128x128.sol b/src/libraries/Math128x128.sol index cdbffc46..4bc51c87 100644 --- a/src/libraries/Math128x128.sol +++ b/src/libraries/Math128x128.sol @@ -114,85 +114,45 @@ library Math128x128 { invert := iszero(invert) } - if and(absY, 0x1) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x1) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x2) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x2) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x4) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x4) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x8) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x8) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x10) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x10) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x20) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x20) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x40) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x40) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x80) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x80) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x100) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x100) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x200) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x200) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x400) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x400) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x800) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x800) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x1000) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x1000) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x2000) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x2000) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x4000) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x4000) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x8000) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x8000) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x10000) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x10000) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x20000) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x20000) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x40000) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x40000) { result := shr(128, mul(result, pow)) } pow := shr(128, mul(pow, pow)) - if and(absY, 0x80000) { - result := shr(128, mul(result, pow)) - } + if and(absY, 0x80000) { result := shr(128, mul(result, pow)) } } } diff --git a/src/libraries/Math512Bits.sol b/src/libraries/Math512Bits.sol index 8f26c54b..941a0b80 100644 --- a/src/libraries/Math512Bits.sol +++ b/src/libraries/Math512Bits.sol @@ -27,11 +27,7 @@ library Math512Bits { /// @param y The multiplier as an uint256 /// @param denominator The divisor as an uint256 /// @return result The result as an uint256 - function mulDivRoundDown( - uint256 x, - uint256 y, - uint256 denominator - ) internal pure returns (uint256 result) { + function mulDivRoundDown(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { (uint256 prod0, uint256 prod1) = _getMulProds(x, y); return _getEndOfDivRoundDown(x, y, denominator, prod0, prod1); @@ -53,11 +49,7 @@ library Math512Bits { /// @param y The multiplier as an uint256 /// @param offset The offset as an uint256, can't be greater than 256 /// @return result The result as an uint256 - function mulShiftRoundDown( - uint256 x, - uint256 y, - uint256 offset - ) internal pure returns (uint256 result) { + function mulShiftRoundDown(uint256 x, uint256 y, uint256 offset) internal pure returns (uint256 result) { if (offset > 255) revert Math512Bits__OffsetOverflows(offset); (uint256 prod0, uint256 prod1) = _getMulProds(x, y); @@ -89,11 +81,7 @@ library Math512Bits { /// @param y The multiplier as an uint256 /// @param offset The offset as an uint256, can't be greater than 256 /// @return result The result as an uint256 - function mulShiftRoundUp( - uint256 x, - uint256 y, - uint256 offset - ) internal pure returns (uint256 result) { + function mulShiftRoundUp(uint256 x, uint256 y, uint256 offset) internal pure returns (uint256 result) { unchecked { result = mulShiftRoundDown(x, y, offset); if (mulmod(x, y, 1 << offset) != 0) result += 1; @@ -116,11 +104,7 @@ library Math512Bits { /// @param offset The number of bit to shift x as an uint256 /// @param denominator The divisor as an uint256 /// @return result The result as an uint256 - function shiftDivRoundDown( - uint256 x, - uint256 offset, - uint256 denominator - ) internal pure returns (uint256 result) { + function shiftDivRoundDown(uint256 x, uint256 offset, uint256 denominator) internal pure returns (uint256 result) { if (offset > 255) revert Math512Bits__OffsetOverflows(offset); uint256 prod0; uint256 prod1; @@ -149,11 +133,7 @@ library Math512Bits { /// @param offset The number of bit to shift x as an uint256 /// @param denominator The divisor as an uint256 /// @return result The result as an uint256 - function shiftDivRoundUp( - uint256 x, - uint256 offset, - uint256 denominator - ) internal pure returns (uint256 result) { + function shiftDivRoundUp(uint256 x, uint256 offset, uint256 denominator) internal pure returns (uint256 result) { result = shiftDivRoundDown(x, offset, denominator); unchecked { if (mulmod(x, 1 << offset, denominator) != 0) result += 1; @@ -183,13 +163,11 @@ library Math512Bits { /// @param prod0 The least significant 256 bits of the product /// @param prod1 The most significant 256 bits of the product /// @return result The result as an uint256 - function _getEndOfDivRoundDown( - uint256 x, - uint256 y, - uint256 denominator, - uint256 prod0, - uint256 prod1 - ) private pure returns (uint256 result) { + function _getEndOfDivRoundDown(uint256 x, uint256 y, uint256 denominator, uint256 prod0, uint256 prod1) + private + pure + returns (uint256 result) + { // Handle non-overflow cases, 256 by 256 division if (prod1 == 0) { unchecked { diff --git a/src/libraries/Oracle.sol b/src/libraries/Oracle.sol index 77c2bb3d..3d3844cf 100644 --- a/src/libraries/Oracle.sol +++ b/src/libraries/Oracle.sol @@ -71,17 +71,14 @@ library Oracle { uint256 _totalWeight = _weightPrev + _weightNext; // _next.timestamp() - _sample.timestamp() cumulativeId = - (_sample.cumulativeId() * _weightPrev + _next.cumulativeId() * _weightNext) / - _totalWeight; - cumulativeVolatilityAccumulated = - (_sample.cumulativeVolatilityAccumulated() * - _weightPrev + - _next.cumulativeVolatilityAccumulated() * - _weightNext) / - _totalWeight; - cumulativeBinCrossed = - (_sample.cumulativeBinCrossed() * _weightPrev + _next.cumulativeBinCrossed() * _weightNext) / - _totalWeight; + (_sample.cumulativeId() * _weightPrev + _next.cumulativeId() * _weightNext) / _totalWeight; + cumulativeVolatilityAccumulated = ( + _sample.cumulativeVolatilityAccumulated() * _weightPrev + + _next.cumulativeVolatilityAccumulated() * _weightNext + ) / _totalWeight; + cumulativeBinCrossed = ( + _sample.cumulativeBinCrossed() * _weightPrev + _next.cumulativeBinCrossed() * _weightNext + ) / _totalWeight; return (_lookUpTimestamp, cumulativeId, cumulativeVolatilityAccumulated, cumulativeBinCrossed); } } @@ -119,7 +116,9 @@ library Oracle { assembly { updatedIndex := addmod(_lastIndex, 1, _size) } - } else updatedIndex = _lastIndex; + } else { + updatedIndex = _lastIndex; + } _oracle[updatedIndex] = _updatedPackedSample; } @@ -179,6 +178,8 @@ library Oracle { _id := addmod(_id, 1, _activeSize) } (prev, next) = (_sample, _oracle[_id]); - } else (prev, next) = (_oracle[_id.before(_activeSize)], _sample); + } else { + (prev, next) = (_oracle[_id.before(_activeSize)], _sample); + } } } diff --git a/src/libraries/Samples.sol b/src/libraries/Samples.sol index d4724ebe..fad23a30 100644 --- a/src/libraries/Samples.sol +++ b/src/libraries/Samples.sol @@ -37,12 +37,11 @@ library Samples { /// @param _volatilityAccumulated The volatility accumulated of the pair during the latest swap /// @param _binCrossed The bin crossed during the latest swap /// @return packedSample The packed sample as bytes32 - function update( - bytes32 _lastSample, - uint256 _activeId, - uint256 _volatilityAccumulated, - uint256 _binCrossed - ) internal view returns (bytes32 packedSample) { + function update(bytes32 _lastSample, uint256 _activeId, uint256 _volatilityAccumulated, uint256 _binCrossed) + internal + view + returns (bytes32 packedSample) + { uint256 _deltaTime = block.timestamp - timestamp(_lastSample); // cumulative can overflow without any issue as what matter is the delta cumulative. @@ -50,9 +49,8 @@ library Samples { // The delta calculation needs to be unchecked math to allow for it to overflow again. unchecked { uint256 _cumulativeId = cumulativeId(_lastSample) + _activeId * _deltaTime; - uint256 _cumulativeVolatilityAccumulated = cumulativeVolatilityAccumulated(_lastSample) + - _volatilityAccumulated * - _deltaTime; + uint256 _cumulativeVolatilityAccumulated = + cumulativeVolatilityAccumulated(_lastSample) + _volatilityAccumulated * _deltaTime; uint256 _cumulativeBinCrossed = cumulativeBinCrossed(_lastSample) + _binCrossed * _deltaTime; return pack(_cumulativeBinCrossed, _cumulativeVolatilityAccumulated, _cumulativeId, block.timestamp, 1); @@ -73,15 +71,12 @@ library Samples { uint256 _timestamp, uint256 _initialized ) internal pure returns (bytes32 packedSample) { - return - _cumulativeBinCrossed.encode(_MASK_CUMULATIVE_BIN_CROSSED, _OFFSET_CUMULATIVE_BIN_CROSSED) | - _cumulativeVolatilityAccumulated.encode( - _MASK_CUMULATIVE_VolatilityAccumulated, - _OFFSET_CUMULATIVE_VolatilityAccumulated - ) | - _cumulativeId.encode(_MASK_CUMULATIVE_ID, _OFFSET_CUMULATIVE_ID) | - _timestamp.encode(_MASK_TIMESTAMP, _OFFSET_TIMESTAMP) | - _initialized.encode(_MASK_INITIALIZED, _OFFSET_INITIALIZED); + return _cumulativeBinCrossed.encode(_MASK_CUMULATIVE_BIN_CROSSED, _OFFSET_CUMULATIVE_BIN_CROSSED) + | _cumulativeVolatilityAccumulated.encode( + _MASK_CUMULATIVE_VolatilityAccumulated, _OFFSET_CUMULATIVE_VolatilityAccumulated + ) | _cumulativeId.encode(_MASK_CUMULATIVE_ID, _OFFSET_CUMULATIVE_ID) + | _timestamp.encode(_MASK_TIMESTAMP, _OFFSET_TIMESTAMP) + | _initialized.encode(_MASK_INITIALIZED, _OFFSET_INITIALIZED); } /// @notice View function to return the initialized value diff --git a/src/libraries/SwapHelper.sol b/src/libraries/SwapHelper.sol index efa81ed3..cd30f375 100644 --- a/src/libraries/SwapHelper.sol +++ b/src/libraries/SwapHelper.sol @@ -34,15 +34,7 @@ library SwapHelper { uint256 activeId, bool swapForY, uint256 amountIn - ) - internal - pure - returns ( - uint256 amountInToBin, - uint256 amountOutOfBin, - FeeHelper.FeesDistribution memory fees - ) - { + ) internal pure returns (uint256 amountInToBin, uint256 amountOutOfBin, FeeHelper.FeesDistribution memory fees) { uint256 _price = BinHelper.getPriceFromId(activeId, fp.binStep); uint256 _reserve; diff --git a/src/libraries/TokenHelper.sol b/src/libraries/TokenHelper.sol index 94146305..54179f4b 100644 --- a/src/libraries/TokenHelper.sol +++ b/src/libraries/TokenHelper.sol @@ -20,12 +20,7 @@ library TokenHelper { /// @param owner The owner of the tokens /// @param recipient The address of the recipient /// @param amount The amount to send - function safeTransferFrom( - IERC20 token, - address owner, - address recipient, - uint256 amount - ) internal { + function safeTransferFrom(IERC20 token, address owner, address recipient, uint256 amount) internal { if (amount != 0) { bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, owner, recipient, amount); @@ -39,11 +34,7 @@ library TokenHelper { /// @param token The address of the token /// @param recipient The address of the recipient /// @param amount The amount to send - function safeTransfer( - IERC20 token, - address recipient, - uint256 amount - ) internal { + function safeTransfer(IERC20 token, address recipient, uint256 amount) internal { if (amount != 0) { bytes memory data = abi.encodeWithSelector(token.transfer.selector, recipient, amount); @@ -58,11 +49,7 @@ library TokenHelper { /// @param reserve The total reserve of token /// @param fees The total fees of token /// @return The amount received by the pair - function received( - IERC20 token, - uint256 reserve, - uint256 fees - ) internal view returns (uint256) { + function received(IERC20 token, uint256 reserve, uint256 fees) internal view returns (uint256) { uint256 _internalBalance; unchecked { _internalBalance = reserve + fees; @@ -81,8 +68,9 @@ library TokenHelper { if (success) { if (returnData.length == 0 && !_isContract(target)) revert TokenHelper__NonContract(); } else { - if (returnData.length == 0) revert TokenHelper__CallFailed(); - else { + if (returnData.length == 0) { + revert TokenHelper__CallFailed(); + } else { // Look for revert reason and bubble it up if present assembly { revert(add(32, returnData), mload(returnData)) diff --git a/src/libraries/TreeMath.sol b/src/libraries/TreeMath.sol index 87d066ff..d8f0ff2b 100644 --- a/src/libraries/TreeMath.sol +++ b/src/libraries/TreeMath.sol @@ -18,11 +18,11 @@ library TreeMath { /// @param _rightSide Whether we're searching in the right side of the tree (true) or the left side (false) /// for the closest non zero bit on the right or the left /// @return The closest non zero bit on the right (or left) side of the tree - function findFirstBin( - mapping(uint256 => uint256)[3] storage _tree, - uint24 _binId, - bool _rightSide - ) internal view returns (uint24) { + function findFirstBin(mapping(uint256 => uint256)[3] storage _tree, uint24 _binId, bool _rightSide) + internal + view + returns (uint24) + { unchecked { uint256 current; uint256 bit; diff --git a/test/BinHelper.T.sol b/test/BinHelper.T.sol index e35bc4ec..45c4aa96 100644 --- a/test/BinHelper.T.sol +++ b/test/BinHelper.T.sol @@ -2,26 +2,20 @@ pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "./helpers/TestHelper.sol"; contract BinHelperTest is TestHelper { function testInversePriceForOppositeBins() public { assertApproxEqAbs( - (getPriceFromId(ID_ONE + 10) * getPriceFromId(ID_ONE - 10)) / Constants.SCALE, - Constants.SCALE, - 1 + (getPriceFromId(ID_ONE + 10) * getPriceFromId(ID_ONE - 10)) / Constants.SCALE, Constants.SCALE, 1 ); assertApproxEqAbs( - (getPriceFromId(ID_ONE + 1_000) * getPriceFromId(ID_ONE - 1_000)) / Constants.SCALE, - Constants.SCALE, - 1 + (getPriceFromId(ID_ONE + 1_000) * getPriceFromId(ID_ONE - 1_000)) / Constants.SCALE, Constants.SCALE, 1 ); assertApproxEqAbs( - (getPriceFromId(ID_ONE + 10_000) * getPriceFromId(ID_ONE - 10_000)) / Constants.SCALE, - Constants.SCALE, - 1 + (getPriceFromId(ID_ONE + 10_000) * getPriceFromId(ID_ONE - 10_000)) / Constants.SCALE, Constants.SCALE, 1 ); } } diff --git a/test/Faucet.t.sol b/test/Faucet.t.sol index 9ad0aa6a..d9bf5a21 100644 --- a/test/Faucet.t.sol +++ b/test/Faucet.t.sol @@ -3,8 +3,9 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; -import "test/mocks/ERC20MockDecimalsOwnable.sol"; +import "test/mocks/ERC20.sol"; import "test/mocks/Faucet.sol"; +import "src/LBErrors.sol"; contract FaucetTest is Test { Faucet private faucet; @@ -14,8 +15,8 @@ contract FaucetTest is Test { address internal constant BOB = address(bytes20(bytes32(keccak256(bytes("BOB"))))); address internal constant OPERATOR = address(bytes20(bytes32(keccak256(bytes("OPERATOR"))))); - ERC20MockDecimalsOwnable token6; - ERC20MockDecimalsOwnable token12; + ERC20Mock token6; + ERC20Mock token12; IERC20 AVAX = IERC20(address(0)); @@ -25,8 +26,8 @@ contract FaucetTest is Test { uint256 constant REQUEST_COOLDOWN = 24 hours; function setUp() public { - token6 = new ERC20MockDecimalsOwnable("Mock Token 6 decimals", "TOKEN6", 6); - token12 = new ERC20MockDecimalsOwnable("Mock Token 12 decimals", "TOKEN12", 12); + token6 = new ERC20Mock( 6); + token12 = new ERC20Mock( 12); faucet = new Faucet{value: 10 * AVAX_PER_REQUEST}(AVAX_PER_REQUEST, REQUEST_COOLDOWN); @@ -70,7 +71,7 @@ contract FaucetTest is Test { } function testAddToken() external { - ERC20MockDecimalsOwnable newToken = new ERC20MockDecimalsOwnable("New Token", "NEW_TOKEN", 18); + ERC20Mock newToken = new ERC20Mock(18); newToken.mint(address(faucet), 1_000e18); vm.startPrank(ALICE, ALICE); @@ -96,10 +97,10 @@ contract FaucetTest is Test { faucet.removeFaucetToken(IERC20(token6)); IERC20 faucetToken; - (faucetToken, ) = faucet.faucetTokens(0); + (faucetToken,) = faucet.faucetTokens(0); assertEq(address(faucetToken), address(AVAX)); - (faucetToken, ) = faucet.faucetTokens(1); + (faucetToken,) = faucet.faucetTokens(1); assertEq(address(faucetToken), address(token12)); assertEq(faucet.owner(), DEV); diff --git a/test/LBRouter.Swaps.t.sol b/test/LBRouter.Swaps.t.sol deleted file mode 100644 index b4d5c442..00000000 --- a/test/LBRouter.Swaps.t.sol +++ /dev/null @@ -1,835 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "./TestHelper.sol"; - -contract LiquidityBinRouterTest is TestHelper { - LBPair internal pair0; - LBPair internal pair1; - LBPair internal pair2; - LBPair internal pair3; - LBPair internal taxTokenPair1; - LBPair internal taxTokenPair; - - function setUp() public { - token6D = new ERC20MockDecimals(6); - token10D = new ERC20MockDecimals(10); - token12D = new ERC20MockDecimals(12); - token18D = new ERC20MockDecimals(18); - token24D = new ERC20MockDecimals(24); - - taxToken = new ERC20WithTransferTax(); - - wavax = new WAVAX(); - - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - addAllAssetsToQuoteWhitelist(factory); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - - router = new LBRouter(factory, IJoeFactory(JOE_V1_FACTORY_ADDRESS), IWAVAX(address(wavax))); - - pair = createLBPairDefaultFees(token6D, token18D); - addLiquidityFromRouter(token6D, token18D, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - - pair0 = createLBPairDefaultFees(token6D, token10D); - addLiquidityFromRouter(token6D, token10D, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - pair1 = createLBPairDefaultFees(token10D, token12D); - addLiquidityFromRouter(token10D, token12D, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - pair2 = createLBPairDefaultFees(token12D, token18D); - addLiquidityFromRouter(token12D, token18D, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - pair3 = createLBPairDefaultFees(token18D, token24D); - addLiquidityFromRouter(token18D, token24D, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - taxTokenPair1 = createLBPairDefaultFees(token6D, taxToken); - addLiquidityFromRouter(token6D, ERC20MockDecimals(address(taxToken)), 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - - taxTokenPair = createLBPairDefaultFees(taxToken, wavax); - addLiquidityFromRouter( - ERC20MockDecimals(address(taxToken)), - ERC20MockDecimals(address(wavax)), - 100e18, - ID_ONE, - 9, - 2, - DEFAULT_BIN_STEP - ); - - pairWavax = createLBPairDefaultFees(token6D, wavax); - addLiquidityFromRouter(token6D, ERC20MockDecimals(address(wavax)), 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - } - - function testSwapExactTokensForTokensSinglePair() public { - uint256 amountIn = 1e18; - - token6D.mint(DEV, amountIn); - - token6D.approve(address(router), amountIn); - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = token6D; - tokenList[1] = token18D; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - (uint256 amountOut, ) = router.getSwapOut(pair, amountIn, true); - vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); - router.swapExactTokensForTokens(amountIn, amountOut + 1, pairVersions, tokenList, DEV, block.timestamp); - - router.swapExactTokensForTokens(amountIn, amountOut, pairVersions, tokenList, DEV, block.timestamp); - - assertApproxEqAbs(token18D.balanceOf(DEV), amountOut, 10); - } - - function testSwapExactTokensForAvaxSinglePair() public { - uint256 amountIn = 1e18; - - token6D.mint(ALICE, amountIn); - - vm.startPrank(ALICE); - token6D.approve(address(router), amountIn); - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = token6D; - tokenList[1] = wavax; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - (uint256 amountOut, ) = router.getSwapOut(pair, amountIn, true); - - uint256 devBalanceBefore = ALICE.balance; - vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); - router.swapExactTokensForAVAX(amountIn, amountOut + 1, pairVersions, tokenList, ALICE, block.timestamp); - - router.swapExactTokensForAVAX(amountIn, amountOut, pairVersions, tokenList, ALICE, block.timestamp); - vm.stopPrank(); - - assertEq(ALICE.balance - devBalanceBefore, amountOut); - } - - function testSwapExactAVAXForTokensSinglePair() public { - uint256 amountIn = 1e18; - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = wavax; - tokenList[1] = token6D; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - (uint256 amountOut, ) = router.getSwapOut(pairWavax, amountIn, false); - - vm.deal(DEV, amountIn); - vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); - router.swapExactAVAXForTokens{value: amountIn}(amountOut + 1, pairVersions, tokenList, DEV, block.timestamp); - - router.swapExactAVAXForTokens{value: amountIn}(amountOut, pairVersions, tokenList, DEV, block.timestamp); - - assertApproxEqAbs(token6D.balanceOf(DEV), amountOut, 13); - } - - function testSwapTokensForExactTokensSinglePair() public { - uint256 amountOut = 1e18; - - (uint256 amountIn, ) = router.getSwapIn(pair, amountOut, true); - token6D.mint(DEV, amountIn); - - token6D.approve(address(router), amountIn); - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = token6D; - tokenList[1] = token18D; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - vm.expectRevert(abi.encodeWithSelector(LBRouter__MaxAmountInExceeded.selector, amountIn - 1, amountIn)); - router.swapTokensForExactTokens(amountOut, amountIn - 1, pairVersions, tokenList, DEV, block.timestamp); - - router.swapTokensForExactTokens(amountOut, amountIn, pairVersions, tokenList, DEV, block.timestamp); - - assertApproxEqAbs(token18D.balanceOf(DEV), amountOut, 10); - } - - function testSwapTokensForExactAVAXSinglePair() public { - uint256 amountOut = 1e18; - - (uint256 amountIn, ) = router.getSwapIn(pairWavax, amountOut, true); - token6D.mint(ALICE, amountIn); - - vm.startPrank(ALICE); - token6D.approve(address(router), amountIn); - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = token6D; - tokenList[1] = wavax; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - uint256 devBalanceBefore = ALICE.balance; - - vm.expectRevert(abi.encodeWithSelector(LBRouter__MaxAmountInExceeded.selector, amountIn - 1, amountIn)); - router.swapTokensForExactAVAX(amountOut, amountIn - 1, pairVersions, tokenList, ALICE, block.timestamp); - - router.swapTokensForExactAVAX(amountOut, amountIn, pairVersions, tokenList, ALICE, block.timestamp); - vm.stopPrank(); - - assertEq(ALICE.balance - devBalanceBefore, amountOut); - } - - function testSwapAVAXForExactTokensSinglePair() public { - uint256 amountOut = 1e18; - - (uint256 amountIn, ) = router.getSwapIn(pairWavax, amountOut, false); - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = wavax; - tokenList[1] = token6D; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - vm.deal(DEV, amountIn); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__MaxAmountInExceeded.selector, amountIn - 1, amountIn)); - router.swapAVAXForExactTokens{value: amountIn - 1}(amountOut, pairVersions, tokenList, ALICE, block.timestamp); - router.swapAVAXForExactTokens{value: amountIn}(amountOut, pairVersions, tokenList, DEV, block.timestamp); - - assertApproxEqAbs(token6D.balanceOf(DEV), amountOut, 13); - } - - function testSwapExactTokensForTokensSupportingFeeOnTransferTokens() public { - uint256 amountIn = 1e18; - - taxToken.mint(DEV, amountIn); - - taxToken.approve(address(router), amountIn); - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = taxToken; - tokenList[1] = wavax; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - (uint256 amountOut, ) = router.getSwapOut(taxTokenPair, amountIn, true); - amountOut = amountOut / 2; - vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - amountIn, - amountOut + 1, - pairVersions, - tokenList, - DEV, - block.timestamp - ); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - amountIn, - 0, - pairVersions, - tokenList, - DEV, - block.timestamp - ); - - // 50% tax to take into account - assertApproxEqAbs(wavax.balanceOf(DEV), amountOut, 10); - - // Swap back in the other direction - amountIn = wavax.balanceOf(DEV); - wavax.approve(address(router), amountIn); - tokenList[0] = wavax; - tokenList[1] = taxToken; - - (amountOut, ) = router.getSwapOut(taxTokenPair, amountIn, true); - amountOut = amountOut / 2; - uint256 balanceBefore = taxToken.balanceOf(DEV); - vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - amountIn, - amountOut + 1, - pairVersions, - tokenList, - DEV, - block.timestamp - ); - - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - amountIn, - amountOut, - pairVersions, - tokenList, - DEV, - block.timestamp - ); - - assertApproxEqAbs(taxToken.balanceOf(DEV) - balanceBefore, amountOut, 10); - } - - function testSwapExactTokensForAVAXSupportingFeeOnTransferTokens() public { - uint256 amountIn = 1e18; - - taxToken.mint(ALICE, amountIn); - - vm.startPrank(ALICE); - taxToken.approve(address(router), amountIn); - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = taxToken; - tokenList[1] = wavax; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - (uint256 amountOut, ) = router.getSwapOut(taxTokenPair, amountIn, true); - amountOut = amountOut / 2; - uint256 devBalanceBefore = ALICE.balance; - vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn, - amountOut + 1, - pairVersions, - tokenList, - ALICE, - block.timestamp - ); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn, - amountOut, - pairVersions, - tokenList, - ALICE, - block.timestamp - ); - vm.stopPrank(); - - assertGe(ALICE.balance - devBalanceBefore, amountOut); - } - - function testSwapExactAVAXForTokensSupportingFeeOnTransferTokens() public { - uint256 amountIn = 1e18; - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = wavax; - tokenList[1] = taxToken; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - (uint256 amountOut, ) = router.getSwapOut(taxTokenPair, amountIn, true); - amountOut = amountOut / 2; - vm.deal(DEV, amountIn); - vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( - amountOut + 1, - pairVersions, - tokenList, - DEV, - block.timestamp - ); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( - amountOut, - pairVersions, - tokenList, - DEV, - block.timestamp - ); - - assertApproxEqAbs(taxToken.balanceOf(DEV), amountOut, 10); - } - - function testSwapExactTokensForTokensMultiplePairs() public { - uint256 amountIn = 1e18; - - token6D.mint(DEV, amountIn); - - token6D.approve(address(router), amountIn); - - (IERC20[] memory tokenList, uint256[] memory pairVersions) = _buildComplexSwapRoute(); - - router.swapExactTokensForTokens(amountIn, 0, pairVersions, tokenList, DEV, block.timestamp); - - assertGt(token24D.balanceOf(DEV), 0); - } - - function testSwapTokensForExactTokensMultiplePairs() public { - uint256 amountOut = 1e18; - - token6D.mint(DEV, 100e18); - - token6D.approve(address(router), 100e18); - - (IERC20[] memory tokenList, uint256[] memory pairVersions) = _buildComplexSwapRoute(); - vm.expectRevert( - abi.encodeWithSelector(LBRouter__MaxAmountInExceeded.selector, 100500938281494149, 1005015664148120440) - ); - router.swapTokensForExactTokens(amountOut, 100500938281494149, pairVersions, tokenList, DEV, block.timestamp); - router.swapTokensForExactTokens(amountOut, 100e18, pairVersions, tokenList, DEV, block.timestamp); - - assertEq(token24D.balanceOf(DEV), amountOut); - } - - function testSwapWithDifferentBinSteps() public { - factory.setPreset( - 75, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - 5, - 10, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME - ); - createLBPairDefaultFeesFromStartIdAndBinStep(token6D, token18D, ID_ONE, 75); - addLiquidityFromRouter(token6D, token18D, 100e18, ID_ONE, 9, 2, 75); - - uint256 amountIn = 1e18; - - token6D.mint(DEV, amountIn); - token6D.approve(address(router), amountIn); - - IERC20[] memory tokenList; - uint256[] memory pairVersions; - tokenList = new IERC20[](2); - tokenList[0] = token6D; - tokenList[1] = token18D; - pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - router.swapExactTokensForTokens(amountIn, 0, pairVersions, tokenList, DEV, block.timestamp); - - assertGt(token18D.balanceOf(DEV), 0); - - token18D.approve(address(router), token18D.balanceOf(DEV)); - - tokenList[0] = token18D; - tokenList[1] = token6D; - pairVersions = new uint256[](1); - pairVersions[0] = 75; - - router.swapExactTokensForTokens(token18D.balanceOf(DEV), 0, pairVersions, tokenList, DEV, block.timestamp); - assertGt(token6D.balanceOf(DEV), 0); - } - - function _buildComplexSwapRoute() private view returns (IERC20[] memory tokenList, uint256[] memory pairVersions) { - tokenList = new IERC20[](5); - tokenList[0] = token6D; - tokenList[1] = token10D; - tokenList[2] = token12D; - tokenList[3] = token18D; - tokenList[4] = token24D; - - pairVersions = new uint256[](4); - pairVersions[0] = DEFAULT_BIN_STEP; - pairVersions[1] = DEFAULT_BIN_STEP; - pairVersions[2] = DEFAULT_BIN_STEP; - pairVersions[3] = DEFAULT_BIN_STEP; - } - - function testTaxTokenEqualOnlyV2Swap() public { - uint256 amountIn = 1e18; - - taxToken.mint(ALICE, amountIn); - taxToken.mint(BOB, amountIn); - token6D.mint(ALICE, amountIn); - token6D.mint(BOB, amountIn); - - IERC20[] memory tokenList = new IERC20[](3); - tokenList[0] = token6D; - tokenList[1] = taxToken; - tokenList[2] = wavax; - uint256[] memory pairVersions = new uint256[](2); - pairVersions[0] = DEFAULT_BIN_STEP; - pairVersions[1] = DEFAULT_BIN_STEP; - - vm.startPrank(ALICE); - token6D.approve(address(router), amountIn); - taxToken.approve(address(router), amountIn); - uint256 aliceBalanceBefore = ALICE.balance; - uint256 amountOutNotSupporting = router.swapExactTokensForAVAX( - amountIn, - 0, - pairVersions, - tokenList, - ALICE, - block.timestamp - ); - vm.stopPrank(); - - vm.startPrank(BOB); - token6D.approve(address(router), amountIn); - taxToken.approve(address(router), amountIn); - uint256 bobBalanceBefore = BOB.balance; - uint256 amountOutSupporting = router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn, - 0, - pairVersions, - tokenList, - BOB, - block.timestamp - ); - vm.stopPrank(); - - assertEq(ALICE.balance, BOB.balance); - assertEq(amountOutNotSupporting, amountOutSupporting); - } - - function testSwappingOnNotExistingV2PairReverts() public { - IERC20[] memory tokenListAvaxIn; - IERC20[] memory tokenList; - uint256[] memory pairVersions; - - uint256 amountIn2 = 1e18; - vm.deal(DEV, amountIn2); - - tokenList = new IERC20[](3); - tokenList[0] = token6D; - tokenList[1] = token18D; - tokenList[2] = wavax; - - pairVersions = new uint256[](2); - pairVersions[0] = DEFAULT_BIN_STEP; - pairVersions[1] = DEFAULT_BIN_STEP + 1; - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token18D, wavax, pairVersions[1])); - router.swapExactTokensForTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token18D, wavax, pairVersions[1])); - router.swapExactTokensForAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token18D, wavax, pairVersions[1])); - router.swapTokensForExactTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token18D, wavax, pairVersions[1])); - router.swapTokensForExactAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token18D, wavax, pairVersions[1])); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - amountIn2, - 0, - pairVersions, - tokenList, - DEV, - block.timestamp - ); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token18D, wavax, pairVersions[1])); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn2, - 0, - pairVersions, - tokenList, - DEV, - block.timestamp - ); - - tokenListAvaxIn = new IERC20[](3); - tokenListAvaxIn[0] = wavax; - tokenListAvaxIn[1] = token6D; - tokenListAvaxIn[2] = token18D; - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token6D, token18D, pairVersions[1])); - router.swapExactAVAXForTokens{value: amountIn2}(0, pairVersions, tokenListAvaxIn, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token6D, token18D, pairVersions[1])); - router.swapAVAXForExactTokens{value: amountIn2}(0, pairVersions, tokenListAvaxIn, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token6D, token18D, pairVersions[1])); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn2}( - 0, - pairVersions, - tokenListAvaxIn, - DEV, - block.timestamp - ); - } - - receive() external payable {} -} - -contract LiquidityBinRouterForkTest is TestHelper { - LBPair internal pair0; - LBPair internal pair1; - LBPair internal pair2; - LBPair internal pair3; - LBPair internal taxTokenPair; - - function setUp() public { - vm.createSelectFork(vm.rpcUrl("avalanche"), 19_358_000); - token6D = new ERC20MockDecimals(6); - token10D = new ERC20MockDecimals(10); - token12D = new ERC20MockDecimals(12); - token18D = new ERC20MockDecimals(18); - token24D = new ERC20MockDecimals(24); - - taxToken = new ERC20WithTransferTax(); - - wavax = WAVAX(WAVAX_AVALANCHE_ADDRESS); - usdc = ERC20MockDecimals(USDC_AVALANCHE_ADDRESS); - - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - addAllAssetsToQuoteWhitelist(factory); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - - router = new LBRouter(factory, IJoeFactory(JOE_V1_FACTORY_ADDRESS), IWAVAX(address(wavax))); - - pair = createLBPairDefaultFees(token6D, token18D); - addLiquidityFromRouter(token6D, token18D, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - - pair0 = createLBPairDefaultFees(token6D, token10D); - addLiquidityFromRouter(token6D, token10D, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - pair1 = createLBPairDefaultFees(token10D, token12D); - addLiquidityFromRouter(token10D, token12D, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - pair2 = createLBPairDefaultFees(token12D, token18D); - addLiquidityFromRouter(token12D, token18D, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - pair3 = createLBPairDefaultFees(token18D, token24D); - addLiquidityFromRouter(token18D, token24D, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - - taxTokenPair = createLBPairDefaultFees(taxToken, wavax); - addLiquidityFromRouter( - ERC20MockDecimals(address(taxToken)), - ERC20MockDecimals(address(wavax)), - 100e18, - ID_ONE, - 9, - 2, - DEFAULT_BIN_STEP - ); - - pairWavax = createLBPairDefaultFees(token6D, wavax); - addLiquidityFromRouter(token6D, ERC20MockDecimals(address(wavax)), 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - } - - function testSwapExactTokensForTokensMultiplePairsWithV1() public { - if (block.number < 1000) { - console.log("fork mainnet for V1 testing support"); - return; - } - - uint256 amountIn = 1e18; - - token6D.mint(DEV, amountIn); - - token6D.approve(address(router), amountIn); - - IERC20[] memory tokenList; - uint256[] memory pairVersions; - - tokenList = new IERC20[](3); - tokenList[0] = token6D; - tokenList[1] = wavax; - tokenList[2] = usdc; - - pairVersions = new uint256[](2); - pairVersions[0] = DEFAULT_BIN_STEP; - pairVersions[1] = 0; - - router.swapExactTokensForTokens(amountIn, 0, pairVersions, tokenList, DEV, block.timestamp); - - assertGt(usdc.balanceOf(DEV), 0); - - tokenList[0] = usdc; - tokenList[1] = wavax; - tokenList[2] = token6D; - - pairVersions[0] = 0; - pairVersions[1] = DEFAULT_BIN_STEP; - - uint256 balanceBefore = token6D.balanceOf(DEV); - - usdc.approve(address(router), usdc.balanceOf(DEV)); - router.swapExactTokensForTokens(usdc.balanceOf(DEV), 0, pairVersions, tokenList, DEV, block.timestamp); - - assertGt(token6D.balanceOf(DEV) - balanceBefore, 0); - } - - function testSwapTokensForExactTokensMultiplePairsWithV1() public { - if (block.number < 1000) { - console.log("fork mainnet for V1 testing support"); - return; - } - - uint256 amountOut = 1e6; - - token6D.mint(DEV, 100e18); - - token6D.approve(address(router), 100e18); - - IERC20[] memory tokenList; - uint256[] memory pairVersions; - - tokenList = new IERC20[](3); - tokenList[0] = token6D; - tokenList[1] = wavax; - tokenList[2] = usdc; - - pairVersions = new uint256[](2); - pairVersions[0] = DEFAULT_BIN_STEP; - pairVersions[1] = 0; - - router.swapTokensForExactTokens(amountOut, 100e18, pairVersions, tokenList, DEV, block.timestamp); - - assertEq(usdc.balanceOf(DEV), amountOut); - - tokenList[0] = usdc; - tokenList[1] = wavax; - tokenList[2] = token6D; - - pairVersions[0] = 0; - pairVersions[1] = DEFAULT_BIN_STEP; - - uint256 balanceBefore = token6D.balanceOf(DEV); - - usdc.approve(address(router), usdc.balanceOf(DEV)); - router.swapTokensForExactTokens(amountOut, usdc.balanceOf(DEV), pairVersions, tokenList, DEV, block.timestamp); - - assertEq(token6D.balanceOf(DEV) - balanceBefore, amountOut); - - vm.stopPrank(); - } - - function testTaxTokenSwappedOnV1Pairs() public { - if (block.number < 1000) { - console.log("fork mainnet for V1 testing support"); - return; - } - uint256 amountIn = 100e18; - - IJoeFactory factoryv1 = IJoeFactory(JOE_V1_FACTORY_ADDRESS); - //create taxToken-AVAX pair in DEXv1 - address taxPairv11 = factoryv1.createPair(address(taxToken), address(wavax)); - taxToken.mint(taxPairv11, amountIn); - vm.deal(DEV, amountIn); - wavax.deposit{value: amountIn}(); - wavax.transfer(taxPairv11, amountIn); - IJoePair(taxPairv11).mint(DEV); - - //create taxToken-token6D pair in DEXv1 - address taxPairv12 = factoryv1.createPair(address(taxToken), address(token6D)); - taxToken.mint(taxPairv12, amountIn); - token6D.mint(taxPairv12, amountIn); - IJoePair(taxPairv12).mint(DEV); - - token6D.mint(DEV, amountIn); - token6D.approve(address(router), amountIn); - - IERC20[] memory tokenList; - uint256[] memory pairVersions; - - tokenList = new IERC20[](3); - tokenList[0] = token6D; - tokenList[1] = taxToken; - tokenList[2] = wavax; - - pairVersions = new uint256[](2); - pairVersions[0] = 0; - pairVersions[1] = 0; - uint256 amountIn2 = 1e18; - - vm.expectRevert("Joe: K"); - router.swapExactTokensForTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - amountIn2, - 0, - pairVersions, - tokenList, - DEV, - block.timestamp - ); - vm.deal(DEV, amountIn2); - vm.expectRevert("Joe: K"); - router.swapExactTokensForAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn2, - 0, - pairVersions, - tokenList, - DEV, - block.timestamp - ); - - tokenList[0] = wavax; - tokenList[1] = taxToken; - tokenList[2] = token6D; - - vm.deal(DEV, amountIn2); - vm.expectRevert("Joe: K"); - router.swapExactAVAXForTokens{value: amountIn2}(0, pairVersions, tokenList, DEV, block.timestamp); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn2}( - 0, - pairVersions, - tokenList, - DEV, - block.timestamp - ); - } - - function testSwappingOnNotExistingV1PairReverts() public { - IERC20[] memory tokenListAvaxIn; - IERC20[] memory tokenList; - uint256[] memory pairVersions; - - uint256 amountIn2 = 1e18; - vm.deal(DEV, amountIn2); - - tokenList = new IERC20[](3); - tokenList[0] = token6D; - tokenList[1] = token18D; - tokenList[2] = wavax; - - pairVersions = new uint256[](2); - pairVersions[0] = DEFAULT_BIN_STEP; - pairVersions[1] = 0; - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token18D, wavax, pairVersions[1])); - router.swapExactTokensForTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token18D, wavax, pairVersions[1])); - router.swapExactTokensForAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token18D, wavax, pairVersions[1])); - router.swapTokensForExactTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token18D, wavax, pairVersions[1])); - router.swapTokensForExactAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token18D, wavax, pairVersions[1])); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - amountIn2, - 0, - pairVersions, - tokenList, - DEV, - block.timestamp - ); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token18D, wavax, pairVersions[1])); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn2, - 0, - pairVersions, - tokenList, - DEV, - block.timestamp - ); - - tokenListAvaxIn = new IERC20[](3); - tokenListAvaxIn[0] = wavax; - tokenListAvaxIn[1] = token6D; - tokenListAvaxIn[2] = token18D; - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token6D, token18D, pairVersions[1])); - router.swapExactAVAXForTokens{value: amountIn2}(0, pairVersions, tokenListAvaxIn, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token6D, token18D, pairVersions[1])); - router.swapAVAXForExactTokens{value: amountIn2}(0, pairVersions, tokenListAvaxIn, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, token6D, token18D, pairVersions[1])); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn2}( - 0, - pairVersions, - tokenListAvaxIn, - DEV, - block.timestamp - ); - } - - receive() external payable {} -} diff --git a/test/TestHelper.sol b/test/TestHelper.sol deleted file mode 100644 index 2fa1807f..00000000 --- a/test/TestHelper.sol +++ /dev/null @@ -1,340 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import "forge-std/Test.sol"; - -import "src/LBFactory.sol"; -import "src/LBPair.sol"; -import "src/LBRouter.sol"; -import "src/LBQuoter.sol"; -import "src/LBErrors.sol"; -import "src/interfaces/ILBRouter.sol"; -import "src/interfaces/IJoeRouter02.sol"; -import "src/LBToken.sol"; -import "src/libraries/Math512Bits.sol"; -import "src/libraries/Constants.sol"; - -import "test/mocks/WAVAX.sol"; -import "test/mocks/ERC20MockDecimals.sol"; -import "test/mocks/FlashloanBorrower.sol"; -import "test/mocks/ERC20WithTransferTax.sol"; - -abstract contract TestHelper is Test, IERC165 { - using Math512Bits for uint256; - - uint24 internal constant ID_ONE = 2**23; - uint256 internal constant BASIS_POINT_MAX = 10_000; - - uint24 internal constant DEFAULT_MAX_VOLATILITY_ACCUMULATED = 1_777_638; - uint16 internal constant DEFAULT_FILTER_PERIOD = 50; - uint16 internal constant DEFAULT_DECAY_PERIOD = 100; - uint16 internal constant DEFAULT_BIN_STEP = 25; - uint16 internal constant DEFAULT_BASE_FACTOR = 5000; - uint16 internal constant DEFAULT_PROTOCOL_SHARE = 1000; - uint16 internal constant DEFAULT_SAMPLE_LIFETIME = 120; - uint16 internal constant DEFAULT_REDUCTION_FACTOR = 5000; - uint24 internal constant DEFAULT_VARIABLE_FEE_CONTROL = 5000; - - address payable internal immutable DEV = payable(address(this)); - address payable internal constant ALICE = payable(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - address payable internal constant BOB = payable(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); - - address internal constant JOE_V1_FACTORY_ADDRESS = 0x9Ad6C38BE94206cA50bb0d90783181662f0Cfa10; - address internal constant JOE_V1_ROUTER_ADDRESS = 0x60aE616a2155Ee3d9A68541Ba4544862310933d4; - address internal constant WAVAX_AVALANCHE_ADDRESS = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7; - address internal constant USDC_AVALANCHE_ADDRESS = 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E; - - WAVAX internal wavax; - ERC20MockDecimals internal usdc; - ERC20MockDecimals internal usdt; - - ERC20MockDecimals internal token6D; - ERC20MockDecimals internal token10D; - ERC20MockDecimals internal token12D; - ERC20MockDecimals internal token18D; - ERC20MockDecimals internal token24D; - - ERC20WithTransferTax internal taxToken; - - LBFactory internal factory; - LBRouter internal router; - IJoeRouter02 internal routerV1 = IJoeRouter02(JOE_V1_ROUTER_ADDRESS); - LBPair internal pair; - LBPair internal pairWavax; - LBQuoter internal quoter; - - function supportsInterface(bytes4 interfaceId) external view virtual returns (bool) { - return interfaceId == type(ILBToken).interfaceId; - } - - function getPriceFromId(uint24 _id) internal pure returns (uint256 price) { - price = BinHelper.getPriceFromId(_id, DEFAULT_BIN_STEP); - } - - function getIdFromPrice(uint256 _price) internal pure returns (uint24 id) { - id = BinHelper.getIdFromPrice(_price, DEFAULT_BIN_STEP); - } - - function createLBPairDefaultFees(IERC20 _tokenX, IERC20 _tokenY) internal returns (LBPair newPair) { - newPair = createLBPairDefaultFeesFromStartId(_tokenX, _tokenY, ID_ONE); - } - - function setDefaultFactoryPresets(uint16 _binStep) internal { - factory.setPreset( - _binStep, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME - ); - } - - function createLBPairDefaultFeesFromStartId( - IERC20 _tokenX, - IERC20 _tokenY, - uint24 _startId - ) internal returns (LBPair newPair) { - newPair = createLBPairDefaultFeesFromStartIdAndBinStep(_tokenX, _tokenY, _startId, DEFAULT_BIN_STEP); - } - - function createLBPairDefaultFeesFromStartIdAndBinStep( - IERC20 _tokenX, - IERC20 _tokenY, - uint24 _startId, - uint16 _binStep - ) internal returns (LBPair newPair) { - newPair = LBPair(address(factory.createLBPair(_tokenX, _tokenY, _startId, _binStep))); - } - - function addLiquidity( - uint256 _amountYIn, - uint24 _startId, - uint24 _numberBins, - uint24 _gap - ) - internal - returns ( - uint256[] memory _ids, - uint256[] memory _distributionX, - uint256[] memory _distributionY, - uint256 amountXIn - ) - { - (_ids, _distributionX, _distributionY, amountXIn) = spreadLiquidity(_amountYIn, _startId, _numberBins, _gap); - - token6D.mint(address(pair), amountXIn); - token18D.mint(address(pair), _amountYIn); - - pair.mint(_ids, _distributionX, _distributionY, DEV); - } - - function spreadLiquidity( - uint256 _amountYIn, - uint24 _startId, - uint24 _numberBins, - uint24 _gap - ) - internal - pure - returns ( - uint256[] memory _ids, - uint256[] memory _distributionX, - uint256[] memory _distributionY, - uint256 amountXIn - ) - { - if (_numberBins % 2 == 0) { - revert("Pls put an uneven number of bins"); - } - - uint24 spread = _numberBins / 2; - _ids = new uint256[](_numberBins); - - _distributionX = new uint256[](_numberBins); - _distributionY = new uint256[](_numberBins); - uint256 binDistribution = Constants.PRECISION / (spread + 1); - uint256 binLiquidity = _amountYIn / (spread + 1); - - for (uint256 i; i < _numberBins; i++) { - _ids[i] = _startId - spread * (1 + _gap) + i * (1 + _gap); - - if (i <= spread) { - _distributionY[i] = binDistribution; - } - if (i >= spread) { - _distributionX[i] = binDistribution; - amountXIn += binLiquidity > 0 - ? (binLiquidity * Constants.SCALE - 1) / getPriceFromId(uint24(_ids[i])) + 1 - : 0; - } - } - } - - function addLiquidityFromRouter( - ERC20MockDecimals _tokenX, - ERC20MockDecimals _tokenY, - uint256 _amountYIn, - uint24 _startId, - uint24 _numberBins, - uint24 _gap, - uint16 _binStep - ) - internal - returns ( - int256[] memory _deltaIds, - uint256[] memory _distributionX, - uint256[] memory _distributionY, - uint256 amountXIn - ) - { - (_deltaIds, _distributionX, _distributionY, amountXIn) = spreadLiquidityForRouter( - _amountYIn, - _startId, - _numberBins, - _gap - ); - - ILBRouter.LiquidityParameters memory _liquidityParameters = ILBRouter.LiquidityParameters( - _tokenX, - _tokenY, - _binStep, - amountXIn, - _amountYIn, - 0, - 0, - _startId, - 0, - _deltaIds, - _distributionX, - _distributionY, - DEV, - block.timestamp - ); - - if (address(_tokenY) == address(wavax)) { - vm.deal(DEV, _amountYIn); - _tokenX.mint(DEV, amountXIn); - _tokenX.approve(address(router), amountXIn); - router.addLiquidityAVAX{value: _amountYIn}(_liquidityParameters); - } else if (address(_tokenX) == address(wavax)) { - vm.deal(DEV, amountXIn); - _tokenY.mint(DEV, _amountYIn); - _tokenY.approve(address(router), _amountYIn); - router.addLiquidityAVAX{value: amountXIn}(_liquidityParameters); - } else { - _tokenX.approve(address(router), amountXIn); - _tokenX.mint(DEV, amountXIn); - _tokenY.approve(address(router), _amountYIn); - _tokenY.mint(DEV, _amountYIn); - router.addLiquidity(_liquidityParameters); - } - } - - function spreadLiquidityForRouter( - uint256 _amountYIn, - uint24 _startId, - uint24 _numberBins, - uint24 _gap - ) - internal - pure - returns ( - int256[] memory _deltaIds, - uint256[] memory _distributionX, - uint256[] memory _distributionY, - uint256 amountXIn - ) - { - if (_numberBins % 2 == 0) { - revert("Pls put an uneven number of bins"); - } - - uint256 spread = _numberBins / 2; - _deltaIds = new int256[](_numberBins); - - _distributionX = new uint256[](_numberBins); - _distributionY = new uint256[](_numberBins); - uint256 binDistribution = Constants.PRECISION / (spread + 1); - uint256 binLiquidity = _amountYIn / (spread + 1); - - for (uint256 i; i < _numberBins; i++) { - _deltaIds[i] = int256(i * (1 + _gap)) - int256(spread * (1 + _gap)); - - if (i <= spread) { - _distributionY[i] = binDistribution; - } - if (i >= spread) { - _distributionX[i] = binDistribution; - amountXIn += - (binLiquidity * Constants.SCALE) / - getPriceFromId(uint24(int24(_startId) + int24(_deltaIds[i]))); - } - } - } - - function prepareLiquidityParameters( - ERC20MockDecimals _tokenX, - ERC20MockDecimals _tokenY, - uint256 _amountYIn, - uint24 _startId, - uint24 _numberBins, - uint24 _gap, - uint16 _binStep - ) internal returns (ILBRouter.LiquidityParameters memory) { - int256[] memory _deltaIds; - uint256[] memory _distributionX; - uint256[] memory _distributionY; - uint256 amountXIn; - (_deltaIds, _distributionX, _distributionY, amountXIn) = spreadLiquidityForRouter( - _amountYIn, - _startId, - _numberBins, - _gap - ); - - _tokenX.mint(DEV, amountXIn); - _tokenX.approve(address(router), amountXIn); - - if (address(_tokenY) == address(wavax)) { - vm.deal(DEV, _amountYIn); - } else { - _tokenY.approve(address(router), _amountYIn); - _tokenY.mint(DEV, _amountYIn); - } - - return - ILBRouter.LiquidityParameters( - _tokenX, - _tokenY, - _binStep, - amountXIn, - _amountYIn, - 0, //possible slippage = max - 0, //possible slippage = max - ID_ONE, - ID_ONE, //possible slippage = max - _deltaIds, - _distributionX, - _distributionY, - DEV, - block.timestamp - ); - } - - function addAllAssetsToQuoteWhitelist(LBFactory _factory) internal { - if (address(wavax) != address(0)) _factory.addQuoteAsset(wavax); - if (address(usdc) != address(0)) _factory.addQuoteAsset(usdc); - if (address(usdt) != address(0)) _factory.addQuoteAsset(usdt); - if (address(taxToken) != address(0)) _factory.addQuoteAsset(taxToken); - if (address(token6D) != address(0)) _factory.addQuoteAsset(token6D); - if (address(token10D) != address(0)) _factory.addQuoteAsset(token10D); - if (address(token12D) != address(0)) _factory.addQuoteAsset(token12D); - if (address(token18D) != address(0)) _factory.addQuoteAsset(token18D); - if (address(token24D) != address(0)) _factory.addQuoteAsset(token24D); - } -} diff --git a/test/helpers/TestHelper.sol b/test/helpers/TestHelper.sol new file mode 100644 index 00000000..36647225 --- /dev/null +++ b/test/helpers/TestHelper.sol @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "src/LBFactory.sol"; +import "src/LBPair.sol"; +import "src/LBRouter.sol"; +import "src/LBQuoter.sol"; +import "src/LBErrors.sol"; +import "src/interfaces/ILBRouter.sol"; +import "src/interfaces/IJoeRouter02.sol"; +import "src/LBToken.sol"; +import "src/libraries/Math512Bits.sol"; +import "src/libraries/Constants.sol"; + +import "test/mocks/WAVAX.sol"; +import "test/mocks/ERC20.sol"; +import "test/mocks/FlashloanBorrower.sol"; +import "test/mocks/ERC20TransferTax.sol"; + +abstract contract TestHelper is Test, IERC165 { + using Math512Bits for uint256; + + uint24 internal constant ID_ONE = 2 ** 23; + uint256 internal constant BASIS_POINT_MAX = 10_000; + + // Avalanche market config for 10bps + uint16 internal constant DEFAULT_BIN_STEP = 10; + uint16 internal constant DEFAULT_BASE_FACTOR = 1000; + uint16 internal constant DEFAULT_FILTER_PERIOD = 30; + uint16 internal constant DEFAULT_DECAY_PERIOD = 600; + uint16 internal constant DEFAULT_REDUCTION_FACTOR = 5_000; + uint24 internal constant DEFAULT_VARIABLE_FEE_CONTROL = 40_000; + uint16 internal constant DEFAULT_PROTOCOL_SHARE = 1_000; + uint24 internal constant DEFAULT_MAX_VOLATILITY_ACCUMULATED = 350_000; + uint16 internal constant DEFAULT_SAMPLE_LIFETIME = 120; + uint256 internal constant DEFAULT_FLASHLOAN_FEE = 8e14; + + address payable immutable DEV = payable(address(this)); + address payable immutable ALICE = payable(makeAddr("alice")); + address payable immutable BOB = payable(makeAddr("bob")); + + // Wrapped Native + WAVAX internal wavax; + + // 6 decimals + ERC20Mock internal usdc; + ERC20Mock internal usdt; + + // 8 decimals + ERC20Mock internal wbtc; + + // 18 decimals + ERC20Mock internal link; + ERC20Mock internal bnb; + ERC20Mock internal weth; + + // Tax tokens (18 decimals) + ERC20TransferTaxMock internal taxToken; + + LBFactory internal factory; + LBRouter internal router; + LBPair internal pair; + LBPair internal pairWavax; + LBQuoter internal quoter; + + function setUp() public virtual { + // Create mocks + wavax = new WAVAX(); + usdc = new ERC20Mock(6); + usdt = new ERC20Mock(6); + wbtc = new ERC20Mock(8); + weth = new ERC20Mock(18); + link = new ERC20Mock(18); + bnb = new ERC20Mock(18); + taxToken = new ERC20TransferTaxMock(); + + // Label mocks + vm.label(address(wavax), "wavax"); + vm.label(address(usdc), "usdc"); + vm.label(address(usdt), "usdt"); + vm.label(address(wbtc), "wbtc"); + vm.label(address(weth), "weth"); + vm.label(address(link), "link"); + vm.label(address(bnb), "bnb"); + vm.label(address(taxToken), "taxToken"); + + // Create factory + factory = new LBFactory(DEV, DEFAULT_FLASHLOAN_FEE); + ILBPair LBPairImplementation = new LBPair(factory); + + // Setup factory + factory.setLBPairImplementation(address(LBPairImplementation)); + addAllAssetsToQuoteWhitelist(); + setDefaultFactoryPresets(DEFAULT_BIN_STEP); + + // Create router + router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(0))); + + // Label deployed contracts + vm.label(address(factory), "factory"); + vm.label(address(router), "router"); + vm.label(address(LBPairImplementation), "LBPairImplementation"); + } + + function supportsInterface(bytes4 interfaceId) external view virtual returns (bool) { + return interfaceId == type(ILBToken).interfaceId; + } + + function getPriceFromId(uint24 id) internal pure returns (uint256 price) { + price = BinHelper.getPriceFromId(id, DEFAULT_BIN_STEP); + } + + function getIdFromPrice(uint256 price) internal pure returns (uint24 id) { + id = BinHelper.getIdFromPrice(price, DEFAULT_BIN_STEP); + } + + function createLBPair(IERC20 tokenX, IERC20 tokenY) internal returns (LBPair newPair) { + newPair = createLBPairFromStartId(tokenX, tokenY, ID_ONE); + } + + function setDefaultFactoryPresets(uint16 binStep) internal { + factory.setPreset( + binStep, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATED, + DEFAULT_SAMPLE_LIFETIME + ); + } + + function createLBPairFromStartId(IERC20 tokenX, IERC20 tokenY, uint24 startId) internal returns (LBPair newPair) { + newPair = createLBPairFromStartIdAndBinStep(tokenX, tokenY, startId, DEFAULT_BIN_STEP); + } + + function createLBPairFromStartIdAndBinStep(IERC20 tokenX, IERC20 tokenY, uint24 startId, uint16 binStep) + internal + returns (LBPair newPair) + { + newPair = LBPair(address(factory.createLBPair(tokenX, tokenY, startId, binStep))); + } + + function convertRelativeIdsToAbsolute(int256[] memory relativeIds, uint24 startId) + internal + pure + returns (uint256[] memory absoluteIds) + { + absoluteIds = new uint256[](relativeIds.length); + for (uint256 i = 0; i < relativeIds.length; i++) { + int256 id = int256(uint256(startId)) + relativeIds[i]; + require(id >= 0, "Id conversion: id must be positive"); + absoluteIds[i] = uint256(id); + } + } + + function convertAbsoluteIdsToRelative(uint256[] memory absoluteIds, uint24 startId) + internal + pure + returns (int256[] memory relativeIds) + { + relativeIds = new int256[](absoluteIds.length); + for (uint256 i = 0; i < absoluteIds.length; i++) { + relativeIds[i] = int256(absoluteIds[i]) - int256(uint256(startId)); + } + } + + function addLiquidityAndReturnAbsoluteIds( + ERC20Mock tokenX, + ERC20Mock tokenY, + uint256 amountYIn, + uint24 startId, + uint24 numberBins, + uint24 gap + ) + internal + returns ( + uint256[] memory ids, + uint256[] memory distributionX, + uint256[] memory distributionY, + uint256 amountXIn + ) + { + (ids, distributionX, distributionY, amountXIn) = + addLiquidity(tokenX, tokenY, amountYIn, startId, numberBins, gap); + } + + function addLiquidityAndReturnRelativeIds( + ERC20Mock tokenX, + ERC20Mock tokenY, + uint256 amountYIn, + uint24 startId, + uint24 numberBins, + uint24 gap + ) + internal + returns (int256[] memory ids, uint256[] memory distributionX, uint256[] memory distributionY, uint256 amountXIn) + { + uint256[] memory absoluteIds; + (absoluteIds, distributionX, distributionY, amountXIn) = + addLiquidity(tokenX, tokenY, amountYIn, startId, numberBins, gap); + ids = convertAbsoluteIdsToRelative(absoluteIds, startId); + } + + function addLiquidity( + ERC20Mock tokenX, + ERC20Mock tokenY, + uint256 amountYIn, + uint24 startId, + uint24 numberBins, + uint24 gap + ) + internal + returns ( + uint256[] memory ids, + uint256[] memory distributionX, + uint256[] memory distributionY, + uint256 amountXIn + ) + { + (ids, distributionX, distributionY, amountXIn) = spreadLiquidity(amountYIn, startId, numberBins, gap); + + tokenX.mint(address(pair), amountXIn); + tokenY.mint(address(pair), amountYIn); + + pair.mint(ids, distributionX, distributionY, DEV); + } + + function spreadLiquidity(uint256 amountYIn, uint24 startId, uint24 numberBins, uint24 gap) + internal + pure + returns ( + uint256[] memory ids, + uint256[] memory distributionX, + uint256[] memory distributionY, + uint256 amountXIn + ) + { + if (numberBins % 2 == 0) { + revert("Pls put an uneven number of bins"); + } + + uint24 spread = numberBins / 2; + ids = new uint256[](numberBins); + + distributionX = new uint256[](numberBins); + distributionY = new uint256[](numberBins); + uint256 binDistribution = Constants.PRECISION / (spread + 1); + uint256 binLiquidity = amountYIn / (spread + 1); + + for (uint256 i; i < numberBins; i++) { + ids[i] = startId - spread * (1 + gap) + i * (1 + gap); + + if (i <= spread) { + distributionY[i] = binDistribution; + } + if (i >= spread) { + distributionX[i] = binDistribution; + amountXIn += + binLiquidity > 0 ? (binLiquidity * Constants.SCALE - 1) / getPriceFromId(uint24(ids[i])) + 1 : 0; + } + } + } + + function addAllAssetsToQuoteWhitelist() internal { + if (address(wavax) != address(0)) factory.addQuoteAsset(wavax); + if (address(usdc) != address(0)) factory.addQuoteAsset(usdc); + if (address(usdt) != address(0)) factory.addQuoteAsset(usdt); + if (address(wbtc) != address(0)) factory.addQuoteAsset(wbtc); + if (address(weth) != address(0)) factory.addQuoteAsset(weth); + if (address(link) != address(0)) factory.addQuoteAsset(link); + if (address(bnb) != address(0)) factory.addQuoteAsset(bnb); + if (address(taxToken) != address(0)) factory.addQuoteAsset(taxToken); + } +} diff --git a/test/integration/Addresses.sol b/test/integration/Addresses.sol new file mode 100644 index 00000000..b3027497 --- /dev/null +++ b/test/integration/Addresses.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.10; + +library Addresses { + address internal constant JOE_V1_FACTORY_ADDRESS = 0x9Ad6C38BE94206cA50bb0d90783181662f0Cfa10; + address internal constant JOE_V1_ROUTER_ADDRESS = 0x60aE616a2155Ee3d9A68541Ba4544862310933d4; + address internal constant WAVAX_AVALANCHE_ADDRESS = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7; + address internal constant USDC_AVALANCHE_ADDRESS = 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E; +} diff --git a/test/mocks/ERC20.sol b/test/mocks/ERC20.sol index 4cabd37a..a4c1cf7a 100644 --- a/test/mocks/ERC20.sol +++ b/test/mocks/ERC20.sol @@ -2,21 +2,18 @@ pragma solidity 0.8.10; -import "openzeppelin/token/ERC20/ERC20.sol"; +import {ERC20} from "openzeppelin/token/ERC20/ERC20.sol"; +import {Ownable} from "openzeppelin/access/Ownable.sol"; /// @title ERC20Mock /// @author Trader Joe /// @dev ONLY FOR TESTS -contract ERC20Mock is ERC20 { - uint8 private decimalsOverride; +contract ERC20Mock is ERC20, Ownable { + uint8 private immutable decimalsOverride; /// @dev Constructor /// @param _decimals The number of decimals for this token - constructor( - string memory _name, - string memory _symbol, - uint8 _decimals - ) ERC20(_name, _symbol) { + constructor(uint8 _decimals) ERC20("ERC20 Mock", "ERC20Mock") { decimalsOverride = _decimals; } @@ -26,10 +23,10 @@ contract ERC20Mock is ERC20 { return decimalsOverride; } - /// @dev Mint _amount to _to. + /// @dev Mint _amount to _to, only callable by the owner /// @param _to The address that will receive the mint /// @param _amount The amount to be minted - function mint(address _to, uint256 _amount) external { + function mint(address _to, uint256 _amount) external onlyOwner { _mint(_to, _amount); } } diff --git a/test/mocks/ERC20MockDecimals.sol b/test/mocks/ERC20MockDecimals.sol deleted file mode 100644 index 95d1797f..00000000 --- a/test/mocks/ERC20MockDecimals.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import "openzeppelin/token/ERC20/ERC20.sol"; - -/// @title ERC20MockDecimals -/// @author Trader Joe -/// @dev ONLY FOR TESTS -contract ERC20MockDecimals is ERC20 { - uint8 private decimalsOverride; - - /// @dev Constructor - /// @param _decimals The number of decimals for this token - constructor(uint8 _decimals) ERC20("ERC20Mock", "ERC20M") { - decimalsOverride = _decimals; - } - - /// @dev Define the number of decimals - /// @return The number of decimals - function decimals() public view override returns (uint8) { - return decimalsOverride; - } - - /// @dev Mint _amount to _to. - /// @param _to The address that will receive the mint - /// @param _amount The amount to be minted - function mint(address _to, uint256 _amount) external { - _mint(_to, _amount); - } -} diff --git a/test/mocks/ERC20MockDecimalsOwnable.sol b/test/mocks/ERC20MockDecimalsOwnable.sol deleted file mode 100644 index 7ca3b95d..00000000 --- a/test/mocks/ERC20MockDecimalsOwnable.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import "openzeppelin/token/ERC20/ERC20.sol"; -import "openzeppelin/access/Ownable.sol"; - -/// @title ERC20MockDecimalsOwnable -/// @author Trader Joe -/// @dev ONLY FOR TESTS -contract ERC20MockDecimalsOwnable is ERC20, Ownable { - uint8 private immutable decimalsOverride; - - /// @dev Constructor - /// @param _name Name of the token - /// @param _symbol Symbol of the token - /// @param _decimals The number of decimals for this token - constructor( - string memory _name, - string memory _symbol, - uint8 _decimals - ) ERC20(_name, _symbol) { - decimalsOverride = _decimals; - } - - /// @dev Define the number of decimals - /// @return The number of decimals - function decimals() public view override returns (uint8) { - return decimalsOverride; - } - - /// @dev Mint _amount to _to, only callable by the owner - /// @param _to The address that will receive the mint - /// @param _amount The amount to be minted - function mint(address _to, uint256 _amount) external onlyOwner { - _mint(_to, _amount); - } -} diff --git a/test/mocks/ERC20TransferTax.sol b/test/mocks/ERC20TransferTax.sol new file mode 100644 index 00000000..04305c62 --- /dev/null +++ b/test/mocks/ERC20TransferTax.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import {ERC20Mock} from "./ERC20.sol"; + +/// @title ERC20MockTransferTax +/// @author Trader Joe +/// @dev ONLY FOR TESTS +contract ERC20TransferTaxMock is ERC20Mock { + /// @dev Constructor + constructor() ERC20Mock(18) {} + + function _transfer(address from, address to, uint256 amount) internal override { + uint256 tax = amount / 2; + _burn(from, tax); + super._transfer(from, to, amount - tax); + } +} diff --git a/test/mocks/ERC20WithTransferTax.sol b/test/mocks/ERC20WithTransferTax.sol deleted file mode 100644 index 269616d7..00000000 --- a/test/mocks/ERC20WithTransferTax.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import "openzeppelin/token/ERC20/ERC20.sol"; - -/// @title ERC20MockDecimals -/// @author Trader Joe -/// @dev ONLY FOR TESTS -contract ERC20WithTransferTax is ERC20 { - /// @dev Constructor - constructor() ERC20("ERC20Mock", "ERC20M") {} - - /// @dev Mint _amount to _to. - /// @param _to The address that will receive the mint - /// @param _amount The amount to be minted - function mint(address _to, uint256 _amount) external { - _mint(_to, _amount); - } - - function transferFrom( - address from, - address to, - uint256 amount - ) public override returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, amount); - uint256 tax = amount / 2; - _transfer(from, to, amount - tax); - _burn(from, tax); - return true; - } - - function transfer(address to, uint256 amount) public override returns (bool) { - uint256 tax = amount / 2; - _transfer(msg.sender, to, amount - tax); - _burn(msg.sender, tax); - return true; - } -} diff --git a/test/mocks/Faucet.sol b/test/mocks/Faucet.sol index 0131e1af..86e983b4 100644 --- a/test/mocks/Faucet.sol +++ b/test/mocks/Faucet.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.10; -import "../../src/libraries/PendingOwnable.sol"; -import "../../src/libraries/TokenHelper.sol"; +import {PendingOwnable} from "src/libraries/PendingOwnable.sol"; +import {TokenHelper, IERC20} from "src/libraries/TokenHelper.sol"; /// @title Faucet contract /// @author Trader Joe @@ -138,11 +138,7 @@ contract Faucet is PendingOwnable { /// @param _token The address of the token to withdraw /// @param _to The recipient address /// @param _amount The token amount to send - function withdrawToken( - IERC20 _token, - address _to, - uint256 _amount - ) external onlyOwner { + function withdrawToken(IERC20 _token, address _to, uint256 _amount) external onlyOwner { if (address(_token) == address(0)) _sendAvax(_to, _amount); else _token.safeTransfer(_to, _amount); } @@ -174,8 +170,9 @@ contract Faucet is PendingOwnable { for (uint256 i = 1; i < len; ++i) { token = faucetTokens[i]; - if (token.amountPerRequest > 0 && token.ERC20.balanceOf(address(this)) >= token.amountPerRequest) + if (token.amountPerRequest > 0 && token.ERC20.balanceOf(address(this)) >= token.amountPerRequest) { token.ERC20.safeTransfer(_to, token.amountPerRequest); + } } } @@ -213,7 +210,7 @@ contract Faucet is PendingOwnable { /// @param _to The recipient address /// @param _amount The AVAX amount to send function _sendAvax(address _to, uint256 _amount) private { - (bool success, ) = _to.call{value: _amount}(""); + (bool success,) = _to.call{value: _amount}(""); require(success, "AVAX transfer failed"); } } diff --git a/test/mocks/FlashloanBorrower.sol b/test/mocks/FlashloanBorrower.sol index d5be67ad..64a2286a 100644 --- a/test/mocks/FlashloanBorrower.sol +++ b/test/mocks/FlashloanBorrower.sol @@ -2,11 +2,11 @@ pragma solidity 0.8.10; -import "openzeppelin/interfaces/IERC20.sol"; +import {IERC20} from "openzeppelin/interfaces/IERC20.sol"; -import "src/LBPair.sol"; -import "src/interfaces/ILBFlashLoanCallback.sol"; -import "src/libraries/Constants.sol"; +import {ILBPair} from "src/LBPair.sol"; +import {ILBFlashLoanCallback} from "src/interfaces/ILBFlashLoanCallback.sol"; +import {Constants} from "src/libraries/Constants.sol"; error FlashBorrower__UntrustedLender(); error FlashBorrower__UntrustedLoanInitiator(); @@ -33,13 +33,11 @@ contract FlashBorrower is ILBFlashLoanCallback { (_tokenX, _tokenY) = (lender_.tokenX(), lender_.tokenY()); } - function LBFlashLoanCallback( - address sender, - IERC20 token, - uint256 amount, - uint256 fee, - bytes calldata data - ) external override returns (bytes32) { + function LBFlashLoanCallback(address, IERC20 token, uint256 amount, uint256 fee, bytes calldata data) + external + override + returns (bytes32) + { if (msg.sender != address(_lender)) { revert FlashBorrower__UntrustedLender(); } diff --git a/test/mocks/WAVAX.sol b/test/mocks/WAVAX.sol index 0ff59a0b..3d1b1a22 100644 --- a/test/mocks/WAVAX.sol +++ b/test/mocks/WAVAX.sol @@ -2,17 +2,10 @@ pragma solidity 0.8.10; -import "openzeppelin/token/ERC20/ERC20.sol"; +import {ERC20} from "openzeppelin/token/ERC20/ERC20.sol"; -/// @title WAVAX -/// @author Trader Joe -/// @dev ONLY FOR TESTS contract WAVAX is ERC20 { - /// @dev Constructor - constructor() ERC20("Wrapped Avax", "WAVAX") { - bool _shh; - _shh; - } + constructor() ERC20("Wrapped Avax", "WAVAX") {} function deposit() external payable { _mint(msg.sender, msg.value); @@ -20,7 +13,7 @@ contract WAVAX is ERC20 { function withdraw(uint256 _amount) external { _burn(msg.sender, _amount); - (bool success, ) = msg.sender.call{value: _amount}(""); + (bool success,) = msg.sender.call{value: _amount}(""); if (!success) { revert("Withdraw failed"); diff --git a/test/LBFactory.MultiPools.t.sol b/test_old/LBFactory.MultiPools.t.sol similarity index 84% rename from test/LBFactory.MultiPools.t.sol rename to test_old/LBFactory.MultiPools.t.sol index 6e894f09..e9a0265a 100644 --- a/test/LBFactory.MultiPools.t.sol +++ b/test_old/LBFactory.MultiPools.t.sol @@ -2,27 +2,24 @@ pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "test/helpers/TestHelper.sol"; contract LiquidityBinFactoryTestM is TestHelper { LBPair internal pair0; LBPair internal pair1; LBPair internal pair2; - function setUp() public { - token6D = new ERC20MockDecimals(6); - token18D = new ERC20MockDecimals(18); - + function setUp() public override { factory = new LBFactory(DEV, 8e14); addAllAssetsToQuoteWhitelist(factory); ILBPair _LBPairImplementation = new LBPair(factory); factory.setLBPairImplementation(address(_LBPairImplementation)); - router = new LBRouter(factory, IJoeFactory(JOE_V1_FACTORY_ADDRESS), IWAVAX(address(wavax))); + router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(wavax))); setDefaultFactoryPresets(DEFAULT_BIN_STEP); - pair0 = createLBPairDefaultFeesFromStartIdAndBinStep(token6D, token18D, ID_ONE, DEFAULT_BIN_STEP); + pair0 = createLBPairDefaultFeesFromStartIdAndBinStep(usdc, weth, ID_ONE, DEFAULT_BIN_STEP); factory.setPreset( 75, DEFAULT_BASE_FACTOR, @@ -34,7 +31,7 @@ contract LiquidityBinFactoryTestM is TestHelper { DEFAULT_MAX_VOLATILITY_ACCUMULATED, DEFAULT_SAMPLE_LIFETIME ); - pair1 = createLBPairDefaultFeesFromStartIdAndBinStep(token6D, token18D, ID_ONE, 75); + pair1 = createLBPairDefaultFeesFromStartIdAndBinStep(usdc, weth, ID_ONE, 75); factory.setPreset( 98, DEFAULT_BASE_FACTOR, @@ -46,7 +43,7 @@ contract LiquidityBinFactoryTestM is TestHelper { DEFAULT_MAX_VOLATILITY_ACCUMULATED, DEFAULT_SAMPLE_LIFETIME ); - pair2 = createLBPairDefaultFeesFromStartIdAndBinStep(token6D, token18D, ID_ONE, 98); + pair2 = createLBPairDefaultFeesFromStartIdAndBinStep(usdc, weth, ID_ONE, 98); } function testSetPresets() public { @@ -133,7 +130,7 @@ contract LiquidityBinFactoryTestM is TestHelper { } function testAvailableBinSteps() public { - ILBFactory.LBPairInformation[] memory LBPairBinSteps = factory.getAllLBPairs(token6D, token18D); + ILBFactory.LBPairInformation[] memory LBPairBinSteps = factory.getAllLBPairs(usdc, weth); assertEq(LBPairBinSteps.length, 3); assertEq(LBPairBinSteps[0].binStep, DEFAULT_BIN_STEP); assertEq(LBPairBinSteps[1].binStep, 75); @@ -142,7 +139,7 @@ contract LiquidityBinFactoryTestM is TestHelper { assertEq(LBPairBinSteps[1].createdByOwner, true); assertEq(LBPairBinSteps[2].createdByOwner, true); - ILBFactory.LBPairInformation[] memory LBPairBinStepsReversed = factory.getAllLBPairs(token18D, token6D); + ILBFactory.LBPairInformation[] memory LBPairBinStepsReversed = factory.getAllLBPairs(weth, usdc); assertEq(LBPairBinStepsReversed.length, 3); assertEq(LBPairBinStepsReversed[0].binStep, DEFAULT_BIN_STEP); assertEq(LBPairBinStepsReversed[1].binStep, 75); @@ -151,30 +148,24 @@ contract LiquidityBinFactoryTestM is TestHelper { factory.removePreset(75); factory.removePreset(98); - ILBFactory.LBPairInformation[] memory LBPairBinStepsAfterPresetRemoval = factory.getAllLBPairs( - token6D, - token18D - ); + ILBFactory.LBPairInformation[] memory LBPairBinStepsAfterPresetRemoval = factory.getAllLBPairs(usdc, weth); assertEq(LBPairBinStepsAfterPresetRemoval.length, 3); assertEq(LBPairBinStepsAfterPresetRemoval[0].binStep, DEFAULT_BIN_STEP); assertEq(LBPairBinStepsAfterPresetRemoval[1].binStep, 75); assertEq(LBPairBinStepsAfterPresetRemoval[2].binStep, 98); - factory.setLBPairIgnored(token6D, token18D, DEFAULT_BIN_STEP, true); - factory.setLBPairIgnored(token18D, token6D, 98, true); + factory.setLBPairIgnored(usdc, weth, DEFAULT_BIN_STEP, true); + factory.setLBPairIgnored(weth, usdc, 98, true); - ILBFactory.LBPairInformation[] memory LBPairBinStepsAfterIgnored = factory.getAllLBPairs(token6D, token18D); + ILBFactory.LBPairInformation[] memory LBPairBinStepsAfterIgnored = factory.getAllLBPairs(usdc, weth); assertEq(LBPairBinStepsAfterIgnored.length, 3); assertEq(LBPairBinStepsAfterIgnored[0].ignoredForRouting, true); assertEq(LBPairBinStepsAfterIgnored[1].ignoredForRouting, false); assertEq(LBPairBinStepsAfterIgnored[2].ignoredForRouting, true); - factory.setLBPairIgnored(token6D, token18D, DEFAULT_BIN_STEP, false); + factory.setLBPairIgnored(usdc, weth, DEFAULT_BIN_STEP, false); - ILBFactory.LBPairInformation[] memory LBPairBinStepsAfterRemovalOfIgnored = factory.getAllLBPairs( - token6D, - token18D - ); + ILBFactory.LBPairInformation[] memory LBPairBinStepsAfterRemovalOfIgnored = factory.getAllLBPairs(usdc, weth); assertEq(LBPairBinStepsAfterRemovalOfIgnored.length, 3); assertEq(LBPairBinStepsAfterRemovalOfIgnored[0].ignoredForRouting, false); assertEq(LBPairBinStepsAfterRemovalOfIgnored[1].ignoredForRouting, false); diff --git a/test/LBFactory.t.sol b/test_old/LBFactory.t.sol similarity index 75% rename from test/LBFactory.t.sol rename to test_old/LBFactory.t.sol index d0208a4c..c98fff6c 100644 --- a/test/LBFactory.t.sol +++ b/test_old/LBFactory.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "test/helpers/TestHelper.sol"; contract LiquidityBinFactoryTest is TestHelper { event QuoteAssetRemoved(IERC20 indexed _quoteAsset); @@ -16,10 +16,10 @@ contract LiquidityBinFactoryTest is TestHelper { bool ignoredForRouting; } - function setUp() public { - token6D = new ERC20MockDecimals(6); - token12D = new ERC20MockDecimals(12); - token18D = new ERC20MockDecimals(18); + function setUp() public override { + usdc = new ERC20Mock(6); + wbtc = new ERC20Mock(12); + weth = new ERC20Mock(18); wavax = new WAVAX(); factory = new LBFactory(DEV, 8e14); ILBPair _LBPairImplementation = new LBPair(factory); @@ -41,7 +41,7 @@ contract LiquidityBinFactoryTest is TestHelper { LBFactory anotherFactory = new LBFactory(DEV, 7e14); vm.expectRevert(LBFactory__ImplementationNotSet.selector); - anotherFactory.createLBPair(token6D, token18D, ID_ONE, DEFAULT_BIN_STEP); + anotherFactory.createLBPair(usdc, weth, ID_ONE, DEFAULT_BIN_STEP); ILBPair _LBPairImplementationAnotherFactory = new LBPair(anotherFactory); vm.expectRevert( @@ -56,12 +56,12 @@ contract LiquidityBinFactoryTest is TestHelper { } function testgetAllLBPairs() public { - assertEq(factory.getAllLBPairs(token6D, token18D).length, 0); - ILBPair pair25 = createLBPairDefaultFees(token6D, token18D); - assertEq(factory.getAllLBPairs(token6D, token18D).length, 1); + assertEq(factory.getAllLBPairs(usdc, weth).length, 0); + ILBPair pair25 = createLBPairDefaultFees(usdc, weth); + assertEq(factory.getAllLBPairs(usdc, weth).length, 1); setDefaultFactoryPresets(1); - ILBPair pair1 = factory.createLBPair(token6D, token18D, ID_ONE, 1); - assertEq(factory.getAllLBPairs(token6D, token18D).length, 2); + ILBPair pair1 = factory.createLBPair(usdc, weth, ID_ONE, 1); + assertEq(factory.getAllLBPairs(usdc, weth).length, 2); factory.setPreset( 50, @@ -74,13 +74,13 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_MAX_VOLATILITY_ACCUMULATED, DEFAULT_SAMPLE_LIFETIME ); - router = new LBRouter(factory, IJoeFactory(JOE_V1_FACTORY_ADDRESS), IWAVAX(WAVAX_AVALANCHE_ADDRESS)); + router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(0))); factory.setFactoryLockedState(false); - ILBPair pair50 = router.createLBPair(token6D, token18D, ID_ONE, 50); - factory.setLBPairIgnored(token6D, token18D, 50, true); - assertEq(factory.getAllLBPairs(token6D, token18D).length, 3); + ILBPair pair50 = router.createLBPair(usdc, weth, ID_ONE, 50); + factory.setLBPairIgnored(usdc, weth, 50, true); + assertEq(factory.getAllLBPairs(usdc, weth).length, 3); - ILBFactory.LBPairInformation[] memory LBPairsAvailable = factory.getAllLBPairs(token6D, token18D); + ILBFactory.LBPairInformation[] memory LBPairsAvailable = factory.getAllLBPairs(usdc, weth); assertEq(LBPairsAvailable[0].binStep, 1); assertEq(address(LBPairsAvailable[0].LBPair), address(pair1)); @@ -99,14 +99,14 @@ contract LiquidityBinFactoryTest is TestHelper { } function testCreateLBPair() public { - ILBPair pair = createLBPairDefaultFees(token6D, token12D); + ILBPair pair = createLBPairDefaultFees(usdc, wbtc); assertEq(factory.getNumberOfLBPairs(), 1); - assertEq(address(factory.getLBPairInformation(token6D, token12D, DEFAULT_BIN_STEP).LBPair), address(pair)); + assertEq(address(factory.getLBPairInformation(usdc, wbtc, DEFAULT_BIN_STEP).LBPair), address(pair)); assertEq(address(pair.factory()), address(factory)); - assertEq(address(pair.tokenX()), address(token6D)); - assertEq(address(pair.tokenY()), address(token12D)); + assertEq(address(pair.tokenX()), address(usdc)); + assertEq(address(pair.tokenY()), address(wbtc)); FeeHelper.FeeParameters memory feeParameters = pair.feeParameters(); assertEq(feeParameters.volatilityAccumulated, 0); @@ -141,16 +141,16 @@ contract LiquidityBinFactoryTest is TestHelper { function testFactoryLockedReverts() public { vm.prank(ALICE); vm.expectRevert(abi.encodeWithSelector(LBFactory__FunctionIsLockedForUsers.selector, ALICE)); - createLBPairDefaultFees(token6D, token12D); + createLBPairDefaultFees(usdc, wbtc); } function testCreatePairWhenFactoryIsUnlocked() public { factory.setFactoryLockedState(false); vm.prank(ALICE); - createLBPairDefaultFees(token6D, token12D); + createLBPairDefaultFees(usdc, wbtc); - ILBFactory.LBPairInformation[] memory LBPairBinSteps = factory.getAllLBPairs(token6D, token12D); + ILBFactory.LBPairInformation[] memory LBPairBinSteps = factory.getAllLBPairs(usdc, wbtc); assertEq(LBPairBinSteps.length, 1); assertEq(LBPairBinSteps[0].binStep, DEFAULT_BIN_STEP); assertEq(LBPairBinSteps[0].ignoredForRouting, false); @@ -158,25 +158,23 @@ contract LiquidityBinFactoryTest is TestHelper { } function testForIdenticalAddressesReverts() public { - vm.expectRevert(abi.encodeWithSelector(LBFactory__IdenticalAddresses.selector, token6D)); - factory.createLBPair(token6D, token6D, ID_ONE, DEFAULT_BIN_STEP); + vm.expectRevert(abi.encodeWithSelector(LBFactory__IdenticalAddresses.selector, usdc)); + factory.createLBPair(usdc, usdc, ID_ONE, DEFAULT_BIN_STEP); } function testForZeroAddressPairReverts() public { factory.addQuoteAsset(IERC20(address(0))); vm.expectRevert(LBFactory__AddressZero.selector); - factory.createLBPair(token6D, IERC20(address(0)), ID_ONE, DEFAULT_BIN_STEP); + factory.createLBPair(usdc, IERC20(address(0)), ID_ONE, DEFAULT_BIN_STEP); vm.expectRevert(LBFactory__AddressZero.selector); - factory.createLBPair(IERC20(address(0)), token6D, ID_ONE, DEFAULT_BIN_STEP); + factory.createLBPair(IERC20(address(0)), usdc, ID_ONE, DEFAULT_BIN_STEP); } function testIfPairAlreadyExistsReverts() public { - createLBPairDefaultFees(token6D, token12D); - vm.expectRevert( - abi.encodeWithSelector(LBFactory__LBPairAlreadyExists.selector, token6D, token12D, DEFAULT_BIN_STEP) - ); - createLBPairDefaultFees(token6D, token12D); + createLBPairDefaultFees(usdc, wbtc); + vm.expectRevert(abi.encodeWithSelector(LBFactory__LBPairAlreadyExists.selector, usdc, wbtc, DEFAULT_BIN_STEP)); + createLBPairDefaultFees(usdc, wbtc); } function testForInvalidBinStepOverflowReverts() public { @@ -227,11 +225,11 @@ contract LiquidityBinFactoryTest is TestHelper { } function testSetFeesParametersOnPair() public { - ILBPair pair = createLBPairDefaultFees(token6D, token12D); + ILBPair pair = createLBPairDefaultFees(usdc, wbtc); factory.setFeesParametersOnPair( - token6D, - token12D, + usdc, + wbtc, DEFAULT_BIN_STEP, DEFAULT_BASE_FACTOR - 1, DEFAULT_FILTER_PERIOD - 1, @@ -256,12 +254,12 @@ contract LiquidityBinFactoryTest is TestHelper { } function testSetFeesParametersOnPairReverts() public { - createLBPairDefaultFees(token6D, token12D); + createLBPairDefaultFees(usdc, wbtc); vm.prank(ALICE); vm.expectRevert(PendingOwnable__NotOwner.selector); factory.setFeesParametersOnPair( - token6D, - token12D, + usdc, + wbtc, DEFAULT_BIN_STEP, DEFAULT_BASE_FACTOR, DEFAULT_FILTER_PERIOD, @@ -271,12 +269,10 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_PROTOCOL_SHARE, DEFAULT_MAX_VOLATILITY_ACCUMULATED ); - vm.expectRevert( - abi.encodeWithSelector(LBFactory__LBPairNotCreated.selector, token6D, token18D, DEFAULT_BIN_STEP) - ); + vm.expectRevert(abi.encodeWithSelector(LBFactory__LBPairNotCreated.selector, usdc, weth, DEFAULT_BIN_STEP)); factory.setFeesParametersOnPair( - token6D, - token18D, + usdc, + weth, DEFAULT_BIN_STEP, DEFAULT_BASE_FACTOR, DEFAULT_FILTER_PERIOD, @@ -289,7 +285,7 @@ contract LiquidityBinFactoryTest is TestHelper { } function testForInvalidFilterPeriod() public { - createLBPairDefaultFees(token6D, token12D); + createLBPairDefaultFees(usdc, wbtc); uint16 invalidFilterPeriod = DEFAULT_DECAY_PERIOD; vm.expectRevert( @@ -297,8 +293,8 @@ contract LiquidityBinFactoryTest is TestHelper { ); factory.setFeesParametersOnPair( - token6D, - token12D, + usdc, + wbtc, DEFAULT_BIN_STEP, DEFAULT_BASE_FACTOR, invalidFilterPeriod, @@ -311,19 +307,17 @@ contract LiquidityBinFactoryTest is TestHelper { } function testForInvalidProtocolShare() public { - createLBPairDefaultFees(token6D, token12D); + createLBPairDefaultFees(usdc, wbtc); uint16 invalidProtocolShare = uint16(factory.MAX_PROTOCOL_SHARE() + 1); vm.expectRevert( abi.encodeWithSelector( - LBFactory__ProtocolShareOverflows.selector, - invalidProtocolShare, - factory.MAX_PROTOCOL_SHARE() + LBFactory__ProtocolShareOverflows.selector, invalidProtocolShare, factory.MAX_PROTOCOL_SHARE() ) ); factory.setFeesParametersOnPair( - token6D, - token12D, + usdc, + wbtc, DEFAULT_BIN_STEP, DEFAULT_BASE_FACTOR, DEFAULT_FILTER_PERIOD, @@ -339,9 +333,7 @@ contract LiquidityBinFactoryTest is TestHelper { uint16 invalidReductionFactor = uint16(Constants.BASIS_POINT_MAX + 1); vm.expectRevert( abi.encodeWithSelector( - LBFactory__ReductionFactorOverflows.selector, - invalidReductionFactor, - Constants.BASIS_POINT_MAX + LBFactory__ReductionFactorOverflows.selector, invalidReductionFactor, Constants.BASIS_POINT_MAX ) ); @@ -359,14 +351,14 @@ contract LiquidityBinFactoryTest is TestHelper { } function testForSetLBPairIgnoredReverts() public { - createLBPairDefaultFees(token6D, token18D); + createLBPairDefaultFees(usdc, weth); - factory.setLBPairIgnored(token6D, token18D, DEFAULT_BIN_STEP, true); + factory.setLBPairIgnored(usdc, weth, DEFAULT_BIN_STEP, true); vm.expectRevert(LBFactory__LBPairIgnoredIsAlreadyInTheSameState.selector); - factory.setLBPairIgnored(token6D, token18D, DEFAULT_BIN_STEP, true); + factory.setLBPairIgnored(usdc, weth, DEFAULT_BIN_STEP, true); vm.expectRevert(LBFactory__AddressZero.selector); - factory.setLBPairIgnored(token6D, token18D, DEFAULT_BIN_STEP + 1, true); + factory.setLBPairIgnored(usdc, weth, DEFAULT_BIN_STEP + 1, true); } function testForSettingFlashloanFee() public { @@ -396,9 +388,10 @@ contract LiquidityBinFactoryTest is TestHelper { //copy of part of factory._getPackedFeeParameters function uint256 _baseFee = (uint256(baseFactorIncreased) * DEFAULT_BIN_STEP) * 1e10; - uint256 _maxVariableFee = (DEFAULT_VARIABLE_FEE_CONTROL * - (uint256(DEFAULT_MAX_VOLATILITY_ACCUMULATED) * DEFAULT_BIN_STEP) * - (uint256(DEFAULT_MAX_VOLATILITY_ACCUMULATED) * DEFAULT_BIN_STEP)) / 100; + uint256 _maxVariableFee = ( + DEFAULT_VARIABLE_FEE_CONTROL * (uint256(DEFAULT_MAX_VOLATILITY_ACCUMULATED) * DEFAULT_BIN_STEP) + * (uint256(DEFAULT_MAX_VOLATILITY_ACCUMULATED) * DEFAULT_BIN_STEP) + ) / 100; uint256 fee = _baseFee + _maxVariableFee; vm.expectRevert(abi.encodeWithSelector(LBFactory__FeesAboveMax.selector, fee, factory.MAX_FEE())); @@ -421,9 +414,10 @@ contract LiquidityBinFactoryTest is TestHelper { //copy of part of factory._getPackedFeeParameters function uint256 _baseFee = (uint256(DEFAULT_BASE_FACTOR) * DEFAULT_BIN_STEP) * 1e10; - uint256 _maxVariableFee = (DEFAULT_VARIABLE_FEE_CONTROL * - (uint256(volatilityAccumulated) * DEFAULT_BIN_STEP) * - (uint256(volatilityAccumulated) * DEFAULT_BIN_STEP)) / 100; + uint256 _maxVariableFee = ( + DEFAULT_VARIABLE_FEE_CONTROL * (uint256(volatilityAccumulated) * DEFAULT_BIN_STEP) + * (uint256(volatilityAccumulated) * DEFAULT_BIN_STEP) + ) / 100; uint256 fee = _baseFee + _maxVariableFee; vm.expectRevert(abi.encodeWithSelector(LBFactory__FeesAboveMax.selector, fee, factory.MAX_FEE())); @@ -442,40 +436,40 @@ contract LiquidityBinFactoryTest is TestHelper { function testInvalidBinStepWhileCreatingLBPair() public { vm.expectRevert(abi.encodeWithSelector(LBFactory__BinStepHasNoPreset.selector, DEFAULT_BIN_STEP + 1)); - createLBPairDefaultFeesFromStartIdAndBinStep(token6D, token12D, ID_ONE, DEFAULT_BIN_STEP + 1); + createLBPairDefaultFeesFromStartIdAndBinStep(usdc, wbtc, ID_ONE, DEFAULT_BIN_STEP + 1); } function testQuoteAssets() public { assertEq(factory.getNumberOfQuoteAssets(), 4); assertEq(address(factory.getQuoteAsset(0)), address(wavax)); - assertEq(address(factory.getQuoteAsset(1)), address(token6D)); - assertEq(address(factory.getQuoteAsset(2)), address(token12D)); - assertEq(address(factory.getQuoteAsset(3)), address(token18D)); + assertEq(address(factory.getQuoteAsset(1)), address(usdc)); + assertEq(address(factory.getQuoteAsset(2)), address(wbtc)); + assertEq(address(factory.getQuoteAsset(3)), address(weth)); assertEq(factory.isQuoteAsset(wavax), true); - assertEq(factory.isQuoteAsset(token6D), true); - assertEq(factory.isQuoteAsset(token12D), true); - assertEq(factory.isQuoteAsset(token18D), true); + assertEq(factory.isQuoteAsset(usdc), true); + assertEq(factory.isQuoteAsset(wbtc), true); + assertEq(factory.isQuoteAsset(weth), true); - vm.expectRevert(abi.encodeWithSelector(LBFactory__QuoteAssetAlreadyWhitelisted.selector, token12D)); - factory.addQuoteAsset(token12D); + vm.expectRevert(abi.encodeWithSelector(LBFactory__QuoteAssetAlreadyWhitelisted.selector, wbtc)); + factory.addQuoteAsset(wbtc); - token24D = new ERC20MockDecimals(24); - vm.expectRevert(abi.encodeWithSelector(LBFactory__QuoteAssetNotWhitelisted.selector, token24D)); - factory.removeQuoteAsset(token24D); + wbtc = new ERC20Mock(24); + vm.expectRevert(abi.encodeWithSelector(LBFactory__QuoteAssetNotWhitelisted.selector, wbtc)); + factory.removeQuoteAsset(wbtc); - assertEq(factory.isQuoteAsset(token24D), false); - vm.expectRevert(abi.encodeWithSelector(LBFactory__QuoteAssetNotWhitelisted.selector, token24D)); - factory.createLBPair(token6D, token24D, ID_ONE, DEFAULT_BIN_STEP); + assertEq(factory.isQuoteAsset(wbtc), false); + vm.expectRevert(abi.encodeWithSelector(LBFactory__QuoteAssetNotWhitelisted.selector, wbtc)); + factory.createLBPair(usdc, wbtc, ID_ONE, DEFAULT_BIN_STEP); vm.expectEmit(true, true, true, true); - emit QuoteAssetAdded(token24D); - factory.addQuoteAsset(token24D); - assertEq(factory.isQuoteAsset(token24D), true); - assertEq(address(factory.getQuoteAsset(4)), address(token24D)); + emit QuoteAssetAdded(wbtc); + factory.addQuoteAsset(wbtc); + assertEq(factory.isQuoteAsset(wbtc), true); + assertEq(address(factory.getQuoteAsset(4)), address(wbtc)); vm.expectEmit(true, true, true, true); - emit QuoteAssetRemoved(token24D); - factory.removeQuoteAsset(token24D); - assertEq(factory.isQuoteAsset(token24D), false); + emit QuoteAssetRemoved(wbtc); + factory.removeQuoteAsset(wbtc); + assertEq(factory.isQuoteAsset(wbtc), false); } } diff --git a/test/LBPair.Fees.t.sol b/test_old/LBPair.Fees.t.sol similarity index 75% rename from test/LBPair.Fees.t.sol rename to test_old/LBPair.Fees.t.sol index 17e5ebd8..4d9d38dd 100644 --- a/test/LBPair.Fees.t.sol +++ b/test_old/LBPair.Fees.t.sol @@ -2,15 +2,15 @@ pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "test/helpers/TestHelper.sol"; import "src/libraries/Math512Bits.sol"; contract LiquidityBinPairFeesTest is TestHelper { using Math512Bits for uint256; - function setUp() public { - token6D = new ERC20MockDecimals(6); - token18D = new ERC20MockDecimals(18); + function setUp() public override { + usdc = new ERC20Mock(6); + weth = new ERC20Mock(18); factory = new LBFactory(DEV, 8e14); ILBPair _LBPairImplementation = new LBPair(factory); @@ -20,7 +20,7 @@ contract LiquidityBinPairFeesTest is TestHelper { router = new LBRouter(ILBFactory(DEV), IJoeFactory(DEV), IWAVAX(DEV)); - pair = createLBPairDefaultFees(token6D, token18D); + pair = createLBPairDefaultFees(usdc, weth); } function testClaimFeesY() public { @@ -32,11 +32,11 @@ contract LiquidityBinPairFeesTest is TestHelper { (uint256 amountYInForSwap, uint256 feesFromGetSwapIn) = router.getSwapIn(pair, amountXOutForSwap, false); - token18D.mint(address(pair), amountYInForSwap); + weth.mint(address(pair), amountYInForSwap); vm.prank(ALICE); pair.swap(false, DEV); - (, uint256 feesYTotal, , uint256 feesYProtocol) = pair.getGlobalFees(); + (, uint256 feesYTotal,, uint256 feesYProtocol) = pair.getGlobalFees(); assertEq(feesYTotal, feesFromGetSwapIn); uint256 accumulatedYFees = feesYTotal - feesYProtocol; @@ -50,12 +50,12 @@ contract LiquidityBinPairFeesTest is TestHelper { assertApproxEqAbs(accumulatedYFees, feeY, 1); - uint256 balanceBefore = token18D.balanceOf(DEV); + uint256 balanceBefore = weth.balanceOf(DEV); pair.collectFees(DEV, orderedIds); - assertEq(feeY, token18D.balanceOf(DEV) - balanceBefore); + assertEq(feeY, weth.balanceOf(DEV) - balanceBefore); // Trying to claim a second time - balanceBefore = token18D.balanceOf(DEV); + balanceBefore = weth.balanceOf(DEV); (feeX, feeY) = pair.pendingFees(DEV, orderedIds); assertEq(feeY, 0); @@ -67,7 +67,7 @@ contract LiquidityBinPairFeesTest is TestHelper { assertEq(feeX, 0); pair.collectFees(DEV, orderedIds); - assertEq(token18D.balanceOf(DEV), balanceBefore); + assertEq(weth.balanceOf(DEV), balanceBefore); } function testClaimFeesX() public { @@ -79,11 +79,11 @@ contract LiquidityBinPairFeesTest is TestHelper { (uint256 amountXInForSwap, uint256 feesFromGetSwapIn) = router.getSwapIn(pair, amountYOutForSwap, true); - token6D.mint(address(pair), amountXInForSwap); + usdc.mint(address(pair), amountXInForSwap); vm.prank(ALICE); pair.swap(true, DEV); - (uint256 feesXTotal, , uint256 feesXProtocol, ) = pair.getGlobalFees(); + (uint256 feesXTotal,, uint256 feesXProtocol,) = pair.getGlobalFees(); assertEq(feesXTotal, feesFromGetSwapIn); uint256 accumulatedXFees = feesXTotal - feesXProtocol; @@ -96,12 +96,12 @@ contract LiquidityBinPairFeesTest is TestHelper { assertApproxEqAbs(accumulatedXFees, feeX, 1); - uint256 balanceBefore = token6D.balanceOf(DEV); + uint256 balanceBefore = usdc.balanceOf(DEV); pair.collectFees(DEV, orderedIds); - assertEq(feeX, token6D.balanceOf(DEV) - balanceBefore); + assertEq(feeX, usdc.balanceOf(DEV) - balanceBefore); // Trying to claim a second time - balanceBefore = token6D.balanceOf(DEV); + balanceBefore = usdc.balanceOf(DEV); (feeX, feeY) = pair.pendingFees(DEV, orderedIds); assertEq(feeX, 0); @@ -114,7 +114,7 @@ contract LiquidityBinPairFeesTest is TestHelper { assertEq(feeX, 0); pair.collectFees(DEV, orderedIds); - assertEq(token6D.balanceOf(DEV), balanceBefore); + assertEq(usdc.balanceOf(DEV), balanceBefore); } function testFeesOnTokenTransfer() public { @@ -122,9 +122,9 @@ contract LiquidityBinPairFeesTest is TestHelper { uint256 amountYForSwap = 1e6; uint24 startId = ID_ONE; - (uint256[] memory _ids, , , ) = addLiquidity(amountYInLiquidity, startId, 5, 0); + (uint256[] memory _ids,,,) = addLiquidity(amountYInLiquidity, startId, 5, 0); - token18D.mint(address(pair), amountYForSwap); + weth.mint(address(pair), amountYForSwap); pair.swap(false, ALICE); @@ -135,7 +135,7 @@ contract LiquidityBinPairFeesTest is TestHelper { pair.safeBatchTransferFrom(DEV, BOB, _ids, amounts); - token18D.mint(address(pair), amountYForSwap); + weth.mint(address(pair), amountYForSwap); pair.swap(false, ALICE); (uint256 feesForDevX, uint256 feesForDevY) = pair.pendingFees(DEV, _ids); @@ -144,27 +144,19 @@ contract LiquidityBinPairFeesTest is TestHelper { assertGt(feesForDevY, 0, "DEV should have fees on token Y"); assertGt(feesForBobY, 0, "BOB should also have fees on token Y"); - (, uint256 feesYTotal, , uint256 feesYProtocol) = pair.getGlobalFees(); + (, uint256 feesYTotal,, uint256 feesYProtocol) = pair.getGlobalFees(); uint256 accumulatedYFees = feesYTotal - feesYProtocol; assertApproxEqAbs(feesForDevY + feesForBobY, accumulatedYFees, 1, "Sum of users fees = accumulated fees"); - uint256 balanceBefore = token18D.balanceOf(DEV); + uint256 balanceBefore = weth.balanceOf(DEV); pair.collectFees(DEV, _ids); - assertEq( - feesForDevY, - token18D.balanceOf(DEV) - balanceBefore, - "DEV gets the expected amount when withdrawing fees" - ); + assertEq(feesForDevY, weth.balanceOf(DEV) - balanceBefore, "DEV gets the expected amount when withdrawing fees"); - balanceBefore = token18D.balanceOf(BOB); + balanceBefore = weth.balanceOf(BOB); pair.collectFees(BOB, _ids); - assertEq( - feesForBobY, - token18D.balanceOf(BOB) - balanceBefore, - "BOB gets the expected amount when withdrawing fees" - ); + assertEq(feesForBobY, weth.balanceOf(BOB) - balanceBefore, "BOB gets the expected amount when withdrawing fees"); } struct FeeInfo { @@ -184,7 +176,7 @@ contract LiquidityBinPairFeesTest is TestHelper { // Add Y fees (uint256 amountIn, uint256 feesIn) = router.getSwapIn(pair, 1e6, false); - token18D.mint(address(pair), amountIn); + weth.mint(address(pair), amountIn); vm.prank(ALICE); pair.swap(false, DEV); @@ -202,12 +194,12 @@ contract LiquidityBinPairFeesTest is TestHelper { assertLt(feesBefore.feeYProtocol, feesBefore.feeYTotal); address protocolFeesReceiver = factory.feeRecipient(); - uint256 balanceBefore = token18D.balanceOf(protocolFeesReceiver); + uint256 balanceBefore = weth.balanceOf(protocolFeesReceiver); vm.prank(protocolFeesReceiver); pair.collectProtocolFees(); - assertEq(token18D.balanceOf(protocolFeesReceiver) - balanceBefore, feesBefore.feeYProtocol - 1); + assertEq(weth.balanceOf(protocolFeesReceiver) - balanceBefore, feesBefore.feeYProtocol - 1); FeeInfo memory feesAfter = _getGlobalFees(); @@ -220,7 +212,7 @@ contract LiquidityBinPairFeesTest is TestHelper { // Claiming twice pair.collectProtocolFees(); - assertEq(token18D.balanceOf(protocolFeesReceiver) - balanceBefore, feesBefore.feeYProtocol - 1); + assertEq(weth.balanceOf(protocolFeesReceiver) - balanceBefore, feesBefore.feeYProtocol - 1); FeeInfo memory feesAfter2 = _getGlobalFees(); @@ -232,7 +224,7 @@ contract LiquidityBinPairFeesTest is TestHelper { // Add X fees (amountIn, feesIn) = router.getSwapIn(pair, 1e18, true); - token6D.mint(address(pair), amountIn); + usdc.mint(address(pair), amountIn); vm.prank(ALICE); pair.swap(true, DEV); @@ -249,12 +241,12 @@ contract LiquidityBinPairFeesTest is TestHelper { assertGt(feesBefore.feeXProtocol, 0); assertLt(feesBefore.feeXProtocol, feesBefore.feeXTotal); - balanceBefore = token6D.balanceOf(protocolFeesReceiver); + balanceBefore = usdc.balanceOf(protocolFeesReceiver); vm.prank(protocolFeesReceiver); pair.collectProtocolFees(); - assertEq(token6D.balanceOf(protocolFeesReceiver) - balanceBefore, feesBefore.feeXProtocol - 1); + assertEq(usdc.balanceOf(protocolFeesReceiver) - balanceBefore, feesBefore.feeXProtocol - 1); feesAfter = _getGlobalFees(); @@ -269,7 +261,7 @@ contract LiquidityBinPairFeesTest is TestHelper { // Claiming twice pair.collectProtocolFees(); - assertEq(token6D.balanceOf(protocolFeesReceiver) - balanceBefore, feesBefore.feeXProtocol - 1); + assertEq(usdc.balanceOf(protocolFeesReceiver) - balanceBefore, feesBefore.feeXProtocol - 1); FeeInfo memory feesAfter2X = _getGlobalFees(); @@ -290,22 +282,22 @@ contract LiquidityBinPairFeesTest is TestHelper { FeeHelper.FeeParameters memory _feeParameters = pair.feeParameters(); addLiquidity(amountYInLiquidity, startId, 51, 5); - (uint256 amountYInForSwap, ) = router.getSwapIn(pair, amountYInLiquidity / 4, true); - token6D.mint(address(pair), amountYInForSwap); + (uint256 amountYInForSwap,) = router.getSwapIn(pair, amountYInLiquidity / 4, true); + usdc.mint(address(pair), amountYInForSwap); vm.prank(ALICE); pair.swap(true, ALICE); vm.warp(block.timestamp + 90); - (amountYInForSwap, ) = router.getSwapIn(pair, amountYInLiquidity / 4, true); - token6D.mint(address(pair), amountYInForSwap); + (amountYInForSwap,) = router.getSwapIn(pair, amountYInLiquidity / 4, true); + usdc.mint(address(pair), amountYInForSwap); vm.prank(ALICE); pair.swap(true, ALICE); _feeParameters = pair.feeParameters(); uint256 referenceBeforeForceDecay = _feeParameters.volatilityReference; - uint256 referenceAfterForceDecayExpected = (uint256(_feeParameters.reductionFactor) * - referenceBeforeForceDecay) / Constants.BASIS_POINT_MAX; + uint256 referenceAfterForceDecayExpected = + (uint256(_feeParameters.reductionFactor) * referenceBeforeForceDecay) / Constants.BASIS_POINT_MAX; factory.forceDecay(pair); @@ -330,40 +322,40 @@ contract LiquidityBinPairFeesTest is TestHelper { (uint256 amountXInForSwap, uint256 feesXFromGetSwap) = router.getSwapIn(pair, amountY, true); totalFeesFromGetSwapX += feesXFromGetSwap; - token6D.mint(address(pair), amountXInForSwap); + usdc.mint(address(pair), amountXInForSwap); vm.prank(ALICE); pair.swap(true, DEV); - (uint256 feesXTotal, , uint256 feesXProtocol, ) = pair.getGlobalFees(); + (uint256 feesXTotal,, uint256 feesXProtocol,) = pair.getGlobalFees(); assertEq(feesXTotal, totalFeesFromGetSwapX); //swap Y -> X and accrue Y fees (uint256 amountYInForSwap, uint256 feesYFromGetSwap) = router.getSwapIn(pair, amountX, false); totalFeesFromGetSwapY += feesYFromGetSwap; - token18D.mint(address(pair), amountYInForSwap); + weth.mint(address(pair), amountYInForSwap); vm.prank(ALICE); pair.swap(false, DEV); - (, uint256 feesYTotal, , uint256 feesYProtocol) = pair.getGlobalFees(); + (, uint256 feesYTotal,, uint256 feesYProtocol) = pair.getGlobalFees(); assertEq(feesYTotal, totalFeesFromGetSwapY); //swap Y -> X and accrue Y fees (, feesYFromGetSwap) = router.getSwapOut(pair, amountY, false); totalFeesFromGetSwapY += feesYFromGetSwap; - token18D.mint(address(pair), amountY); + weth.mint(address(pair), amountY); vm.prank(ALICE); pair.swap(false, DEV); - (, feesYTotal, , feesYProtocol) = pair.getGlobalFees(); + (, feesYTotal,, feesYProtocol) = pair.getGlobalFees(); assertEq(feesYTotal, totalFeesFromGetSwapY); //swap X -> Y and accrue X fees (, feesXFromGetSwap) = router.getSwapOut(pair, amountX, true); totalFeesFromGetSwapX += feesXFromGetSwap; - token6D.mint(address(pair), amountX); + usdc.mint(address(pair), amountX); vm.prank(ALICE); pair.swap(true, DEV); - (feesXTotal, , feesXProtocol, ) = pair.getGlobalFees(); + (feesXTotal,, feesXProtocol,) = pair.getGlobalFees(); assertEq(feesXTotal, totalFeesFromGetSwapX); } @@ -375,16 +367,16 @@ contract LiquidityBinPairFeesTest is TestHelper { addLiquidity(amountYInLiquidity, startId, 5, 0); - (uint256 amountYInForSwap, ) = router.getSwapIn(pair, amountXOutForSwap, false); + (uint256 amountYInForSwap,) = router.getSwapIn(pair, amountXOutForSwap, false); // setup non-zero Y fees - token18D.mint(address(pair), amountYInForSwap); + weth.mint(address(pair), amountYInForSwap); vm.prank(ALICE); pair.swap(false, DEV); // setup non-zero X fees - (uint256 amountXInForSwap, ) = router.getSwapIn(pair, amountYOutForSwap, true); - token6D.mint(address(pair), amountXInForSwap); + (uint256 amountXInForSwap,) = router.getSwapIn(pair, amountYOutForSwap, true); + usdc.mint(address(pair), amountXInForSwap); vm.prank(ALICE); pair.swap(true, DEV); diff --git a/test/LBPair.FlashLoans.t.sol b/test_old/LBPair.FlashLoans.t.sol similarity index 75% rename from test/LBPair.FlashLoans.t.sol rename to test_old/LBPair.FlashLoans.t.sol index 2f273e98..4cfcc568 100644 --- a/test/LBPair.FlashLoans.t.sol +++ b/test_old/LBPair.FlashLoans.t.sol @@ -2,16 +2,16 @@ pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "test/helpers/TestHelper.sol"; contract LiquidityBinPairFlashLoansTest is TestHelper { FlashBorrower private borrower; event CalldataTransmitted(); - function setUp() public { - token6D = new ERC20MockDecimals(6); - token18D = new ERC20MockDecimals(18); + function setUp() public override { + usdc = new ERC20Mock(6); + weth = new ERC20Mock(18); factory = new LBFactory(DEV, 8e14); ILBPair _LBPairImplementation = new LBPair(factory); @@ -21,19 +21,19 @@ contract LiquidityBinPairFlashLoansTest is TestHelper { router = new LBRouter(ILBFactory(DEV), IJoeFactory(DEV), IWAVAX(DEV)); - pair = createLBPairDefaultFees(token6D, token18D); + pair = createLBPairDefaultFees(usdc, weth); borrower = new FlashBorrower(pair); } function testFlashloan() public { - (uint256[] memory _ids, , , ) = addLiquidity(100e18, ID_ONE, 9, 5); + (uint256[] memory _ids,,,) = addLiquidity(100e18, ID_ONE, 9, 5); uint256 amountXBorrowed = 10e18; uint256 amountYBorrowed = 10e18; // Paying for fees - token6D.mint(address(borrower), 1e18); - token18D.mint(address(borrower), 1e18); + usdc.mint(address(borrower), 1e18); + weth.mint(address(borrower), 1e18); vm.expectEmit(false, false, false, false); emit CalldataTransmitted(); @@ -48,7 +48,7 @@ contract LiquidityBinPairFlashLoansTest is TestHelper { function testFailFlashloanMoreThanReserves() public { uint256 amountXBorrowed = 150e18; - token6D.mint(address(borrower), 1e18); + usdc.mint(address(borrower), 1e18); borrower.flashBorrow(amountXBorrowed, 0); } @@ -56,7 +56,7 @@ contract LiquidityBinPairFlashLoansTest is TestHelper { function testFailFlashlaonWithReentrancy() public { uint256 amountXBorrowed = 150e18; - token6D.mint(address(borrower), 1e18); + usdc.mint(address(borrower), 1e18); borrower.flashBorrowWithReentrancy(amountXBorrowed, 0); } diff --git a/test/LBPair.Liquidity.t.sol b/test_old/LBPair.Liquidity.t.sol similarity index 82% rename from test/LBPair.Liquidity.t.sol rename to test_old/LBPair.Liquidity.t.sol index b1bac3c8..7d3bbafc 100644 --- a/test/LBPair.Liquidity.t.sol +++ b/test_old/LBPair.Liquidity.t.sol @@ -2,12 +2,12 @@ pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "test/helpers/TestHelper.sol"; contract LiquidityBinPairLiquidityTest is TestHelper { - function setUp() public { - token6D = new ERC20MockDecimals(6); - token18D = new ERC20MockDecimals(18); + function setUp() public override { + usdc = new ERC20Mock(6); + weth = new ERC20Mock(18); factory = new LBFactory(DEV, 8e14); ILBPair _LBPairImplementation = new LBPair(factory); @@ -42,11 +42,11 @@ contract LiquidityBinPairLiquidityTest is TestHelper { ); pair = new LBPair(ILBFactory(DEV)); - pair.initialize(token6D, token18D, ID_ONE, DEFAULT_SAMPLE_LIFETIME, _packedFeeParameters); + pair.initialize(usdc, weth, ID_ONE, DEFAULT_SAMPLE_LIFETIME, _packedFeeParameters); assertEq(address(pair.factory()), DEV); - assertEq(address(pair.tokenX()), address(token6D)); - assertEq(address(pair.tokenY()), address(token18D)); + assertEq(address(pair.tokenX()), address(usdc)); + assertEq(address(pair.tokenY()), address(weth)); FeeHelper.FeeParameters memory feeParameters = pair.feeParameters(); assertEq(feeParameters.volatilityAccumulated, 0, "volatilityAccumulated should be 0"); @@ -67,9 +67,9 @@ contract LiquidityBinPairLiquidityTest is TestHelper { function testFuzzingAddLiquidity(uint256 _price) public { // Avoids Math__Exp2InputTooBig and very small x amounts - vm.assume(_price < 2**238); + vm.assume(_price < 2 ** 238); // Avoids LBPair__BinReserveOverflows (very big x amounts) - vm.assume(_price > 2**18); + vm.assume(_price > 2 ** 18); uint24 startId = getIdFromPrice(_price); @@ -79,22 +79,24 @@ contract LiquidityBinPairLiquidityTest is TestHelper { // Assert that price is at most `binStep`% away from the calculated price assertEq( ( - (((_price * (Constants.BASIS_POINT_MAX - DEFAULT_BIN_STEP)) / 10_000) <= _calculatedPrice && - _calculatedPrice <= (_price * (Constants.BASIS_POINT_MAX + DEFAULT_BIN_STEP)) / 10_000) + ( + ((_price * (Constants.BASIS_POINT_MAX - DEFAULT_BIN_STEP)) / 10_000) <= _calculatedPrice + && _calculatedPrice <= (_price * (Constants.BASIS_POINT_MAX + DEFAULT_BIN_STEP)) / 10_000 + ) ), true, "Wrong log2" ); - pair = createLBPairDefaultFeesFromStartId(token6D, token18D, startId); + pair = createLBPairDefaultFeesFromStartId(usdc, weth, startId); - uint256 amountYIn = _price < type(uint128).max ? 2**18 : type(uint112).max; + uint256 amountYIn = _price < type(uint128).max ? 2 ** 18 : type(uint112).max; uint256 amountXIn = (amountYIn << 112) / _price + 3; console.log(amountXIn, amountYIn); - token6D.mint(address(pair), amountXIn); - token18D.mint(address(pair), amountYIn); + usdc.mint(address(pair), amountXIn); + weth.mint(address(pair), amountYIn); uint256[] memory ids = new uint256[](3); uint256[] memory distribX = new uint256[](3); @@ -127,22 +129,18 @@ contract LiquidityBinPairLiquidityTest is TestHelper { } function testBurnLiquidity() public { - pair = createLBPairDefaultFees(token6D, token18D); + pair = createLBPairDefaultFees(usdc, weth); uint256 amount1In = 3e12; - ( - uint256[] memory _ids, - uint256[] memory _distributionX, - uint256[] memory _distributionY, - uint256 amount0In - ) = spreadLiquidity(amount1In * 2, ID_ONE, 5, 0); + (uint256[] memory _ids, uint256[] memory _distributionX, uint256[] memory _distributionY, uint256 amount0In) = + spreadLiquidity(amount1In * 2, ID_ONE, 5, 0); - token6D.mint(address(pair), amount0In); - token18D.mint(address(pair), amount1In); + usdc.mint(address(pair), amount0In); + weth.mint(address(pair), amount1In); pair.mint(_ids, _distributionX, _distributionY, ALICE); - token6D.mint(address(pair), amount0In); - token18D.mint(address(pair), amount1In); + usdc.mint(address(pair), amount0In); + weth.mint(address(pair), amount1In); pair.mint(_ids, _distributionX, _distributionY, BOB); @@ -157,8 +155,8 @@ contract LiquidityBinPairLiquidityTest is TestHelper { pair.collectFees(BOB, _ids); // the excess token were sent to fees, so they need to be claimed vm.stopPrank(); - assertEq(token6D.balanceOf(BOB), amount0In); - assertEq(token18D.balanceOf(BOB), amount1In); + assertEq(usdc.balanceOf(BOB), amount0In); + assertEq(weth.balanceOf(BOB), amount1In); } function testFlawedCompositionFactor() public { @@ -166,7 +164,7 @@ contract LiquidityBinPairLiquidityTest is TestHelper { uint24 startId = ID_ONE; uint256 amount0In = 3e12; uint256 amount1In = 3e12; - pair = createLBPairDefaultFees(token6D, token18D); + pair = createLBPairDefaultFees(usdc, weth); addLiquidity(amount1In, startId, _numberBins, 0); @@ -182,8 +180,8 @@ contract LiquidityBinPairLiquidityTest is TestHelper { _distributionX[2] = Constants.PRECISION / 3; uint256[] memory _distributionY = new uint256[](3); - token6D.mint(address(pair), amount0In); - token18D.mint(address(pair), amount1In); + usdc.mint(address(pair), amount0In); + weth.mint(address(pair), amount1In); vm.expectRevert(abi.encodeWithSelector(LBPair__CompositionFactorFlawed.selector, _ids[0])); pair.mint(_ids, _distributionX, _distributionY, ALICE); @@ -219,7 +217,7 @@ contract LiquidityBinPairLiquidityTest is TestHelper { uint24 startId = ID_ONE; uint256 amount0In = 3e12; uint256 amount1In = 3e12; - pair = createLBPairDefaultFees(token6D, token18D); + pair = createLBPairDefaultFees(usdc, weth); addLiquidity(amount1In, startId, _numberBins, 0); @@ -235,8 +233,8 @@ contract LiquidityBinPairLiquidityTest is TestHelper { _distributionX[2] = Constants.PRECISION / 3; uint256[] memory _distributionY = new uint256[](3); - token6D.mint(address(pair), amount0In); - token18D.mint(address(pair), amount1In); + usdc.mint(address(pair), amount0In); + weth.mint(address(pair), amount1In); vm.expectRevert(abi.encodeWithSelector(LBPair__InsufficientLiquidityMinted.selector, _ids[0])); pair.mint(_ids, _distributionX, _distributionY, ALICE); diff --git a/test/LBPair.Oracle.t.sol b/test_old/LBPair.Oracle.t.sol similarity index 73% rename from test/LBPair.Oracle.t.sol rename to test_old/LBPair.Oracle.t.sol index da0b11c3..2771d4fe 100644 --- a/test/LBPair.Oracle.t.sol +++ b/test_old/LBPair.Oracle.t.sol @@ -2,13 +2,13 @@ pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "test/helpers/TestHelper.sol"; import "src/libraries/Oracle.sol"; contract LiquidityBinPairOracleTest is TestHelper { - function setUp() public { - token6D = new ERC20MockDecimals(6); - token18D = new ERC20MockDecimals(18); + function setUp() public override { + usdc = new ERC20Mock(6); + weth = new ERC20Mock(18); factory = new LBFactory(DEV, 8e14); ILBPair _LBPairImplementation = new LBPair(factory); @@ -18,7 +18,7 @@ contract LiquidityBinPairOracleTest is TestHelper { router = new LBRouter(ILBFactory(DEV), IJoeFactory(DEV), IWAVAX(DEV)); - pair = createLBPairDefaultFees(token6D, token18D); + pair = createLBPairDefaultFees(usdc, weth); } function testVerifyOracleInitialParams() public { @@ -75,7 +75,7 @@ contract LiquidityBinPairOracleTest is TestHelper { function testOracleSampleFromEdgeCases() public { uint256 tokenAmount = 100e18; - token18D.mint(address(pair), tokenAmount); + weth.mint(address(pair), tokenAmount); uint256[] memory _ids = new uint256[](1); _ids[0] = ID_ONE; @@ -85,7 +85,7 @@ contract LiquidityBinPairOracleTest is TestHelper { pair.mint(_ids, new uint256[](1), _liquidities, DEV); - token6D.mint(address(pair), 5e18); + usdc.mint(address(pair), 5e18); vm.expectRevert(Oracle__NotInitialized.selector); pair.getOracleSampleFrom(0); @@ -100,7 +100,7 @@ contract LiquidityBinPairOracleTest is TestHelper { function testOracleSampleFromWith2Samples() public { uint256 tokenAmount = 100e18; - token18D.mint(address(pair), tokenAmount); + weth.mint(address(pair), tokenAmount); uint256[] memory _ids = new uint256[](1); _ids[0] = ID_ONE; @@ -110,21 +110,21 @@ contract LiquidityBinPairOracleTest is TestHelper { pair.mint(_ids, new uint256[](1), _liquidities, DEV); - token6D.mint(address(pair), 5e18); + usdc.mint(address(pair), 5e18); pair.swap(true, DEV); vm.warp(block.timestamp + 250); - token6D.mint(address(pair), 5e18); + usdc.mint(address(pair), 5e18); pair.swap(true, DEV); uint256 _ago = 130; uint256 _time = block.timestamp - _ago; - (uint256 cumulativeId, uint256 cumulativeVolatilityAccumulated, uint256 cumulativeBinCrossed) = pair - .getOracleSampleFrom(_ago); + (uint256 cumulativeId, uint256 cumulativeVolatilityAccumulated, uint256 cumulativeBinCrossed) = + pair.getOracleSampleFrom(_ago); assertEq(cumulativeId / _time, ID_ONE); assertEq(cumulativeVolatilityAccumulated, 0); assertEq(cumulativeBinCrossed, 0); @@ -132,15 +132,11 @@ contract LiquidityBinPairOracleTest is TestHelper { function testOracleSampleFromWith100Samples() public { uint256 amount1In = 200e18; - ( - uint256[] memory _ids, - uint256[] memory _distributionX, - uint256[] memory _distributionY, - uint256 amount0In - ) = spreadLiquidity(amount1In * 2, ID_ONE, 99, 100); + (uint256[] memory _ids, uint256[] memory _distributionX, uint256[] memory _distributionY, uint256 amount0In) = + spreadLiquidity(amount1In * 2, ID_ONE, 99, 100); - token6D.mint(address(pair), amount0In); - token18D.mint(address(pair), amount1In); + usdc.mint(address(pair), amount0In); + weth.mint(address(pair), amount1In); pair.mint(_ids, _distributionX, _distributionY, DEV); pair.increaseOracleLength(100); @@ -148,7 +144,7 @@ contract LiquidityBinPairOracleTest is TestHelper { uint256 startTimestamp; for (uint256 i; i < 200; ++i) { - token6D.mint(address(pair), 1e18); + usdc.mint(address(pair), 1e18); vm.warp(1500 + 100 * i); pair.swap(true, DEV); @@ -161,8 +157,8 @@ contract LiquidityBinPairOracleTest is TestHelper { for (uint256 i; i < 99; ++i) { uint256 _ago = ((block.timestamp - startTimestamp) * i) / 100; - (uint256 cumulativeId, uint256 cumulativeVolatilityAccumulated, uint256 cumulativeBinCrossed) = pair - .getOracleSampleFrom(_ago); + (uint256 cumulativeId, uint256 cumulativeVolatilityAccumulated, uint256 cumulativeBinCrossed) = + pair.getOracleSampleFrom(_ago); assertGe(cId, cumulativeId); assertGe(cAcc, cumulativeVolatilityAccumulated); assertGe(cBin, cumulativeBinCrossed); @@ -173,15 +169,11 @@ contract LiquidityBinPairOracleTest is TestHelper { function testOracleSampleFromWith100SamplesNotAllInitialized() public { uint256 amount1In = 101e18; - ( - uint256[] memory _ids, - uint256[] memory _distributionX, - uint256[] memory _distributionY, - uint256 amount0In - ) = spreadLiquidity(amount1In * 2, ID_ONE, 99, 100); + (uint256[] memory _ids, uint256[] memory _distributionX, uint256[] memory _distributionY, uint256 amount0In) = + spreadLiquidity(amount1In * 2, ID_ONE, 99, 100); - token6D.mint(address(pair), amount0In); - token18D.mint(address(pair), amount1In); + usdc.mint(address(pair), amount0In); + weth.mint(address(pair), amount1In); pair.mint(_ids, _distributionX, _distributionY, DEV); @@ -189,7 +181,7 @@ contract LiquidityBinPairOracleTest is TestHelper { uint16 newSize = 2; for (uint256 i; i < 50; ++i) { - token6D.mint(address(pair), 1e18); + usdc.mint(address(pair), 1e18); newSize += 2; pair.increaseOracleLength(newSize); @@ -205,8 +197,8 @@ contract LiquidityBinPairOracleTest is TestHelper { for (uint256 i; i < 49; ++i) { uint256 _ago = ((block.timestamp - startTimestamp) * i) / 50; - (uint256 cumulativeId, uint256 cumulativeVolatilityAccumulated, uint256 cumulativeBinCrossed) = pair - .getOracleSampleFrom(_ago); + (uint256 cumulativeId, uint256 cumulativeVolatilityAccumulated, uint256 cumulativeBinCrossed) = + pair.getOracleSampleFrom(_ago); assertGe(cId, cumulativeId); assertGe(cAcc, cumulativeVolatilityAccumulated); assertGe(cBin, cumulativeBinCrossed); @@ -222,20 +214,17 @@ contract LiquidityBinPairOracleTest is TestHelper { FeeHelper.FeeParameters memory _feeParameters = pair.feeParameters(); addLiquidity(amountYInLiquidity, startId, 51, 5); - (uint256 amountYInForSwap, ) = router.getSwapIn(pair, amountYInLiquidity / 4, true); - token6D.mint(address(pair), amountYInForSwap); + (uint256 amountYInForSwap,) = router.getSwapIn(pair, amountYInLiquidity / 4, true); + usdc.mint(address(pair), amountYInForSwap); vm.prank(ALICE); pair.swap(true, ALICE); - (uint256 cumulativeId, uint256 cumulativeVolatilityAccumulated, uint256 cumulativeBinCrossed) = pair - .getOracleSampleFrom(0); + (uint256 cumulativeId, uint256 cumulativeVolatilityAccumulated, uint256 cumulativeBinCrossed) = + pair.getOracleSampleFrom(0); vm.warp(block.timestamp + 90); - ( - uint256 cumulativeIdAfter, - uint256 cumulativeVolatilityAccumulatedAfter, - uint256 cumulativeBinCrossedAfter - ) = pair.getOracleSampleFrom(0); + (uint256 cumulativeIdAfter, uint256 cumulativeVolatilityAccumulatedAfter, uint256 cumulativeBinCrossedAfter) = + pair.getOracleSampleFrom(0); assertEq(cumulativeId * block.timestamp, cumulativeIdAfter); assertLt(cumulativeVolatilityAccumulated, cumulativeVolatilityAccumulatedAfter); @@ -243,12 +232,16 @@ contract LiquidityBinPairOracleTest is TestHelper { } function testTheSameOracleSizeReverts() public { - (, uint256 currentOracleSize, , , , , ) = pair.getOracleParameters(); - vm.expectRevert(abi.encodeWithSelector(LBPair__OracleNewSizeTooSmall.selector, currentOracleSize, currentOracleSize)); + (, uint256 currentOracleSize,,,,,) = pair.getOracleParameters(); + vm.expectRevert( + abi.encodeWithSelector(LBPair__OracleNewSizeTooSmall.selector, currentOracleSize, currentOracleSize) + ); pair.increaseOracleLength(uint16(currentOracleSize)); pair.increaseOracleLength(uint16(currentOracleSize + 22)); - (, currentOracleSize, , , , , ) = pair.getOracleParameters(); - vm.expectRevert(abi.encodeWithSelector(LBPair__OracleNewSizeTooSmall.selector, currentOracleSize, currentOracleSize)); + (, currentOracleSize,,,,,) = pair.getOracleParameters(); + vm.expectRevert( + abi.encodeWithSelector(LBPair__OracleNewSizeTooSmall.selector, currentOracleSize, currentOracleSize) + ); pair.increaseOracleLength(uint16(currentOracleSize)); } } diff --git a/test/LBPair.Swaps.t.sol b/test_old/LBPair.Swaps.t.sol similarity index 65% rename from test/LBPair.Swaps.t.sol rename to test_old/LBPair.Swaps.t.sol index 834bd89e..1ef6bf52 100644 --- a/test/LBPair.Swaps.t.sol +++ b/test_old/LBPair.Swaps.t.sol @@ -2,12 +2,12 @@ pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "test/helpers/TestHelper.sol"; contract LiquidityBinPairSwapsTest is TestHelper { - function setUp() public { - token6D = new ERC20MockDecimals(6); - token18D = new ERC20MockDecimals(18); + function setUp() public override { + usdc = new ERC20Mock(6); + weth = new ERC20Mock(18); factory = new LBFactory(DEV, 8e14); ILBPair _LBPairImplementation = new LBPair(factory); @@ -16,7 +16,7 @@ contract LiquidityBinPairSwapsTest is TestHelper { addAllAssetsToQuoteWhitelist(factory); router = new LBRouter(ILBFactory(DEV), IJoeFactory(DEV), IWAVAX(DEV)); - pair = createLBPairDefaultFees(token6D, token18D); + pair = createLBPairDefaultFees(usdc, weth); } function testSwapInsufficientAmountReverts() public { @@ -28,7 +28,7 @@ contract LiquidityBinPairSwapsTest is TestHelper { function testSwapXtoYSingleBinFromGetSwapOut() public { uint256 tokenAmount = 100e18; - token18D.mint(address(pair), tokenAmount); + weth.mint(address(pair), tokenAmount); uint256[] memory _ids = new uint256[](1); _ids[0] = ID_ONE; @@ -40,18 +40,18 @@ contract LiquidityBinPairSwapsTest is TestHelper { uint256 amountXIn = 1e12; - (uint256 amountYOut, ) = router.getSwapOut(pair, amountXIn, true); + (uint256 amountYOut,) = router.getSwapOut(pair, amountXIn, true); - token6D.mint(address(pair), amountXIn); + usdc.mint(address(pair), amountXIn); pair.swap(true, DEV); - assertEq(token6D.balanceOf(DEV), 0); - assertEq(token18D.balanceOf(DEV), amountYOut); + assertEq(usdc.balanceOf(DEV), 0); + assertEq(weth.balanceOf(DEV), amountYOut); (uint256 binReserveX, uint256 binReserveY) = pair.getBin(ID_ONE); - (uint256 feesXTotal, , , ) = pair.getGlobalFees(); + (uint256 feesXTotal,,,) = pair.getGlobalFees(); assertEq(binReserveX, amountXIn - feesXTotal); assertEq(binReserveY, tokenAmount - amountYOut); @@ -59,7 +59,7 @@ contract LiquidityBinPairSwapsTest is TestHelper { function testSwapYtoXSingleBinFromGetSwapOut() public { uint256 tokenAmount = 100e18; - token6D.mint(address(pair), tokenAmount); + usdc.mint(address(pair), tokenAmount); uint256[] memory _ids = new uint256[](1); _ids[0] = ID_ONE; @@ -73,18 +73,18 @@ contract LiquidityBinPairSwapsTest is TestHelper { uint256 amountYIn = 1e12; - (uint256 amountXOut, ) = router.getSwapOut(pair, amountYIn, false); + (uint256 amountXOut,) = router.getSwapOut(pair, amountYIn, false); - token18D.mint(address(pair), amountYIn); + weth.mint(address(pair), amountYIn); pair.swap(false, DEV); - assertEq(token6D.balanceOf(DEV), amountXOut); - assertEq(token18D.balanceOf(DEV), 0); + assertEq(usdc.balanceOf(DEV), amountXOut); + assertEq(weth.balanceOf(DEV), 0); (uint256 binReserveX, uint256 binReserveY) = pair.getBin(uint24(_ids[0])); - (, uint256 feesYTotal, , ) = pair.getGlobalFees(); + (, uint256 feesYTotal,,) = pair.getGlobalFees(); assertEq(binReserveX, tokenAmount - amountXOut); assertEq(binReserveY, amountYIn - feesYTotal); @@ -92,7 +92,7 @@ contract LiquidityBinPairSwapsTest is TestHelper { function testSwapXtoYSingleBinFromGetSwapIn() public { uint256 tokenAmount = 100e18; - token18D.mint(address(pair), tokenAmount); + weth.mint(address(pair), tokenAmount); uint256[] memory _ids = new uint256[](1); _ids[0] = ID_ONE; @@ -104,18 +104,18 @@ contract LiquidityBinPairSwapsTest is TestHelper { uint256 amountYOut = 1e12; - (uint256 amountXIn, ) = router.getSwapIn(pair, amountYOut, true); + (uint256 amountXIn,) = router.getSwapIn(pair, amountYOut, true); - token6D.mint(address(pair), amountXIn); + usdc.mint(address(pair), amountXIn); pair.swap(true, DEV); - assertEq(token6D.balanceOf(DEV), 0); - assertEq(token18D.balanceOf(DEV), amountYOut); + assertEq(usdc.balanceOf(DEV), 0); + assertEq(weth.balanceOf(DEV), amountYOut); (uint256 binReserveX, uint256 binReserveY) = pair.getBin(ID_ONE); - (uint256 feesXTotal, , , ) = pair.getGlobalFees(); + (uint256 feesXTotal,,,) = pair.getGlobalFees(); assertEq(binReserveX, amountXIn - feesXTotal); assertEq(binReserveY, tokenAmount - amountYOut); @@ -123,7 +123,7 @@ contract LiquidityBinPairSwapsTest is TestHelper { function testSwapYtoXSingleBinFromGetSwapIn() public { uint256 tokenAmount = 100e18; - token6D.mint(address(pair), tokenAmount); + usdc.mint(address(pair), tokenAmount); uint256[] memory _ids = new uint256[](1); _ids[0] = ID_ONE + 1; @@ -135,18 +135,18 @@ contract LiquidityBinPairSwapsTest is TestHelper { uint256 amountXOut = 1e12; - (uint256 amountYIn, ) = router.getSwapIn(pair, amountXOut, false); + (uint256 amountYIn,) = router.getSwapIn(pair, amountXOut, false); - token18D.mint(address(pair), amountYIn); + weth.mint(address(pair), amountYIn); pair.swap(false, DEV); - assertEq(token6D.balanceOf(DEV), amountXOut); - assertEq(token18D.balanceOf(DEV), 0); + assertEq(usdc.balanceOf(DEV), amountXOut); + assertEq(weth.balanceOf(DEV), 0); (uint256 binReserveX, uint256 binReserveY) = pair.getBin(uint24(_ids[0])); - (, uint256 feesYTotal, , ) = pair.getGlobalFees(); + (, uint256 feesYTotal,,) = pair.getGlobalFees(); assertEq(binReserveX, tokenAmount - amountXOut); assertEq(binReserveY, amountYIn - feesYTotal); @@ -159,13 +159,13 @@ contract LiquidityBinPairSwapsTest is TestHelper { addLiquidity(amountYInLiquidity, startId, 9, 0); - (uint256 amountYInForSwap, ) = router.getSwapIn(pair, amountXOutForSwap, false); + (uint256 amountYInForSwap,) = router.getSwapIn(pair, amountXOutForSwap, false); - token18D.mint(address(pair), amountYInForSwap); + weth.mint(address(pair), amountYInForSwap); pair.swap(false, ALICE); - assertEq(token6D.balanceOf(ALICE), amountXOutForSwap); + assertEq(usdc.balanceOf(ALICE), amountXOutForSwap); } function testSwapXtoYConsecutiveBinFromGetSwapIn() public { @@ -175,13 +175,13 @@ contract LiquidityBinPairSwapsTest is TestHelper { addLiquidity(amountYInLiquidity, startId, 9, 0); - (uint256 amountXInForSwap, ) = router.getSwapIn(pair, amountYOutForSwap, true); + (uint256 amountXInForSwap,) = router.getSwapIn(pair, amountYOutForSwap, true); - token6D.mint(address(pair), amountXInForSwap); + usdc.mint(address(pair), amountXInForSwap); pair.swap(true, ALICE); - assertEq(token18D.balanceOf(ALICE), amountYOutForSwap); + assertEq(weth.balanceOf(ALICE), amountYOutForSwap); } function testSwapYtoXConsecutiveBinFromGetSwapOut() public { @@ -191,13 +191,13 @@ contract LiquidityBinPairSwapsTest is TestHelper { addLiquidity(amountYInLiquidity, startId, 9, 0); - (uint256 amountXOutForSwap, ) = router.getSwapOut(pair, amountYInForSwap, false); + (uint256 amountXOutForSwap,) = router.getSwapOut(pair, amountYInForSwap, false); - token18D.mint(address(pair), amountYInForSwap); + weth.mint(address(pair), amountYInForSwap); pair.swap(false, ALICE); - assertEq(token6D.balanceOf(ALICE), amountXOutForSwap); + assertEq(usdc.balanceOf(ALICE), amountXOutForSwap); } function testSwapXtoYConsecutiveBinFromGetSwapOut() public { @@ -207,13 +207,13 @@ contract LiquidityBinPairSwapsTest is TestHelper { addLiquidity(amountYInLiquidity, startId, 9, 0); - (uint256 amountYOutForSwap, ) = router.getSwapOut(pair, amountXInForSwap, true); + (uint256 amountYOutForSwap,) = router.getSwapOut(pair, amountXInForSwap, true); - token6D.mint(address(pair), amountXInForSwap); + usdc.mint(address(pair), amountXInForSwap); pair.swap(true, ALICE); - assertEq(token18D.balanceOf(ALICE), amountYOutForSwap); + assertEq(weth.balanceOf(ALICE), amountYOutForSwap); } function testSwapYtoXDistantBinsFromGetSwapIn() public { @@ -223,13 +223,13 @@ contract LiquidityBinPairSwapsTest is TestHelper { addLiquidity(amountYInLiquidity, startId, 9, 100); - (uint256 amountYInForSwap, ) = router.getSwapIn(pair, amountXOutForSwap, false); + (uint256 amountYInForSwap,) = router.getSwapIn(pair, amountXOutForSwap, false); - token18D.mint(address(pair), amountYInForSwap); + weth.mint(address(pair), amountYInForSwap); pair.swap(false, ALICE); - assertEq(token6D.balanceOf(ALICE), amountXOutForSwap); + assertEq(usdc.balanceOf(ALICE), amountXOutForSwap); } function testSwapXtoYDistantBinsFromGetSwapIn() public { @@ -239,13 +239,13 @@ contract LiquidityBinPairSwapsTest is TestHelper { addLiquidity(amountYInLiquidity, startId, 9, 100); - (uint256 amountXInForSwap, ) = router.getSwapIn(pair, amountYOutForSwap, true); + (uint256 amountXInForSwap,) = router.getSwapIn(pair, amountYOutForSwap, true); - token6D.mint(address(pair), amountXInForSwap); + usdc.mint(address(pair), amountXInForSwap); pair.swap(true, ALICE); - assertEq(token18D.balanceOf(ALICE), amountYOutForSwap); + assertEq(weth.balanceOf(ALICE), amountYOutForSwap); } function testSwapYtoXDistantBinsFromGetSwapOut() public { @@ -255,13 +255,13 @@ contract LiquidityBinPairSwapsTest is TestHelper { addLiquidity(amountYInLiquidity, startId, 9, 100); - (uint256 amountXOutForSwap, ) = router.getSwapOut(pair, amountYInForSwap, false); + (uint256 amountXOutForSwap,) = router.getSwapOut(pair, amountYInForSwap, false); - token18D.mint(address(pair), amountYInForSwap); + weth.mint(address(pair), amountYInForSwap); pair.swap(false, ALICE); - assertEq(token6D.balanceOf(ALICE), amountXOutForSwap); + assertEq(usdc.balanceOf(ALICE), amountXOutForSwap); } function testSwapXtoYDistantBinsFromGetSwapOut() public { @@ -271,13 +271,13 @@ contract LiquidityBinPairSwapsTest is TestHelper { addLiquidity(amountYInLiquidity, startId, 9, 100); - (uint256 amountYOutForSwap, ) = router.getSwapOut(pair, amountXInForSwap, true); + (uint256 amountYOutForSwap,) = router.getSwapOut(pair, amountXInForSwap, true); - token6D.mint(address(pair), amountXInForSwap); + usdc.mint(address(pair), amountXInForSwap); pair.swap(true, ALICE); - assertEq(token18D.balanceOf(ALICE), amountYOutForSwap); + assertEq(weth.balanceOf(ALICE), amountYOutForSwap); } function testInvalidTokenPathReverts() public { @@ -285,8 +285,8 @@ contract LiquidityBinPairSwapsTest is TestHelper { uint256 _amountOutMinAVAX = 10e18; uint256[] memory _pairBinSteps = new uint256[](1); IERC20[] memory _tokenPath = new IERC20[](2); - _tokenPath[0] = token6D; - _tokenPath[1] = token18D; + _tokenPath[0] = usdc; + _tokenPath[1] = weth; vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, _tokenPath[1])); router.swapExactTokensForAVAX(_amountIn, _amountOutMinAVAX, _pairBinSteps, _tokenPath, DEV, block.timestamp); @@ -296,12 +296,7 @@ contract LiquidityBinPairSwapsTest is TestHelper { vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, _tokenPath[1])); router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - _amountIn, - _amountOutMinAVAX, - _pairBinSteps, - _tokenPath, - DEV, - block.timestamp + _amountIn, _amountOutMinAVAX, _pairBinSteps, _tokenPath, DEV, block.timestamp ); vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, _tokenPath[0])); @@ -309,11 +304,7 @@ contract LiquidityBinPairSwapsTest is TestHelper { vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, _tokenPath[0])); router.swapExactAVAXForTokensSupportingFeeOnTransferTokens( - _amountIn, - _pairBinSteps, - _tokenPath, - DEV, - block.timestamp + _amountIn, _pairBinSteps, _tokenPath, DEV, block.timestamp ); } } diff --git a/test/LBPair.t.sol b/test_old/LBPair.t.sol similarity index 87% rename from test/LBPair.t.sol rename to test_old/LBPair.t.sol index 866ad236..fa0435a6 100644 --- a/test/LBPair.t.sol +++ b/test_old/LBPair.t.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "test/helpers/TestHelper.sol"; contract LiquidityBinPairTest is TestHelper { ILBPair _LBPairImplementation; - function setUp() public { - token6D = new ERC20MockDecimals(6); - token18D = new ERC20MockDecimals(18); + function setUp() public override { + usdc = new ERC20Mock(6); + weth = new ERC20Mock(18); factory = new LBFactory(DEV, 8e14); _LBPairImplementation = new LBPair(factory); @@ -17,7 +17,7 @@ contract LiquidityBinPairTest is TestHelper { addAllAssetsToQuoteWhitelist(factory); router = new LBRouter(ILBFactory(DEV), IJoeFactory(DEV), IWAVAX(DEV)); - pair = createLBPairDefaultFees(token6D, token18D); + pair = createLBPairDefaultFees(usdc, weth); } function testPendingFeesNotIncreasingReverts() public { @@ -69,8 +69,8 @@ contract LiquidityBinPairTest is TestHelper { function testDistributionOverflowReverts() public { uint256 amount = 10e18; - token6D.mint(address(pair), amount); - token18D.mint(address(pair), amount); + usdc.mint(address(pair), amount); + weth.mint(address(pair), amount); uint256[] memory _ids; uint256[] memory _distributionX; uint256[] memory _distributionY; @@ -139,12 +139,12 @@ contract LiquidityBinPairTest is TestHelper { vm.startPrank(address(factory)); vm.expectRevert(LBPair__AddressZero.selector); - _LBPairImplementation.initialize(IERC20(address(0)), token18D, _activeId, _sampleLifetime, _packedFeeParameters); + _LBPairImplementation.initialize(IERC20(address(0)), weth, _activeId, _sampleLifetime, _packedFeeParameters); vm.expectRevert(LBPair__AddressZero.selector); - _LBPairImplementation.initialize(token18D, IERC20(address(0)), _activeId, _sampleLifetime, _packedFeeParameters); + _LBPairImplementation.initialize(weth, IERC20(address(0)), _activeId, _sampleLifetime, _packedFeeParameters); - _LBPairImplementation.initialize(token18D, token6D, _activeId, _sampleLifetime, _packedFeeParameters); + _LBPairImplementation.initialize(weth, usdc, _activeId, _sampleLifetime, _packedFeeParameters); vm.expectRevert(LBPair__AlreadyInitialized.selector); - _LBPairImplementation.initialize(token18D, token6D, _activeId, _sampleLifetime, _packedFeeParameters); + _LBPairImplementation.initialize(weth, usdc, _activeId, _sampleLifetime, _packedFeeParameters); } } diff --git a/test/LBQuoter.t.sol b/test_old/LBQuoter.t.sol similarity index 90% rename from test/LBQuoter.t.sol rename to test_old/LBQuoter.t.sol index 63ce4a88..4e5fa9ca 100644 --- a/test/LBQuoter.t.sol +++ b/test_old/LBQuoter.t.sol @@ -2,24 +2,28 @@ pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "../helpers/TestHelper.sol"; +import {Addresses} from "./Addresses.sol"; -contract LiquidityBinQuoterTest is TestHelper { +contract _LiquidityBinQuoterTest is TestHelper { using Math512Bits for uint256; IJoeFactory private factoryV1; - ERC20MockDecimals private testWavax; + ERC20Mock private testWavax; + IJoeRouter02 internal routerV1; uint256 private defaultBaseFee = DEFAULT_BIN_STEP * uint256(DEFAULT_BASE_FACTOR) * 1e10; - function setUp() public { + function setUp() public override { vm.createSelectFork(vm.rpcUrl("avalanche"), 19_358_000); - usdc = new ERC20MockDecimals(6); - usdt = new ERC20MockDecimals(6); - testWavax = new ERC20MockDecimals(18); + usdc = new ERC20Mock(6); + usdt = new ERC20Mock(6); + testWavax = new ERC20Mock(18); - factoryV1 = IJoeFactory(JOE_V1_FACTORY_ADDRESS); + routerV1 = IJoeRouter02(Addresses.JOE_V1_ROUTER_ADDRESS); + + factoryV1 = IJoeFactory(Addresses.JOE_V1_FACTORY_ADDRESS); factory = new LBFactory(DEV, 8e14); ILBPair _LBPairImplementation = new LBPair(factory); factory.setLBPairImplementation(address(_LBPairImplementation)); @@ -27,8 +31,9 @@ contract LiquidityBinQuoterTest is TestHelper { factory.addQuoteAsset(testWavax); setDefaultFactoryPresets(DEFAULT_BIN_STEP); - router = new LBRouter(factory, IJoeFactory(JOE_V1_FACTORY_ADDRESS), IWAVAX(WAVAX_AVALANCHE_ADDRESS)); - quoter = new LBQuoter(address(router), JOE_V1_FACTORY_ADDRESS, address(factory)); + router = + new LBRouter(factory, IJoeFactory(Addresses.JOE_V1_FACTORY_ADDRESS), IWAVAX(Addresses.WAVAX_AVALANCHE_ADDRESS)); + quoter = new LBQuoter(address(router), Addresses.JOE_V1_FACTORY_ADDRESS, address(factory)); // Minting and giving approval testWavax.mint(DEV, 1_000_000e18); @@ -47,7 +52,7 @@ contract LiquidityBinQuoterTest is TestHelper { function testConstructor() public { assertEq(address(quoter.routerV2()), address(router)); - assertEq(address(quoter.factoryV1()), JOE_V1_FACTORY_ADDRESS); + assertEq(address(quoter.factoryV1()), Addresses.JOE_V1_FACTORY_ADDRESS); assertEq(address(quoter.factoryV2()), address(factory)); } @@ -122,11 +127,8 @@ contract LiquidityBinQuoterTest is TestHelper { // Fees are expressed in the In token // To get the theorical amountIn without slippage but with the fees, the calculation is amountIn = amountOut / price / (1 - fees) - uint256 quoteAmountInWithFees = JoeLibrary.quote( - (amountOut * 1e18) / (1e18 - quote.fees[0]), - 20_000e6, - 1_000e18 - ); + uint256 quoteAmountInWithFees = + JoeLibrary.quote((amountOut * 1e18) / (1e18 - quote.fees[0]), 20_000e6, 1_000e18); assertApproxEqAbs(quote.virtualAmountsWithoutSlippage[0], quoteAmountInWithFees, 1e12); } diff --git a/test/LBRouter.FeesOnLiquidityAdd.t.sol b/test_old/LBRouter.FeesOnLiquidityAdd.t.sol similarity index 78% rename from test/LBRouter.FeesOnLiquidityAdd.t.sol rename to test_old/LBRouter.FeesOnLiquidityAdd.t.sol index 1a760fe8..e5ffcac5 100644 --- a/test/LBRouter.FeesOnLiquidityAdd.t.sol +++ b/test_old/LBRouter.FeesOnLiquidityAdd.t.sol @@ -2,14 +2,14 @@ pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "test/helpers/TestHelper.sol"; contract LiquidityBinRouterTest is TestHelper { event AVAXreceived(); - function setUp() public { - token6D = new ERC20MockDecimals(6); - token18D = new ERC20MockDecimals(18); + function setUp() public override { + usdc = new ERC20Mock(6); + weth = new ERC20Mock(18); wavax = new WAVAX(); uint16 binStep = 100; factory = new LBFactory(DEV, 8e14); @@ -28,9 +28,9 @@ contract LiquidityBinRouterTest is TestHelper { DEFAULT_SAMPLE_LIFETIME ); - router = new LBRouter(factory, IJoeFactory(JOE_V1_FACTORY_ADDRESS), IWAVAX(address(wavax))); + router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(wavax))); - pair = LBPair(address(factory.createLBPair(token6D, token18D, ID_ONE, 100))); + pair = LBPair(address(factory.createLBPair(usdc, weth, ID_ONE, 100))); } function testFeeOnActiveBin() public { @@ -49,13 +49,13 @@ contract LiquidityBinRouterTest is TestHelper { _distributionY[0] = Constants.PRECISION; vm.prank(BOB); - token18D.approve(address(router), _amountYIn); + weth.approve(address(router), _amountYIn); - token18D.mint(BOB, _amountYIn); + weth.mint(BOB, _amountYIn); ILBRouter.LiquidityParameters memory _liquidityParameters = ILBRouter.LiquidityParameters( - token6D, - token18D, + usdc, + weth, binStep, 0, _amountYIn, @@ -77,17 +77,17 @@ contract LiquidityBinRouterTest is TestHelper { _distributionX[0] = Constants.PRECISION; _distributionY[0] = 0; - token6D.mint(ALICE, amountXIn); + usdc.mint(ALICE, amountXIn); vm.prank(ALICE); - token6D.approve(address(router), amountXIn); + usdc.approve(address(router), amountXIn); uint256 feesXTotal; - (feesXTotal, , , ) = pair.getGlobalFees(); + (feesXTotal,,,) = pair.getGlobalFees(); assertEq(feesXTotal, 0); _liquidityParameters = ILBRouter.LiquidityParameters( - token6D, - token18D, + usdc, + weth, binStep, amountXIn, 0, @@ -115,9 +115,9 @@ contract LiquidityBinRouterTest is TestHelper { vm.prank(ALICE); pair.setApprovalForAll(address(router), true); vm.prank(ALICE); - router.removeLiquidity(token6D, token18D, binStep, 0, 0, ids, amounts, ALICE, block.timestamp); + router.removeLiquidity(usdc, weth, binStep, 0, 0, ids, amounts, ALICE, block.timestamp); - (feesXTotal, , , ) = pair.getGlobalFees(); + (feesXTotal,,,) = pair.getGlobalFees(); assertGt(feesXTotal, amountXIn / 199); //remove BOB's liquidity to ALICE account @@ -128,9 +128,9 @@ contract LiquidityBinRouterTest is TestHelper { vm.prank(BOB); pair.setApprovalForAll(address(router), true); vm.prank(BOB); - router.removeLiquidity(token6D, token18D, binStep, 0, 0, ids, amounts, ALICE, block.timestamp); + router.removeLiquidity(usdc, weth, binStep, 0, 0, ids, amounts, ALICE, block.timestamp); - uint256 ALICE6DbalanceAfterSecondRemove = token6D.balanceOf(ALICE); + uint256 ALICE6DbalanceAfterSecondRemove = usdc.balanceOf(ALICE); assertEq(ALICE6DbalanceAfterSecondRemove + feesXTotal, amountXIn); } @@ -151,13 +151,13 @@ contract LiquidityBinRouterTest is TestHelper { _distributionY[0] = 0; vm.prank(BOB); - token6D.approve(address(router), _amountXIn); + usdc.approve(address(router), _amountXIn); - token6D.mint(BOB, _amountXIn); + usdc.mint(BOB, _amountXIn); ILBRouter.LiquidityParameters memory _liquidityParameters = ILBRouter.LiquidityParameters( - token6D, - token18D, + usdc, + weth, binStep, _amountXIn, 0, @@ -179,17 +179,17 @@ contract LiquidityBinRouterTest is TestHelper { _distributionX[0] = 0; _distributionY[0] = Constants.PRECISION; - token18D.mint(ALICE, amountYIn); + weth.mint(ALICE, amountYIn); vm.prank(ALICE); - token18D.approve(address(router), amountYIn); + weth.approve(address(router), amountYIn); uint256 feesYTotal; - (, feesYTotal, , ) = pair.getGlobalFees(); + (, feesYTotal,,) = pair.getGlobalFees(); assertEq(feesYTotal, 0); _liquidityParameters = ILBRouter.LiquidityParameters( - token6D, - token18D, + usdc, + weth, binStep, 0, amountYIn, @@ -217,7 +217,7 @@ contract LiquidityBinRouterTest is TestHelper { vm.prank(ALICE); pair.setApprovalForAll(address(router), true); vm.prank(ALICE); - router.removeLiquidity(token6D, token18D, binStep, 0, 0, ids, amounts, ALICE, block.timestamp); + router.removeLiquidity(usdc, weth, binStep, 0, 0, ids, amounts, ALICE, block.timestamp); //remove BOB's liquidity to ALICE account for (uint256 i; i < _numberBins; i++) { @@ -227,11 +227,11 @@ contract LiquidityBinRouterTest is TestHelper { vm.prank(BOB); pair.setApprovalForAll(address(router), true); vm.prank(BOB); - router.removeLiquidity(token6D, token18D, binStep, 0, 0, ids, amounts, ALICE, block.timestamp); + router.removeLiquidity(usdc, weth, binStep, 0, 0, ids, amounts, ALICE, block.timestamp); - uint256 ALICE6DbalanceAfterSecondRemove = token18D.balanceOf(ALICE); + uint256 ALICE6DbalanceAfterSecondRemove = weth.balanceOf(ALICE); - (, feesYTotal, , ) = pair.getGlobalFees(); + (, feesYTotal,,) = pair.getGlobalFees(); assertGt(feesYTotal, amountYIn / 199); assertEq(ALICE6DbalanceAfterSecondRemove + feesYTotal, amountYIn); } diff --git a/test_old/LBRouter.Fork.Swaps.t.sol b/test_old/LBRouter.Fork.Swaps.t.sol new file mode 100644 index 00000000..150c7b5e --- /dev/null +++ b/test_old/LBRouter.Fork.Swaps.t.sol @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.10; + +import "test/helpers/TestHelper.sol"; + +import {Addresses} from "./Addresses.sol"; + +contract LiquidityBinRouterForkTest is TestHelper { + LBPair internal pair0; + LBPair internal pair1; + LBPair internal pair2; + LBPair internal pair3; + LBPair internal taxTokenPair; + + function setUp() public override { + vm.createSelectFork(vm.rpcUrl("avalanche"), 19_358_000); + super.setUp(); + // usdc = new ERC20Mock(6); + // link = new ERC20Mock(10); + // wbtc = new ERC20Mock(12); + // weth = new ERC20Mock(18); + // wbtc = new ERC20Mock(24); + + // taxToken = new ERC20TransferTaxMock(); + + wavax = WAVAX(Addresses.WAVAX_AVALANCHE_ADDRESS); + usdc = ERC20Mock(Addresses.USDC_AVALANCHE_ADDRESS); + + // factory = new LBFactory(DEV, 8e14); + factory.addQuoteAsset(IERC20(address(wavax))); + factory.addQuoteAsset(IERC20(address(usdc))); + // ILBPair _LBPairImplementation = new LBPair(factory); + // factory.setLBPairImplementation(address(_LBPairImplementation)); + // addAllAssetsToQuoteWhitelist(factory); + // setDefaultFactoryPresets(DEFAULT_BIN_STEP); + + router = new LBRouter(factory, IJoeFactory(Addresses.JOE_V1_FACTORY_ADDRESS), IWAVAX(address(wavax))); + + pair = createLBPairDefaultFees(usdc, weth); + addLiquidityFromRouter(usdc, weth, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); + + pair0 = createLBPairDefaultFees(usdc, link); + addLiquidityFromRouter(usdc, link, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); + pair1 = createLBPairDefaultFees(link, wbtc); + addLiquidityFromRouter(link, wbtc, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); + pair2 = createLBPairDefaultFees(wbtc, weth); + addLiquidityFromRouter(wbtc, weth, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); + pair3 = createLBPairDefaultFees(weth, bnb); + addLiquidityFromRouter(weth, bnb, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); + + taxTokenPair = createLBPairDefaultFees(taxToken, wavax); + addLiquidityFromRouter( + ERC20Mock(address(taxToken)), ERC20Mock(address(wavax)), 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP + ); + + pairWavax = createLBPairDefaultFees(usdc, wavax); + addLiquidityFromRouter(usdc, ERC20Mock(address(wavax)), 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); + } + + function testSwapExactTokensForTokensMultiplePairsWithV1() public { + if (block.number < 1000) { + console.log("fork mainnet for V1 testing support"); + return; + } + + uint256 amountIn = 1e18; + + deal(address(usdc), DEV, amountIn); + + usdc.approve(address(router), amountIn); + + IERC20[] memory tokenList; + uint256[] memory pairVersions; + + tokenList = new IERC20[](3); + tokenList[0] = usdc; + tokenList[1] = wavax; + tokenList[2] = usdc; + + pairVersions = new uint256[](2); + pairVersions[0] = DEFAULT_BIN_STEP; + pairVersions[1] = 0; + + router.swapExactTokensForTokens(amountIn, 0, pairVersions, tokenList, DEV, block.timestamp); + + assertGt(usdc.balanceOf(DEV), 0); + + tokenList[0] = usdc; + tokenList[1] = wavax; + tokenList[2] = usdc; + + pairVersions[0] = 0; + pairVersions[1] = DEFAULT_BIN_STEP; + + uint256 balanceBefore = usdc.balanceOf(DEV); + + usdc.approve(address(router), usdc.balanceOf(DEV)); + router.swapExactTokensForTokens(usdc.balanceOf(DEV), 0, pairVersions, tokenList, DEV, block.timestamp); + + assertGt(usdc.balanceOf(DEV) - balanceBefore, 0); + } + + function testSwapTokensForExactTokensMultiplePairsWithV1() public { + if (block.number < 1000) { + console.log("fork mainnet for V1 testing support"); + return; + } + + uint256 amountOut = 1e6; + + deal(address(usdc), DEV, 1e6); + + usdc.approve(address(router), 100e18); + + IERC20[] memory tokenList; + uint256[] memory pairVersions; + + tokenList = new IERC20[](3); + tokenList[0] = usdc; + tokenList[1] = wavax; + tokenList[2] = usdc; + + pairVersions = new uint256[](2); + pairVersions[0] = DEFAULT_BIN_STEP; + pairVersions[1] = 0; + + router.swapTokensForExactTokens(amountOut, 100e18, pairVersions, tokenList, DEV, block.timestamp); + + assertEq(usdc.balanceOf(DEV), amountOut); + + tokenList[0] = usdc; + tokenList[1] = wavax; + tokenList[2] = usdc; + + pairVersions[0] = 0; + pairVersions[1] = DEFAULT_BIN_STEP; + + uint256 balanceBefore = usdc.balanceOf(DEV); + + usdc.approve(address(router), usdc.balanceOf(DEV)); + router.swapTokensForExactTokens(amountOut, usdc.balanceOf(DEV), pairVersions, tokenList, DEV, block.timestamp); + + assertEq(usdc.balanceOf(DEV) - balanceBefore, amountOut); + + vm.stopPrank(); + } + + function testTaxTokenSwappedOnV1Pairs() public { + if (block.number < 1000) { + console.log("fork mainnet for V1 testing support"); + return; + } + uint256 amountIn = 100e18; + + IJoeFactory factoryv1 = IJoeFactory(Addresses.JOE_V1_FACTORY_ADDRESS); + //create taxToken-AVAX pair in DEXv1 + address taxPairv11 = factoryv1.createPair(address(taxToken), address(wavax)); + taxToken.mint(taxPairv11, amountIn); + vm.deal(DEV, amountIn); + wavax.deposit{value: amountIn}(); + wavax.transfer(taxPairv11, amountIn); + IJoePair(taxPairv11).mint(DEV); + + //create taxToken-usdc pair in DEXv1 + address taxPairv12 = factoryv1.createPair(address(taxToken), address(usdc)); + taxToken.mint(taxPairv12, amountIn); + usdc.mint(taxPairv12, amountIn); + IJoePair(taxPairv12).mint(DEV); + + usdc.mint(DEV, amountIn); + usdc.approve(address(router), amountIn); + + IERC20[] memory tokenList; + uint256[] memory pairVersions; + + tokenList = new IERC20[](3); + tokenList[0] = usdc; + tokenList[1] = taxToken; + tokenList[2] = wavax; + + pairVersions = new uint256[](2); + pairVersions[0] = 0; + pairVersions[1] = 0; + uint256 amountIn2 = 1e18; + + vm.expectRevert("Joe: K"); + router.swapExactTokensForTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); + router.swapExactTokensForTokensSupportingFeeOnTransferTokens( + amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp + ); + vm.deal(DEV, amountIn2); + vm.expectRevert("Joe: K"); + router.swapExactTokensForAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); + router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp + ); + + tokenList[0] = wavax; + tokenList[1] = taxToken; + tokenList[2] = usdc; + + vm.deal(DEV, amountIn2); + vm.expectRevert("Joe: K"); + router.swapExactAVAXForTokens{value: amountIn2}(0, pairVersions, tokenList, DEV, block.timestamp); + router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn2}( + 0, pairVersions, tokenList, DEV, block.timestamp + ); + } + + function testSwappingOnNotExistingV1PairReverts() public { + IERC20[] memory tokenListAvaxIn; + IERC20[] memory tokenList; + uint256[] memory pairVersions; + + uint256 amountIn2 = 1e18; + vm.deal(DEV, amountIn2); + + tokenList = new IERC20[](3); + tokenList[0] = usdc; + tokenList[1] = weth; + tokenList[2] = wavax; + + pairVersions = new uint256[](2); + pairVersions[0] = DEFAULT_BIN_STEP; + pairVersions[1] = 0; + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); + router.swapExactTokensForTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); + router.swapExactTokensForAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); + router.swapTokensForExactTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); + router.swapTokensForExactAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); + router.swapExactTokensForTokensSupportingFeeOnTransferTokens( + amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp + ); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); + router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp + ); + + tokenListAvaxIn = new IERC20[](3); + tokenListAvaxIn[0] = wavax; + tokenListAvaxIn[1] = usdc; + tokenListAvaxIn[2] = weth; + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, usdc, weth, pairVersions[1])); + router.swapExactAVAXForTokens{value: amountIn2}(0, pairVersions, tokenListAvaxIn, DEV, block.timestamp); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, usdc, weth, pairVersions[1])); + router.swapAVAXForExactTokens{value: amountIn2}(0, pairVersions, tokenListAvaxIn, DEV, block.timestamp); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, usdc, weth, pairVersions[1])); + router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn2}( + 0, pairVersions, tokenListAvaxIn, DEV, block.timestamp + ); + } + + receive() external payable {} +} diff --git a/test/LBRouter.Liquidity.t.sol b/test_old/LBRouter.Liquidity.t.sol similarity index 67% rename from test/LBRouter.Liquidity.t.sol rename to test_old/LBRouter.Liquidity.t.sol index ea72f6b3..8f4ec6f4 100644 --- a/test/LBRouter.Liquidity.t.sol +++ b/test_old/LBRouter.Liquidity.t.sol @@ -2,14 +2,14 @@ pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "test/helpers/TestHelper.sol"; contract LiquidityBinRouterTest is TestHelper { event AVAXreceived(); - function setUp() public { - token6D = new ERC20MockDecimals(6); - token18D = new ERC20MockDecimals(18); + function setUp() public override { + usdc = new ERC20Mock(6); + weth = new ERC20Mock(18); wavax = new WAVAX(); factory = new LBFactory(DEV, 8e14); @@ -17,9 +17,9 @@ contract LiquidityBinRouterTest is TestHelper { factory.setLBPairImplementation(address(_LBPairImplementation)); setDefaultFactoryPresets(DEFAULT_BIN_STEP); addAllAssetsToQuoteWhitelist(factory); - router = new LBRouter(factory, IJoeFactory(JOE_V1_FACTORY_ADDRESS), IWAVAX(address(wavax))); + router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(wavax))); - pair = createLBPairDefaultFees(token6D, token18D); + pair = createLBPairDefaultFees(usdc, weth); } function testAddLiquidityNoSlippage() public { @@ -28,15 +28,8 @@ contract LiquidityBinRouterTest is TestHelper { uint24 _numberBins = 9; uint24 _gap = 2; - (int256[] memory _deltaIds, , , uint256 amountXIn) = addLiquidityFromRouter( - token6D, - token18D, - _amountYIn, - _startId, - _numberBins, - _gap, - DEFAULT_BIN_STEP - ); + (int256[] memory _deltaIds,,, uint256 amountXIn) = + addLiquidityFromRouter(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); uint256[] memory amounts = new uint256[](_numberBins); uint256[] memory ids = new uint256[](_numberBins); @@ -58,19 +51,11 @@ contract LiquidityBinRouterTest is TestHelper { pair.setApprovalForAll(address(router), true); router.removeLiquidity( - token6D, - token18D, - DEFAULT_BIN_STEP, - totalXbalance, - totalYBalance, - ids, - amounts, - DEV, - block.timestamp + usdc, weth, DEFAULT_BIN_STEP, totalXbalance, totalYBalance, ids, amounts, DEV, block.timestamp ); - assertEq(token6D.balanceOf(DEV), amountXIn); - assertEq(token18D.balanceOf(DEV), _amountYIn); + assertEq(usdc.balanceOf(DEV), amountXIn); + assertEq(weth.balanceOf(DEV), _amountYIn); } function testRemoveLiquidityReverseOrder() public { @@ -79,15 +64,8 @@ contract LiquidityBinRouterTest is TestHelper { uint24 _numberBins = 9; uint24 _gap = 2; - (int256[] memory _deltaIds, , , uint256 amountXIn) = addLiquidityFromRouter( - token6D, - token18D, - _amountYIn, - _startId, - _numberBins, - _gap, - DEFAULT_BIN_STEP - ); + (int256[] memory _deltaIds,,, uint256 amountXIn) = + addLiquidityFromRouter(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); uint256[] memory amounts = new uint256[](_numberBins); uint256[] memory ids = new uint256[](_numberBins); @@ -109,19 +87,11 @@ contract LiquidityBinRouterTest is TestHelper { pair.setApprovalForAll(address(router), true); router.removeLiquidity( - token18D, - token6D, - DEFAULT_BIN_STEP, - _amountYIn, - totalXbalance, - ids, - amounts, - DEV, - block.timestamp + weth, usdc, DEFAULT_BIN_STEP, _amountYIn, totalXbalance, ids, amounts, DEV, block.timestamp ); - assertEq(token6D.balanceOf(DEV), amountXIn); - assertEq(token18D.balanceOf(DEV), _amountYIn); + assertEq(usdc.balanceOf(DEV), amountXIn); + assertEq(weth.balanceOf(DEV), _amountYIn); } function testRemoveLiquiditySlippageReverts() public { @@ -130,15 +100,8 @@ contract LiquidityBinRouterTest is TestHelper { uint24 _numberBins = 23; uint24 _gap = 2; - (int256[] memory _deltaIds, , , uint256 amountXIn) = addLiquidityFromRouter( - token6D, - token18D, - _amountYIn, - _startId, - _numberBins, - _gap, - DEFAULT_BIN_STEP - ); + (int256[] memory _deltaIds,,, uint256 amountXIn) = + addLiquidityFromRouter(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); uint256[] memory amounts = new uint256[](_numberBins); uint256[] memory ids = new uint256[](_numberBins); @@ -160,60 +123,30 @@ contract LiquidityBinRouterTest is TestHelper { pair.setApprovalForAll(address(router), true); vm.expectRevert( abi.encodeWithSelector( - LBRouter__AmountSlippageCaught.selector, - totalXbalance + 1, - totalXbalance, - totalYBalance, - totalYBalance + LBRouter__AmountSlippageCaught.selector, totalXbalance + 1, totalXbalance, totalYBalance, totalYBalance ) ); router.removeLiquidity( - token6D, - token18D, - DEFAULT_BIN_STEP, - totalXbalance + 1, - totalYBalance, - ids, - amounts, - DEV, - block.timestamp + usdc, weth, DEFAULT_BIN_STEP, totalXbalance + 1, totalYBalance, ids, amounts, DEV, block.timestamp ); vm.expectRevert( abi.encodeWithSelector( - LBRouter__AmountSlippageCaught.selector, - totalXbalance, - totalXbalance, - totalYBalance + 1, - totalYBalance + LBRouter__AmountSlippageCaught.selector, totalXbalance, totalXbalance, totalYBalance + 1, totalYBalance ) ); router.removeLiquidity( - token6D, - token18D, - DEFAULT_BIN_STEP, - totalXbalance, - totalYBalance + 1, - ids, - amounts, - DEV, - block.timestamp + usdc, weth, DEFAULT_BIN_STEP, totalXbalance, totalYBalance + 1, ids, amounts, DEV, block.timestamp ); } function testAddLiquidityAVAX() public { - pair = createLBPairDefaultFees(token6D, wavax); + pair = createLBPairDefaultFees(usdc, wavax); uint24 _numberBins = 23; uint256 _amountYIn = 100e18; //AVAX uint24 _gap = 2; - (int256[] memory _deltaIds, , , uint256 amountXIn) = addLiquidityFromRouter( - token6D, - ERC20MockDecimals(address(wavax)), - _amountYIn, - ID_ONE, - _numberBins, - _gap, - DEFAULT_BIN_STEP + (int256[] memory _deltaIds,,, uint256 amountXIn) = addLiquidityFromRouter( + usdc, ERC20Mock(address(wavax)), _amountYIn, ID_ONE, _numberBins, _gap, DEFAULT_BIN_STEP ); uint256[] memory amounts = new uint256[](_numberBins); @@ -238,33 +171,20 @@ contract LiquidityBinRouterTest is TestHelper { uint256 AVAXBalanceBefore = address(DEV).balance; { router.removeLiquidityAVAX( - token6D, - DEFAULT_BIN_STEP, - totalXbalance, - totalYBalance, - ids, - amounts, - DEV, - block.timestamp + usdc, DEFAULT_BIN_STEP, totalXbalance, totalYBalance, ids, amounts, DEV, block.timestamp ); } - assertEq(token6D.balanceOf(DEV), amountXIn); + assertEq(usdc.balanceOf(DEV), amountXIn); assertEq(address(DEV).balance - AVAXBalanceBefore, totalYBalance); } function testAddLiquidityAVAXReversed() public { - pair = createLBPairDefaultFees(wavax, token6D); + pair = createLBPairDefaultFees(wavax, usdc); uint24 _numberBins = 21; uint256 amountTokenIn = 100e18; uint24 _gap = 2; - (int256[] memory _deltaIds, , , uint256 _amountAVAXIn) = addLiquidityFromRouter( - ERC20MockDecimals(address(wavax)), - token6D, - amountTokenIn, - ID_ONE, - _numberBins, - _gap, - DEFAULT_BIN_STEP + (int256[] memory _deltaIds,,, uint256 _amountAVAXIn) = addLiquidityFromRouter( + ERC20Mock(address(wavax)), usdc, amountTokenIn, ID_ONE, _numberBins, _gap, DEFAULT_BIN_STEP ); uint256[] memory amounts = new uint256[](_numberBins); uint256[] memory ids = new uint256[](_numberBins); @@ -287,30 +207,23 @@ contract LiquidityBinRouterTest is TestHelper { uint256 AVAXBalanceBefore = address(DEV).balance; { router.removeLiquidityAVAX( - token6D, - DEFAULT_BIN_STEP, - totalYBalance, - totalXbalance, - ids, - amounts, - DEV, - block.timestamp + usdc, DEFAULT_BIN_STEP, totalYBalance, totalXbalance, ids, amounts, DEV, block.timestamp ); } - assertEq(token6D.balanceOf(DEV), amountTokenIn); + assertEq(usdc.balanceOf(DEV), amountTokenIn); assertEq(address(DEV).balance - AVAXBalanceBefore, totalXbalance); } function testAddLiquidityTaxToken() public { - taxToken = new ERC20WithTransferTax(); + taxToken = new ERC20TransferTaxMock(); pair = createLBPairDefaultFees(taxToken, wavax); uint24 _numberBins = 9; uint256 _amountAVAXIn = 100e18; uint24 _gap = 2; - (int256[] memory _deltaIds, , , uint256 amountTokenIn) = addLiquidityFromRouter( - ERC20MockDecimals(address(taxToken)), - ERC20MockDecimals(address(wavax)), + (int256[] memory _deltaIds,,, uint256 amountTokenIn) = addLiquidityFromRouter( + ERC20Mock(address(taxToken)), + ERC20Mock(address(wavax)), _amountAVAXIn, ID_ONE, _numberBins, @@ -337,26 +250,11 @@ contract LiquidityBinRouterTest is TestHelper { vm.expectRevert(bytes("ERC20: burn amount exceeds balance")); router.removeLiquidityAVAX( - taxToken, - DEFAULT_BIN_STEP, - totalXbalance, - _amountAVAXIn, - ids, - amounts, - DEV, - block.timestamp + taxToken, DEFAULT_BIN_STEP, totalXbalance, _amountAVAXIn, ids, amounts, DEV, block.timestamp ); router.removeLiquidity( - taxToken, - wavax, - DEFAULT_BIN_STEP, - totalXbalance, - _amountAVAXIn, - ids, - amounts, - DEV, - block.timestamp + taxToken, wavax, DEFAULT_BIN_STEP, totalXbalance, _amountAVAXIn, ids, amounts, DEV, block.timestamp ); assertEq(taxToken.balanceOf(DEV), amountTokenIn / 4 + 1); //2 transfers with 50% tax @@ -369,18 +267,11 @@ contract LiquidityBinRouterTest is TestHelper { uint24 _numberBins = 9; uint24 _gap = 2; - addLiquidityFromRouter(token6D, token18D, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); + addLiquidityFromRouter(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - factory.setLBPairIgnored(token6D, token18D, DEFAULT_BIN_STEP, true); - ILBRouter.LiquidityParameters memory _liquidityParameters = prepareLiquidityParameters( - token6D, - token18D, - _amountYIn, - _startId, - _numberBins, - _gap, - DEFAULT_BIN_STEP - ); + factory.setLBPairIgnored(usdc, weth, DEFAULT_BIN_STEP, true); + ILBRouter.LiquidityParameters memory _liquidityParameters = + prepareLiquidityParameters(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); router.addLiquidity(_liquidityParameters); } @@ -391,45 +282,32 @@ contract LiquidityBinRouterTest is TestHelper { uint24 _numberBins = 9; uint24 _gap = 2; - addLiquidityFromRouter(token6D, token18D, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); + addLiquidityFromRouter(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - (, , , uint256 amountXIn) = spreadLiquidityForRouter(_amountYIn, _startId, _numberBins, _gap); + (,,, uint256 amountXIn) = spreadLiquidityForRouter(_amountYIn, _startId, _numberBins, _gap); - ILBRouter.LiquidityParameters memory _liquidityParameters = prepareLiquidityParameters( - token6D, - token18D, - _amountYIn, - _startId, - _numberBins, - _gap, - DEFAULT_BIN_STEP - ); + ILBRouter.LiquidityParameters memory _liquidityParameters = + prepareLiquidityParameters(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); _liquidityParameters.amountXMin = 0; _liquidityParameters.amountYMin = 0; _liquidityParameters.idSlippage = 0; //_liq.activeIdDesired + _liq.idSlippage < _activeId - token18D.mint(address(pair), _amountYIn); + weth.mint(address(pair), _amountYIn); pair.swap(false, ALICE); vm.expectRevert( abi.encodeWithSelector( - LBRouter__IdSlippageCaught.selector, - 8388608, - _liquidityParameters.idSlippage, - 8388620 + LBRouter__IdSlippageCaught.selector, 8388608, _liquidityParameters.idSlippage, 8388620 ) ); router.addLiquidity(_liquidityParameters); // _activeId + _liq.idSlippage < _liq.activeIdDesired - token6D.mint(address(pair), 2 * amountXIn); + usdc.mint(address(pair), 2 * amountXIn); pair.swap(true, ALICE); vm.expectRevert( abi.encodeWithSelector( - LBRouter__IdSlippageCaught.selector, - 8388608, - _liquidityParameters.idSlippage, - 8388596 + LBRouter__IdSlippageCaught.selector, 8388608, _liquidityParameters.idSlippage, 8388596 ) ); router.addLiquidity(_liquidityParameters); @@ -440,21 +318,14 @@ contract LiquidityBinRouterTest is TestHelper { uint24 _startId = ID_ONE; uint24 _numberBins = 9; uint24 _gap = 2; - addLiquidityFromRouter(token6D, token18D, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); + addLiquidityFromRouter(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - ILBRouter.LiquidityParameters memory _liquidityParameters = prepareLiquidityParameters( - token6D, - token18D, - _amountYIn, - _startId, - _numberBins, - _gap, - DEFAULT_BIN_STEP - ); + ILBRouter.LiquidityParameters memory _liquidityParameters = + prepareLiquidityParameters(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - (, , , uint256 amountXIn) = spreadLiquidityForRouter(_amountYIn, _startId, _numberBins, _gap); + (,,, uint256 amountXIn) = spreadLiquidityForRouter(_amountYIn, _startId, _numberBins, _gap); - token18D.mint(address(pair), _amountYIn / 3); + weth.mint(address(pair), _amountYIn / 3); pair.swap(false, ALICE); //no slippage allowed @@ -481,15 +352,8 @@ contract LiquidityBinRouterTest is TestHelper { uint24 _gap = 2; uint256 overflown24 = uint256(type(uint24).max) + 1; - ILBRouter.LiquidityParameters memory _liquidityParameters = prepareLiquidityParameters( - token6D, - token18D, - _amountYIn, - _startId, - _numberBins, - _gap, - DEFAULT_BIN_STEP - ); + ILBRouter.LiquidityParameters memory _liquidityParameters = + prepareLiquidityParameters(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); //this will fail until n16 from audit will be fixed _liquidityParameters.activeIdDesired = overflown24; _liquidityParameters.idSlippage = 0; @@ -513,15 +377,8 @@ contract LiquidityBinRouterTest is TestHelper { uint24 _numberBins = 9; uint24 _gap = 2; - ILBRouter.LiquidityParameters memory _liquidityParameters = prepareLiquidityParameters( - token6D, - token18D, - _amountYIn, - _startId, - _numberBins, - _gap, - DEFAULT_BIN_STEP - ); + ILBRouter.LiquidityParameters memory _liquidityParameters = + prepareLiquidityParameters(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); int256[] memory _wrongLengthDeltaIds = new int256[](_numberBins - 1); @@ -537,15 +394,8 @@ contract LiquidityBinRouterTest is TestHelper { uint24 _numberBins = 9; uint24 _gap = 2; - ILBRouter.LiquidityParameters memory _liquidityParameters = prepareLiquidityParameters( - token18D, - token6D, - _amountYIn, - _startId, - _numberBins, - _gap, - DEFAULT_BIN_STEP - ); + ILBRouter.LiquidityParameters memory _liquidityParameters = + prepareLiquidityParameters(weth, usdc, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); vm.expectRevert(LBRouter__WrongTokenOrder.selector); router.addLiquidity(_liquidityParameters); @@ -559,15 +409,8 @@ contract LiquidityBinRouterTest is TestHelper { uint24 _numberBins = 9; uint24 _gap = 2; - ILBRouter.LiquidityParameters memory _liquidityParameters = prepareLiquidityParameters( - token6D, - token18D, - _amountYIn, - _startId, - _numberBins, - _gap, - DEFAULT_BIN_STEP - ); + ILBRouter.LiquidityParameters memory _liquidityParameters = + prepareLiquidityParameters(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); vm.expectRevert( abi.encodeWithSelector( diff --git a/test_old/LBRouter.Swaps.t.sol b/test_old/LBRouter.Swaps.t.sol new file mode 100644 index 00000000..d8785d1e --- /dev/null +++ b/test_old/LBRouter.Swaps.t.sol @@ -0,0 +1,474 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.10; + +import "test/helpers/TestHelper.sol"; + +contract LiquidityBinRouterTest is TestHelper { + LBPair internal pair0; + LBPair internal pair1; + LBPair internal pair2; + LBPair internal pair3; + LBPair internal taxTokenPair1; + LBPair internal taxTokenPair; + + function setUp() public override { + usdc = new ERC20Mock(6); + link = new ERC20Mock(10); + wbtc = new ERC20Mock(12); + weth = new ERC20Mock(18); + wbtc = new ERC20Mock(24); + + taxToken = new ERC20TransferTaxMock(); + + wavax = new WAVAX(); + + factory = new LBFactory(DEV, 8e14); + ILBPair _LBPairImplementation = new LBPair(factory); + factory.setLBPairImplementation(address(_LBPairImplementation)); + addAllAssetsToQuoteWhitelist(factory); + setDefaultFactoryPresets(DEFAULT_BIN_STEP); + + router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(wavax))); + + pair = createLBPairDefaultFees(usdc, weth); + addLiquidityFromRouter(usdc, weth, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); + + pair0 = createLBPairDefaultFees(usdc, link); + addLiquidityFromRouter(usdc, link, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); + pair1 = createLBPairDefaultFees(link, wbtc); + addLiquidityFromRouter(link, wbtc, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); + pair2 = createLBPairDefaultFees(wbtc, weth); + addLiquidityFromRouter(wbtc, weth, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); + pair3 = createLBPairDefaultFees(weth, wbtc); + addLiquidityFromRouter(weth, wbtc, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); + taxTokenPair1 = createLBPairDefaultFees(usdc, taxToken); + addLiquidityFromRouter(usdc, ERC20Mock(address(taxToken)), 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); + + taxTokenPair = createLBPairDefaultFees(taxToken, wavax); + addLiquidityFromRouter( + ERC20Mock(address(taxToken)), ERC20Mock(address(wavax)), 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP + ); + + pairWavax = createLBPairDefaultFees(usdc, wavax); + addLiquidityFromRouter(usdc, ERC20Mock(address(wavax)), 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); + } + + function testSwapExactTokensForTokensSinglePair() public { + uint256 amountIn = 1e18; + + usdc.mint(DEV, amountIn); + + usdc.approve(address(router), amountIn); + + IERC20[] memory tokenList = new IERC20[](2); + tokenList[0] = usdc; + tokenList[1] = weth; + uint256[] memory pairVersions = new uint256[](1); + pairVersions[0] = DEFAULT_BIN_STEP; + + (uint256 amountOut,) = router.getSwapOut(pair, amountIn, true); + vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); + router.swapExactTokensForTokens(amountIn, amountOut + 1, pairVersions, tokenList, DEV, block.timestamp); + + router.swapExactTokensForTokens(amountIn, amountOut, pairVersions, tokenList, DEV, block.timestamp); + + assertApproxEqAbs(weth.balanceOf(DEV), amountOut, 10); + } + + function testSwapExactTokensForAvaxSinglePair() public { + uint256 amountIn = 1e18; + + usdc.mint(ALICE, amountIn); + + vm.startPrank(ALICE); + usdc.approve(address(router), amountIn); + + IERC20[] memory tokenList = new IERC20[](2); + tokenList[0] = usdc; + tokenList[1] = wavax; + uint256[] memory pairVersions = new uint256[](1); + pairVersions[0] = DEFAULT_BIN_STEP; + + (uint256 amountOut,) = router.getSwapOut(pair, amountIn, true); + + uint256 devBalanceBefore = ALICE.balance; + vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); + router.swapExactTokensForAVAX(amountIn, amountOut + 1, pairVersions, tokenList, ALICE, block.timestamp); + + router.swapExactTokensForAVAX(amountIn, amountOut, pairVersions, tokenList, ALICE, block.timestamp); + vm.stopPrank(); + + assertEq(ALICE.balance - devBalanceBefore, amountOut); + } + + function testSwapExactAVAXForTokensSinglePair() public { + uint256 amountIn = 1e18; + + IERC20[] memory tokenList = new IERC20[](2); + tokenList[0] = wavax; + tokenList[1] = usdc; + uint256[] memory pairVersions = new uint256[](1); + pairVersions[0] = DEFAULT_BIN_STEP; + + (uint256 amountOut,) = router.getSwapOut(pairWavax, amountIn, false); + + vm.deal(DEV, amountIn); + vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); + router.swapExactAVAXForTokens{value: amountIn}(amountOut + 1, pairVersions, tokenList, DEV, block.timestamp); + + router.swapExactAVAXForTokens{value: amountIn}(amountOut, pairVersions, tokenList, DEV, block.timestamp); + + assertApproxEqAbs(usdc.balanceOf(DEV), amountOut, 13); + } + + function testSwapTokensForExactTokensSinglePair() public { + uint256 amountOut = 1e18; + + (uint256 amountIn,) = router.getSwapIn(pair, amountOut, true); + usdc.mint(DEV, amountIn); + + usdc.approve(address(router), amountIn); + + IERC20[] memory tokenList = new IERC20[](2); + tokenList[0] = usdc; + tokenList[1] = weth; + uint256[] memory pairVersions = new uint256[](1); + pairVersions[0] = DEFAULT_BIN_STEP; + vm.expectRevert(abi.encodeWithSelector(LBRouter__MaxAmountInExceeded.selector, amountIn - 1, amountIn)); + router.swapTokensForExactTokens(amountOut, amountIn - 1, pairVersions, tokenList, DEV, block.timestamp); + + router.swapTokensForExactTokens(amountOut, amountIn, pairVersions, tokenList, DEV, block.timestamp); + + assertApproxEqAbs(weth.balanceOf(DEV), amountOut, 10); + } + + function testSwapTokensForExactAVAXSinglePair() public { + uint256 amountOut = 1e18; + + (uint256 amountIn,) = router.getSwapIn(pairWavax, amountOut, true); + usdc.mint(ALICE, amountIn); + + vm.startPrank(ALICE); + usdc.approve(address(router), amountIn); + + IERC20[] memory tokenList = new IERC20[](2); + tokenList[0] = usdc; + tokenList[1] = wavax; + uint256[] memory pairVersions = new uint256[](1); + pairVersions[0] = DEFAULT_BIN_STEP; + + uint256 devBalanceBefore = ALICE.balance; + + vm.expectRevert(abi.encodeWithSelector(LBRouter__MaxAmountInExceeded.selector, amountIn - 1, amountIn)); + router.swapTokensForExactAVAX(amountOut, amountIn - 1, pairVersions, tokenList, ALICE, block.timestamp); + + router.swapTokensForExactAVAX(amountOut, amountIn, pairVersions, tokenList, ALICE, block.timestamp); + vm.stopPrank(); + + assertEq(ALICE.balance - devBalanceBefore, amountOut); + } + + function testSwapAVAXForExactTokensSinglePair() public { + uint256 amountOut = 1e18; + + (uint256 amountIn,) = router.getSwapIn(pairWavax, amountOut, false); + + IERC20[] memory tokenList = new IERC20[](2); + tokenList[0] = wavax; + tokenList[1] = usdc; + uint256[] memory pairVersions = new uint256[](1); + pairVersions[0] = DEFAULT_BIN_STEP; + + vm.deal(DEV, amountIn); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__MaxAmountInExceeded.selector, amountIn - 1, amountIn)); + router.swapAVAXForExactTokens{value: amountIn - 1}(amountOut, pairVersions, tokenList, ALICE, block.timestamp); + router.swapAVAXForExactTokens{value: amountIn}(amountOut, pairVersions, tokenList, DEV, block.timestamp); + + assertApproxEqAbs(usdc.balanceOf(DEV), amountOut, 13); + } + + function testSwapExactTokensForTokensSupportingFeeOnTransferTokens() public { + uint256 amountIn = 1e18; + + taxToken.mint(DEV, amountIn); + + taxToken.approve(address(router), amountIn); + + IERC20[] memory tokenList = new IERC20[](2); + tokenList[0] = taxToken; + tokenList[1] = wavax; + uint256[] memory pairVersions = new uint256[](1); + pairVersions[0] = DEFAULT_BIN_STEP; + + (uint256 amountOut,) = router.getSwapOut(taxTokenPair, amountIn, true); + amountOut = amountOut / 2; + vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); + router.swapExactTokensForTokensSupportingFeeOnTransferTokens( + amountIn, amountOut + 1, pairVersions, tokenList, DEV, block.timestamp + ); + router.swapExactTokensForTokensSupportingFeeOnTransferTokens( + amountIn, 0, pairVersions, tokenList, DEV, block.timestamp + ); + + // 50% tax to take into account + assertApproxEqAbs(wavax.balanceOf(DEV), amountOut, 10); + + // Swap back in the other direction + amountIn = wavax.balanceOf(DEV); + wavax.approve(address(router), amountIn); + tokenList[0] = wavax; + tokenList[1] = taxToken; + + (amountOut,) = router.getSwapOut(taxTokenPair, amountIn, true); + amountOut = amountOut / 2; + uint256 balanceBefore = taxToken.balanceOf(DEV); + vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); + router.swapExactTokensForTokensSupportingFeeOnTransferTokens( + amountIn, amountOut + 1, pairVersions, tokenList, DEV, block.timestamp + ); + + router.swapExactTokensForTokensSupportingFeeOnTransferTokens( + amountIn, amountOut, pairVersions, tokenList, DEV, block.timestamp + ); + + assertApproxEqAbs(taxToken.balanceOf(DEV) - balanceBefore, amountOut, 10); + } + + function testSwapExactTokensForAVAXSupportingFeeOnTransferTokens() public { + uint256 amountIn = 1e18; + + taxToken.mint(ALICE, amountIn); + + vm.startPrank(ALICE); + taxToken.approve(address(router), amountIn); + + IERC20[] memory tokenList = new IERC20[](2); + tokenList[0] = taxToken; + tokenList[1] = wavax; + uint256[] memory pairVersions = new uint256[](1); + pairVersions[0] = DEFAULT_BIN_STEP; + + (uint256 amountOut,) = router.getSwapOut(taxTokenPair, amountIn, true); + amountOut = amountOut / 2; + uint256 devBalanceBefore = ALICE.balance; + vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); + router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + amountIn, amountOut + 1, pairVersions, tokenList, ALICE, block.timestamp + ); + router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + amountIn, amountOut, pairVersions, tokenList, ALICE, block.timestamp + ); + vm.stopPrank(); + + assertGe(ALICE.balance - devBalanceBefore, amountOut); + } + + function testSwapExactAVAXForTokensSupportingFeeOnTransferTokens() public { + uint256 amountIn = 1e18; + + IERC20[] memory tokenList = new IERC20[](2); + tokenList[0] = wavax; + tokenList[1] = taxToken; + uint256[] memory pairVersions = new uint256[](1); + pairVersions[0] = DEFAULT_BIN_STEP; + + (uint256 amountOut,) = router.getSwapOut(taxTokenPair, amountIn, true); + amountOut = amountOut / 2; + vm.deal(DEV, amountIn); + vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); + router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( + amountOut + 1, pairVersions, tokenList, DEV, block.timestamp + ); + router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( + amountOut, pairVersions, tokenList, DEV, block.timestamp + ); + + assertApproxEqAbs(taxToken.balanceOf(DEV), amountOut, 10); + } + + function testSwapExactTokensForTokensMultiplePairs() public { + uint256 amountIn = 1e18; + + usdc.mint(DEV, amountIn); + + usdc.approve(address(router), amountIn); + + (IERC20[] memory tokenList, uint256[] memory pairVersions) = _buildComplexSwapRoute(); + + router.swapExactTokensForTokens(amountIn, 0, pairVersions, tokenList, DEV, block.timestamp); + + assertGt(wbtc.balanceOf(DEV), 0); + } + + function testSwapTokensForExactTokensMultiplePairs() public { + uint256 amountOut = 1e18; + + usdc.mint(DEV, 100e18); + + usdc.approve(address(router), 100e18); + + (IERC20[] memory tokenList, uint256[] memory pairVersions) = _buildComplexSwapRoute(); + vm.expectRevert( + abi.encodeWithSelector(LBRouter__MaxAmountInExceeded.selector, 100500938281494149, 1005015664148120440) + ); + router.swapTokensForExactTokens(amountOut, 100500938281494149, pairVersions, tokenList, DEV, block.timestamp); + router.swapTokensForExactTokens(amountOut, 100e18, pairVersions, tokenList, DEV, block.timestamp); + + assertEq(wbtc.balanceOf(DEV), amountOut); + } + + function testSwapWithDifferentBinSteps() public { + factory.setPreset( + 75, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + 5, + 10, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATED, + DEFAULT_SAMPLE_LIFETIME + ); + createLBPairDefaultFeesFromStartIdAndBinStep(usdc, weth, ID_ONE, 75); + addLiquidityFromRouter(usdc, weth, 100e18, ID_ONE, 9, 2, 75); + + uint256 amountIn = 1e18; + + usdc.mint(DEV, amountIn); + usdc.approve(address(router), amountIn); + + IERC20[] memory tokenList; + uint256[] memory pairVersions; + tokenList = new IERC20[](2); + tokenList[0] = usdc; + tokenList[1] = weth; + pairVersions = new uint256[](1); + pairVersions[0] = DEFAULT_BIN_STEP; + + router.swapExactTokensForTokens(amountIn, 0, pairVersions, tokenList, DEV, block.timestamp); + + assertGt(weth.balanceOf(DEV), 0); + + weth.approve(address(router), weth.balanceOf(DEV)); + + tokenList[0] = weth; + tokenList[1] = usdc; + pairVersions = new uint256[](1); + pairVersions[0] = 75; + + router.swapExactTokensForTokens(weth.balanceOf(DEV), 0, pairVersions, tokenList, DEV, block.timestamp); + assertGt(usdc.balanceOf(DEV), 0); + } + + function _buildComplexSwapRoute() private view returns (IERC20[] memory tokenList, uint256[] memory pairVersions) { + tokenList = new IERC20[](5); + tokenList[0] = usdc; + tokenList[1] = link; + tokenList[2] = wbtc; + tokenList[3] = weth; + tokenList[4] = wbtc; + + pairVersions = new uint256[](4); + pairVersions[0] = DEFAULT_BIN_STEP; + pairVersions[1] = DEFAULT_BIN_STEP; + pairVersions[2] = DEFAULT_BIN_STEP; + pairVersions[3] = DEFAULT_BIN_STEP; + } + + function testTaxTokenEqualOnlyV2Swap() public { + uint256 amountIn = 1e18; + + taxToken.mint(ALICE, amountIn); + taxToken.mint(BOB, amountIn); + usdc.mint(ALICE, amountIn); + usdc.mint(BOB, amountIn); + + IERC20[] memory tokenList = new IERC20[](3); + tokenList[0] = usdc; + tokenList[1] = taxToken; + tokenList[2] = wavax; + uint256[] memory pairVersions = new uint256[](2); + pairVersions[0] = DEFAULT_BIN_STEP; + pairVersions[1] = DEFAULT_BIN_STEP; + + vm.startPrank(ALICE); + usdc.approve(address(router), amountIn); + taxToken.approve(address(router), amountIn); + uint256 aliceBalanceBefore = ALICE.balance; + uint256 amountOutNotSupporting = + router.swapExactTokensForAVAX(amountIn, 0, pairVersions, tokenList, ALICE, block.timestamp); + vm.stopPrank(); + + vm.startPrank(BOB); + usdc.approve(address(router), amountIn); + taxToken.approve(address(router), amountIn); + uint256 bobBalanceBefore = BOB.balance; + uint256 amountOutSupporting = router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + amountIn, 0, pairVersions, tokenList, BOB, block.timestamp + ); + vm.stopPrank(); + + assertEq(ALICE.balance, BOB.balance); + assertEq(amountOutNotSupporting, amountOutSupporting); + } + + function testSwappingOnNotExistingV2PairReverts() public { + IERC20[] memory tokenListAvaxIn; + IERC20[] memory tokenList; + uint256[] memory pairVersions; + + uint256 amountIn2 = 1e18; + vm.deal(DEV, amountIn2); + + tokenList = new IERC20[](3); + tokenList[0] = usdc; + tokenList[1] = weth; + tokenList[2] = wavax; + + pairVersions = new uint256[](2); + pairVersions[0] = DEFAULT_BIN_STEP; + pairVersions[1] = DEFAULT_BIN_STEP + 1; + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); + router.swapExactTokensForTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); + router.swapExactTokensForAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); + router.swapTokensForExactTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); + router.swapTokensForExactAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); + router.swapExactTokensForTokensSupportingFeeOnTransferTokens( + amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp + ); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); + router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp + ); + + tokenListAvaxIn = new IERC20[](3); + tokenListAvaxIn[0] = wavax; + tokenListAvaxIn[1] = usdc; + tokenListAvaxIn[2] = weth; + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, usdc, weth, pairVersions[1])); + router.swapExactAVAXForTokens{value: amountIn2}(0, pairVersions, tokenListAvaxIn, DEV, block.timestamp); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, usdc, weth, pairVersions[1])); + router.swapAVAXForExactTokens{value: amountIn2}(0, pairVersions, tokenListAvaxIn, DEV, block.timestamp); + + vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, usdc, weth, pairVersions[1])); + router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn2}( + 0, pairVersions, tokenListAvaxIn, DEV, block.timestamp + ); + } + + receive() external payable {} +} diff --git a/test/LBRouter.t.sol b/test_old/LBRouter.t.sol similarity index 74% rename from test/LBRouter.t.sol rename to test_old/LBRouter.t.sol index 2eb84ccb..94de93ef 100644 --- a/test/LBRouter.t.sol +++ b/test_old/LBRouter.t.sol @@ -2,39 +2,31 @@ pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "test/helpers/TestHelper.sol"; +import {Addresses} from "test/integration/Addresses.sol"; contract LiquidityBinRouterTest is TestHelper { - function setUp() public { - token6D = new ERC20MockDecimals(6); - token18D = new ERC20MockDecimals(18); - wavax = new WAVAX(); - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - addAllAssetsToQuoteWhitelist(factory); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - - router = new LBRouter(factory, IJoeFactory(JOE_V1_FACTORY_ADDRESS), IWAVAX(WAVAX_AVALANCHE_ADDRESS)); + function setUp() public override { + super.setUp(); } function testConstructor() public { assertEq(address(router.factory()), address(factory)); - assertEq(address(router.oldFactory()), JOE_V1_FACTORY_ADDRESS); - assertEq(address(router.wavax()), WAVAX_AVALANCHE_ADDRESS); + assertEq(address(router.oldFactory()), Addresses.JOE_V1_FACTORY_ADDRESS); + assertEq(address(router.wavax()), Addresses.WAVAX_AVALANCHE_ADDRESS); } function testCreateLBPair() public { factory.setFactoryLockedState(false); - router.createLBPair(token6D, token18D, ID_ONE, DEFAULT_BIN_STEP); + router.createLBPair(usdc, weth, ID_ONE, DEFAULT_BIN_STEP); assertEq(factory.getNumberOfLBPairs(), 1); - pair = LBPair(address(factory.getLBPairInformation(token6D, token18D, DEFAULT_BIN_STEP).LBPair)); + pair = LBPair(address(factory.getLBPairInformation(usdc, weth, DEFAULT_BIN_STEP).LBPair)); assertEq(address(pair.factory()), address(factory)); - assertEq(address(pair.tokenX()), address(token6D)); - assertEq(address(pair.tokenY()), address(token18D)); + assertEq(address(pair.tokenX()), address(usdc)); + assertEq(address(pair.tokenY()), address(weth)); FeeHelper.FeeParameters memory feeParameters = pair.feeParameters(); assertEq(feeParameters.volatilityAccumulated, 0); @@ -55,28 +47,11 @@ contract LiquidityBinRouterTest is TestHelper { vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); router.removeLiquidity( - token6D, - token18D, - DEFAULT_BIN_STEP, - 1, - 1, - defaultUintArray, - defaultUintArray, - DEV, - wrongDeadline + usdc, weth, DEFAULT_BIN_STEP, 1, 1, defaultUintArray, defaultUintArray, DEV, wrongDeadline ); vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); - router.removeLiquidityAVAX( - token6D, - DEFAULT_BIN_STEP, - 1, - 1, - defaultUintArray, - defaultUintArray, - DEV, - wrongDeadline - ); + router.removeLiquidityAVAX(usdc, DEFAULT_BIN_STEP, 1, 1, defaultUintArray, defaultUintArray, DEV, wrongDeadline); vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); router.swapExactTokensForTokens(1, 1, defaultUintArray, defaultIERCArray, DEV, wrongDeadline); @@ -97,40 +72,26 @@ contract LiquidityBinRouterTest is TestHelper { vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - 1, - 1, - defaultUintArray, - defaultIERCArray, - DEV, - wrongDeadline + 1, 1, defaultUintArray, defaultIERCArray, DEV, wrongDeadline ); vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - 1, - 1, - defaultUintArray, - defaultIERCArray, - DEV, - wrongDeadline + 1, 1, defaultUintArray, defaultIERCArray, DEV, wrongDeadline ); vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); router.swapExactAVAXForTokensSupportingFeeOnTransferTokens( - 1, - defaultUintArray, - defaultIERCArray, - DEV, - wrongDeadline + 1, defaultUintArray, defaultIERCArray, DEV, wrongDeadline ); - //TODO _addLiquidity private + // _addLiquidity private } function testModifieronlyFactoryOwner() public { vm.prank(ALICE); vm.expectRevert(LBRouter__NotFactoryOwner.selector); - router.sweep(token6D, address(0), 1); + router.sweep(usdc, address(0), 1); } function testModifierVerifyInputs() public { @@ -170,58 +131,30 @@ contract LiquidityBinRouterTest is TestHelper { vm.expectRevert(LBRouter__LengthsMismatch.selector); router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - 1, - 1, - pairBinStepsZeroLength, - defaultIERCArray, - DEV, - block.timestamp + 1, 1, pairBinStepsZeroLength, defaultIERCArray, DEV, block.timestamp ); vm.expectRevert(LBRouter__LengthsMismatch.selector); router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - 1, - 1, - mismatchedpairBinSteps, - mismatchedIERCArray, - DEV, - block.timestamp + 1, 1, mismatchedpairBinSteps, mismatchedIERCArray, DEV, block.timestamp ); vm.expectRevert(LBRouter__LengthsMismatch.selector); router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - 1, - 1, - pairBinStepsZeroLength, - defaultIERCArray, - DEV, - block.timestamp + 1, 1, pairBinStepsZeroLength, defaultIERCArray, DEV, block.timestamp ); vm.expectRevert(LBRouter__LengthsMismatch.selector); router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - 1, - 1, - mismatchedpairBinSteps, - mismatchedIERCArray, - DEV, - block.timestamp + 1, 1, mismatchedpairBinSteps, mismatchedIERCArray, DEV, block.timestamp ); vm.expectRevert(LBRouter__LengthsMismatch.selector); router.swapExactAVAXForTokensSupportingFeeOnTransferTokens( - 1, - pairBinStepsZeroLength, - defaultIERCArray, - DEV, - block.timestamp + 1, pairBinStepsZeroLength, defaultIERCArray, DEV, block.timestamp ); vm.expectRevert(LBRouter__LengthsMismatch.selector); router.swapExactAVAXForTokensSupportingFeeOnTransferTokens( - 1, - mismatchedpairBinSteps, - mismatchedIERCArray, - DEV, - block.timestamp + 1, mismatchedpairBinSteps, mismatchedIERCArray, DEV, block.timestamp ); } @@ -236,24 +169,20 @@ contract LiquidityBinRouterTest is TestHelper { uint256[] memory _distributionY; uint256 amountXIn; factory.setFactoryLockedState(false); - router.createLBPair(token6D, token18D, ID_ONE, DEFAULT_BIN_STEP); - router.createLBPair(token6D, wavax, ID_ONE, DEFAULT_BIN_STEP); + router.createLBPair(usdc, weth, ID_ONE, DEFAULT_BIN_STEP); + router.createLBPair(usdc, wavax, ID_ONE, DEFAULT_BIN_STEP); - (_deltaIds, _distributionX, _distributionY, amountXIn) = spreadLiquidityForRouter( - _amountYIn, - ID_ONE, - _numberBins, - _gap - ); + (_deltaIds, _distributionX, _distributionY, amountXIn) = + spreadLiquidityForRouter(_amountYIn, ID_ONE, _numberBins, _gap); - token6D.mint(DEV, amountXIn); - token6D.approve(address(router), amountXIn); - token18D.mint(DEV, _amountYIn); - token18D.approve(address(router), _amountYIn); + usdc.mint(DEV, amountXIn); + usdc.approve(address(router), amountXIn); + weth.mint(DEV, _amountYIn); + weth.approve(address(router), _amountYIn); ILBRouter.LiquidityParameters memory _liquidityParameters = ILBRouter.LiquidityParameters( - token6D, - token18D, + usdc, + weth, _binStep, amountXIn, _amountYIn, @@ -273,7 +202,7 @@ contract LiquidityBinRouterTest is TestHelper { } function testGetPriceFromId() public { - pair = createLBPairDefaultFees(token6D, token18D); + pair = createLBPairDefaultFees(usdc, weth); uint256 price; price = router.getPriceFromId(pair, ID_ONE); @@ -287,7 +216,7 @@ contract LiquidityBinRouterTest is TestHelper { } function testGetIdFromPrice() public { - pair = createLBPairDefaultFees(token6D, token18D); + pair = createLBPairDefaultFees(usdc, weth); uint24 id; id = router.getIdFromPrice(pair, 340282366920938463463374607431768211456); @@ -307,16 +236,9 @@ contract LiquidityBinRouterTest is TestHelper { uint24 _gap = 2; uint256 amountXIn; int256[] memory _deltaIds; - pair = createLBPairDefaultFees(token6D, token18D); - (_deltaIds, , , amountXIn) = addLiquidityFromRouter( - token6D, - token18D, - _amountYIn, - _startId, - _numberBins, - _gap, - DEFAULT_BIN_STEP - ); + pair = createLBPairDefaultFees(usdc, weth); + (_deltaIds,,, amountXIn) = + addLiquidityFromRouter(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); vm.expectRevert(abi.encodeWithSelector(LBRouter__WrongAmounts.selector, 0, _amountYIn)); router.getSwapIn(pair, 0, true); @@ -351,8 +273,8 @@ contract LiquidityBinRouterTest is TestHelper { uint24 _numberBins = 1; uint24 _gap = 2; uint256 amountXIn; - pair = createLBPairDefaultFees(token6D, token18D); - (, , , amountXIn) = addLiquidity(_amountYIn, _startId, _numberBins, _gap); + pair = createLBPairDefaultFees(usdc, weth); + (,,, amountXIn) = addLiquidity(_amountYIn, _startId, _numberBins, _gap); vm.expectRevert(abi.encodeWithSelector(LBRouter__SwapOverflows.selector, _startId)); router.getSwapIn(pair, _amountYIn, true); @@ -363,10 +285,10 @@ contract LiquidityBinRouterTest is TestHelper { function testSweep() public { uint256 amountMinted = 100e6; - token6D.mint(address(router), amountMinted); - router.sweep(token6D, ALICE, amountMinted); - assertEq(token6D.balanceOf(ALICE), amountMinted); - assertEq(token6D.balanceOf(address(router)), 0); + usdc.mint(address(router), amountMinted); + router.sweep(usdc, ALICE, amountMinted); + assertEq(usdc.balanceOf(ALICE), amountMinted); + assertEq(usdc.balanceOf(address(router)), 0); uint256 amountAvax = 10e18; vm.deal(address(router), amountAvax); @@ -377,10 +299,10 @@ contract LiquidityBinRouterTest is TestHelper { function testSweepMax() public { uint256 amountMinted = 1000e6; - token6D.mint(address(router), amountMinted); - router.sweep(token6D, ALICE, type(uint256).max); - assertEq(token6D.balanceOf(ALICE), amountMinted); - assertEq(token6D.balanceOf(address(router)), 0); + usdc.mint(address(router), amountMinted); + router.sweep(usdc, ALICE, type(uint256).max); + assertEq(usdc.balanceOf(ALICE), amountMinted); + assertEq(usdc.balanceOf(address(router)), 0); uint256 amountAvax = 100e18; vm.deal(address(router), amountAvax); @@ -395,17 +317,17 @@ contract LiquidityBinRouterTest is TestHelper { uint24 _numberBins = 9; uint24 _gap = 2; uint256 amountXIn; - pair = createLBPairDefaultFees(token6D, token18D); - (, , , amountXIn) = addLiquidity(_amountYIn, _startId, _numberBins, _gap); + pair = createLBPairDefaultFees(usdc, weth); + (,,, amountXIn) = addLiquidity(_amountYIn, _startId, _numberBins, _gap); //getSwapIn goes through all bins with liquidity - (uint256 amountIn, ) = router.getSwapIn(pair, amountXIn - 100, false); - (uint256 amountIn2, ) = router.getSwapIn(pair, _amountYIn - 100, true); + (uint256 amountIn,) = router.getSwapIn(pair, amountXIn - 100, false); + (uint256 amountIn2,) = router.getSwapIn(pair, _amountYIn - 100, true); } function testWrongTokenWAVAXSwaps() public { IERC20[] memory IERCArray = new IERC20[](2); - IERCArray[0] = token6D; - IERCArray[1] = token18D; + IERCArray[0] = usdc; + IERCArray[1] = weth; uint256[] memory pairBinStepsArray = new uint256[](1); vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, address(IERCArray[1]))); @@ -422,29 +344,20 @@ contract LiquidityBinRouterTest is TestHelper { vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, address(IERCArray[1]))); router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - 1, - 1, - pairBinStepsArray, - IERCArray, - DEV, - block.timestamp + 1, 1, pairBinStepsArray, IERCArray, DEV, block.timestamp ); vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, address(IERCArray[0]))); router.swapExactAVAXForTokensSupportingFeeOnTransferTokens( - 1, - pairBinStepsArray, - IERCArray, - DEV, - block.timestamp + 1, pairBinStepsArray, IERCArray, DEV, block.timestamp ); } function testSweepLBToken() public { uint256 amountIn = 1e18; - pair = createLBPairDefaultFees(token6D, token18D); - (uint256[] memory _ids, , , ) = addLiquidity(amountIn, ID_ONE, 5, 0); + pair = createLBPairDefaultFees(usdc, weth); + (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, 5, 0); uint256[] memory amounts = new uint256[](5); for (uint256 i; i < 5; i++) { diff --git a/test/LBToken.t.sol b/test_old/LBToken.t.sol similarity index 89% rename from test/LBToken.t.sol rename to test_old/LBToken.t.sol index 25089fe7..db454eb7 100644 --- a/test/LBToken.t.sol +++ b/test_old/LBToken.t.sol @@ -2,21 +2,17 @@ pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "test/helpers/TestHelper.sol"; contract LiquidityBinTokenTest is TestHelper { event TransferBatch( - address indexed sender, - address indexed from, - address indexed to, - uint256[] ids, - uint256[] amounts + address indexed sender, address indexed from, address indexed to, uint256[] ids, uint256[] amounts ); event TransferSingle(address indexed sender, address indexed from, address indexed to, uint256 id, uint256 amount); - function setUp() public { - token6D = new ERC20MockDecimals(6); - token18D = new ERC20MockDecimals(18); + function setUp() public override { + usdc = new ERC20Mock(6); + weth = new ERC20Mock(18); factory = new LBFactory(DEV, 8e14); ILBPair _LBPairImplementation = new LBPair(factory); @@ -24,13 +20,13 @@ contract LiquidityBinTokenTest is TestHelper { addAllAssetsToQuoteWhitelist(factory); setDefaultFactoryPresets(DEFAULT_BIN_STEP); - pair = createLBPairDefaultFees(token6D, token18D); + pair = createLBPairDefaultFees(usdc, weth); } function testSafeBatchTransferFrom() public { uint256 amountIn = 1e18; - (uint256[] memory _ids, , , ) = addLiquidity(amountIn, ID_ONE, 5, 0); + (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, 5, 0); uint256[] memory amounts = new uint256[](5); for (uint256 i; i < 5; i++) { @@ -61,7 +57,7 @@ contract LiquidityBinTokenTest is TestHelper { function testSafeTransferFrom() public { uint256 amountIn = 1e18; - (uint256[] memory _ids, , , ) = addLiquidity(amountIn, ID_ONE, 5, 0); + (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, 5, 0); uint256[] memory amounts = new uint256[](5); for (uint256 i; i < 5; i++) { @@ -91,7 +87,7 @@ contract LiquidityBinTokenTest is TestHelper { function testSafeBatchTransferNotApprovedReverts() public { uint256 amountIn = 1e18; - (uint256[] memory _ids, , , ) = addLiquidity(amountIn, ID_ONE, 5, 0); + (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, 5, 0); uint256[] memory amounts = new uint256[](5); for (uint256 i; i < 5; i++) { @@ -105,7 +101,7 @@ contract LiquidityBinTokenTest is TestHelper { function testSafeTransferNotApprovedReverts() public { uint256 amountIn = 1e18; - (uint256[] memory _ids, , , ) = addLiquidity(amountIn, ID_ONE, 5, 0); + (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, 5, 0); uint256[] memory amounts = new uint256[](5); for (uint256 i; i < 5; i++) { @@ -120,7 +116,7 @@ contract LiquidityBinTokenTest is TestHelper { function testSafeBatchTransferFromReverts() public { uint24 binAmount = 11; uint256 amountIn = 1e18; - (uint256[] memory _ids, , , ) = addLiquidity(amountIn, ID_ONE, binAmount, 0); + (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, binAmount, 0); uint256[] memory amounts = new uint256[](binAmount); for (uint256 i; i < binAmount; i++) { @@ -152,7 +148,7 @@ contract LiquidityBinTokenTest is TestHelper { function testSafeTransferFromReverts() public { uint24 binAmount = 11; uint256 amountIn = 1e18; - (uint256[] memory _ids, , , ) = addLiquidity(amountIn, ID_ONE, binAmount, 0); + (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, binAmount, 0); uint256[] memory amounts = new uint256[](binAmount); for (uint256 i; i < binAmount; i++) { @@ -183,7 +179,7 @@ contract LiquidityBinTokenTest is TestHelper { function testModifierCheckLength() public { uint24 binAmount = 11; uint256 amountIn = 1e18; - (uint256[] memory _ids, , , ) = addLiquidity(amountIn, ID_ONE, binAmount, 0); + (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, binAmount, 0); uint256[] memory amounts = new uint256[](binAmount - 1); for (uint256 i; i < binAmount - 1; i++) { @@ -232,7 +228,7 @@ contract LiquidityBinTokenTest is TestHelper { assertEq(batchBalances[i], 0); } - (_ids, , , ) = addLiquidity(amountIn, _startId, binAmount, _gap); + (_ids,,,) = addLiquidity(amountIn, _startId, binAmount, _gap); uint256[] memory amounts = new uint256[](binAmount); for (uint256 i; i < binAmount; i++) { amounts[i] = pair.balanceOf(DEV, _ids[i]); diff --git a/test/LBTokenInternal.t.sol b/test_old/LBTokenInternal.t.sol similarity index 88% rename from test/LBTokenInternal.t.sol rename to test_old/LBTokenInternal.t.sol index 5648b4c8..dcb731c7 100644 --- a/test/LBTokenInternal.t.sol +++ b/test_old/LBTokenInternal.t.sol @@ -2,13 +2,13 @@ pragma solidity 0.8.10; -import "./TestHelper.sol"; +import "test/helpers/TestHelper.sol"; import "../src/LBToken.sol"; contract LiquidityBinTokenTest is TestHelper, LBToken { - function setUp() public { - token6D = new ERC20MockDecimals(6); - token18D = new ERC20MockDecimals(18); + function setUp() public override { + usdc = new ERC20Mock(6); + weth = new ERC20Mock(18); factory = new LBFactory(DEV, 8e14); ILBPair _LBPairImplementation = new LBPair(factory); @@ -16,16 +16,16 @@ contract LiquidityBinTokenTest is TestHelper, LBToken { addAllAssetsToQuoteWhitelist(factory); setDefaultFactoryPresets(DEFAULT_BIN_STEP); - pair = createLBPairDefaultFees(token6D, token18D); + pair = createLBPairDefaultFees(usdc, weth); } function testInternalMintTo0AddressReverts() public { vm.expectRevert(LBToken__MintToAddress0.selector); - _mint(address(0), 2**23, 1000); + _mint(address(0), 2 ** 23, 1000); } function testInternalMint(uint256 mintAmount) public { - uint256 binNumber = 2**23; + uint256 binNumber = 2 ** 23; uint256 totalSupplyBefore = totalSupply(binNumber); uint256 balanceBefore = balanceOf(ALICE, binNumber); vm.expectEmit(true, true, true, true); @@ -39,13 +39,13 @@ contract LiquidityBinTokenTest is TestHelper, LBToken { function testInternalBurnFrom0AddressReverts() public { vm.expectRevert(LBToken__BurnFromAddress0.selector); - _burn(address(0), 2**23, 1000); + _burn(address(0), 2 ** 23, 1000); } function testInternalExcessiveBurnAmountReverts(uint128 mintAmount, uint128 excessiveBurnAmount) public { vm.assume(excessiveBurnAmount > 0); uint256 burnAmount = uint256(mintAmount) + uint256(excessiveBurnAmount); - uint256 binNumber = 2**23; + uint256 binNumber = 2 ** 23; _mint(ALICE, binNumber, mintAmount); vm.expectRevert(abi.encodeWithSelector(LBToken__BurnExceedsBalance.selector, ALICE, binNumber, burnAmount)); _burn(ALICE, binNumber, burnAmount); @@ -54,7 +54,7 @@ contract LiquidityBinTokenTest is TestHelper, LBToken { function testInternalBurn(uint256 mintAmount, uint256 burnAmount) public { vm.assume(mintAmount > 0 && burnAmount > 0); vm.assume(mintAmount >= burnAmount); - uint256 binNumber = 2**23; + uint256 binNumber = 2 ** 23; _mint(ALICE, binNumber, mintAmount); From 6baefb9f2d149a2268fccf5e4f61ad7fe0664aa1 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 25 Jan 2023 17:18:45 +0100 Subject: [PATCH 02/47] removing unused file / commenting files that needs to change --- script/deploy-core.s.sol | 122 +- src/LBErrors.sol | 212 --- src/LBFactory.sol | 1232 +++++++------- src/LBQuoter.sol | 434 ++--- src/LBRouter.sol | 1940 +++++++++++------------ src/interfaces/ILBFactory.sol | 234 +-- src/interfaces/ILBRouter.sol | 360 ++--- src/libraries/Buffer.sol | 22 - src/libraries/Decoder.sol | 20 - src/libraries/Encoder.sol | 20 - src/libraries/FeeDistributionHelper.sol | 28 - src/libraries/Oracle.sol | 185 --- src/libraries/SafeMath.sol | 18 - src/libraries/Samples.sol | 116 -- src/libraries/SwapHelper.sol | 124 -- src/libraries/TreeMath.sol | 120 -- test/BinHelper.T.sol | 28 +- test/helpers/TestHelper.sol | 551 ++++--- test/mocks/FlashloanBorrower.sol | 67 +- 19 files changed, 2487 insertions(+), 3346 deletions(-) delete mode 100644 src/LBErrors.sol delete mode 100644 src/libraries/Buffer.sol delete mode 100644 src/libraries/Decoder.sol delete mode 100644 src/libraries/Encoder.sol delete mode 100644 src/libraries/FeeDistributionHelper.sol delete mode 100644 src/libraries/Oracle.sol delete mode 100644 src/libraries/SafeMath.sol delete mode 100644 src/libraries/Samples.sol delete mode 100644 src/libraries/SwapHelper.sol delete mode 100644 src/libraries/TreeMath.sol diff --git a/script/deploy-core.s.sol b/script/deploy-core.s.sol index 677b6cda..008bc7ae 100644 --- a/script/deploy-core.s.sol +++ b/script/deploy-core.s.sol @@ -12,65 +12,65 @@ import "src/LBQuoter.sol"; import "./config/bips-config.sol"; contract CoreDeployer is Script { - address private constant WAVAX_AVALANCHE = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7; - address private constant WAVAX_FUJI = 0xd00ae08403B9bbb9124bB305C09058E32C39A48c; - - address private constant FACTORY_V1_AVALANCHE = 0x9Ad6C38BE94206cA50bb0d90783181662f0Cfa10; - address private constant FACTORY_V1_FUJI = 0xF5c7d9733e5f53abCC1695820c4818C59B457C2C; - - address private wavax; - address private factoryV1; - - uint256 private constant FLASHLOAN_FEE = 5e12; - - function run() external { - if (block.chainid == 43114) { - wavax = WAVAX_AVALANCHE; - factoryV1 = FACTORY_V1_AVALANCHE; - } else { - wavax = WAVAX_FUJI; - factoryV1 = FACTORY_V1_FUJI; - } - - vm.broadcast(); - LBFactory factory = new LBFactory(msg.sender, FLASHLOAN_FEE); - console.log("LBFactory deployed -->", address(factory)); - - vm.broadcast(); - LBPair pairImplementation = new LBPair(factory); - console.log("LBPair implementation deployed -->", address(pairImplementation)); - - vm.broadcast(); - LBRouter router = new LBRouter(factory, IJoeFactory(factoryV1), IWAVAX(wavax)); - console.log("LBRouter deployed -->", address(router)); - - vm.startBroadcast(); - LBQuoter quoter = new LBQuoter(address(router), address(factoryV1), address(factory)); - console.log("LBQuoter deployed -->", address(quoter)); - - factory.setLBPairImplementation(address(pairImplementation)); - console.log("LBPair implementation set on factory"); - - factory.addQuoteAsset(IERC20(wavax)); - console.log("Wavax whitelisted as quote asset"); - vm.stopBroadcast(); - - vm.startBroadcast(); - uint256[] memory presetList = BipsConfig.getPresetList(); - for (uint256 i; i < presetList.length; i++) { - BipsConfig.FactoryPreset memory preset = BipsConfig.getPreset(presetList[i]); - factory.setPreset( - preset.binStep, - preset.baseFactor, - preset.filterPeriod, - preset.decayPeriod, - preset.reductionFactor, - preset.variableFeeControl, - preset.protocolShare, - preset.maxVolatilityAccumulated, - preset.sampleLifetime - ); - } - vm.stopBroadcast(); - } + // address private constant WAVAX_AVALANCHE = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7; + // address private constant WAVAX_FUJI = 0xd00ae08403B9bbb9124bB305C09058E32C39A48c; + + // address private constant FACTORY_V1_AVALANCHE = 0x9Ad6C38BE94206cA50bb0d90783181662f0Cfa10; + // address private constant FACTORY_V1_FUJI = 0xF5c7d9733e5f53abCC1695820c4818C59B457C2C; + + // address private wavax; + // address private factoryV1; + + // uint256 private constant FLASHLOAN_FEE = 5e12; + + // function run() external { + // if (block.chainid == 43114) { + // wavax = WAVAX_AVALANCHE; + // factoryV1 = FACTORY_V1_AVALANCHE; + // } else { + // wavax = WAVAX_FUJI; + // factoryV1 = FACTORY_V1_FUJI; + // } + + // vm.broadcast(); + // LBFactory factory = new LBFactory(msg.sender, FLASHLOAN_FEE); + // console.log("LBFactory deployed -->", address(factory)); + + // vm.broadcast(); + // LBPair pairImplementation = new LBPair(factory); + // console.log("LBPair implementation deployed -->", address(pairImplementation)); + + // vm.broadcast(); + // LBRouter router = new LBRouter(factory, IJoeFactory(factoryV1), IWAVAX(wavax)); + // console.log("LBRouter deployed -->", address(router)); + + // vm.startBroadcast(); + // LBQuoter quoter = new LBQuoter(address(router), address(factoryV1), address(factory)); + // console.log("LBQuoter deployed -->", address(quoter)); + + // factory.setLBPairImplementation(address(pairImplementation)); + // console.log("LBPair implementation set on factory"); + + // factory.addQuoteAsset(IERC20(wavax)); + // console.log("Wavax whitelisted as quote asset"); + // vm.stopBroadcast(); + + // vm.startBroadcast(); + // uint256[] memory presetList = BipsConfig.getPresetList(); + // for (uint256 i; i < presetList.length; i++) { + // BipsConfig.FactoryPreset memory preset = BipsConfig.getPreset(presetList[i]); + // factory.setPreset( + // preset.binStep, + // preset.baseFactor, + // preset.filterPeriod, + // preset.decayPeriod, + // preset.reductionFactor, + // preset.variableFeeControl, + // preset.protocolShare, + // preset.maxVolatilityAccumulated, + // preset.sampleLifetime + // ); + // } + // vm.stopBroadcast(); + // } } diff --git a/src/LBErrors.sol b/src/LBErrors.sol deleted file mode 100644 index e0cdfb3f..00000000 --- a/src/LBErrors.sol +++ /dev/null @@ -1,212 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import "./interfaces/ILBPair.sol"; - -/** - * LBRouter errors - */ - -error LBRouter__SenderIsNotWAVAX(); -error LBRouter__PairNotCreated(address tokenX, address tokenY, uint256 binStep); -error LBRouter__WrongAmounts(uint256 amount, uint256 reserve); -error LBRouter__SwapOverflows(uint256 id); -error LBRouter__BrokenSwapSafetyCheck(); -error LBRouter__NotFactoryOwner(); -error LBRouter__TooMuchTokensIn(uint256 excess); -error LBRouter__BinReserveOverflows(uint256 id); -error LBRouter__IdOverflows(int256 id); -error LBRouter__LengthsMismatch(); -error LBRouter__WrongTokenOrder(); -error LBRouter__IdSlippageCaught(uint256 activeIdDesired, uint256 idSlippage, uint256 activeId); -error LBRouter__AmountSlippageCaught(uint256 amountXMin, uint256 amountX, uint256 amountYMin, uint256 amountY); -error LBRouter__IdDesiredOverflows(uint256 idDesired, uint256 idSlippage); -error LBRouter__FailedToSendAVAX(address recipient, uint256 amount); -error LBRouter__DeadlineExceeded(uint256 deadline, uint256 currentTimestamp); -error LBRouter__AmountSlippageBPTooBig(uint256 amountSlippage); -error LBRouter__InsufficientAmountOut(uint256 amountOutMin, uint256 amountOut); -error LBRouter__MaxAmountInExceeded(uint256 amountInMax, uint256 amountIn); -error LBRouter__InvalidTokenPath(address wrongToken); -error LBRouter__InvalidVersion(uint256 version); -error LBRouter__WrongAvaxLiquidityParameters( - address tokenX, address tokenY, uint256 amountX, uint256 amountY, uint256 msgValue -); - -/** - * LBToken errors - */ - -error LBToken__SpenderNotApproved(address owner, address spender); -error LBToken__TransferFromOrToAddress0(); -error LBToken__MintToAddress0(); -error LBToken__BurnFromAddress0(); -error LBToken__BurnExceedsBalance(address from, uint256 id, uint256 amount); -error LBToken__LengthMismatch(uint256 accountsLength, uint256 idsLength); -error LBToken__SelfApproval(address owner); -error LBToken__TransferExceedsBalance(address from, uint256 id, uint256 amount); -error LBToken__TransferToSelf(); - -/** - * LBFactory errors - */ - -error LBFactory__IdenticalAddresses(IERC20 token); -error LBFactory__QuoteAssetNotWhitelisted(IERC20 quoteAsset); -error LBFactory__QuoteAssetAlreadyWhitelisted(IERC20 quoteAsset); -error LBFactory__AddressZero(); -error LBFactory__LBPairAlreadyExists(IERC20 tokenX, IERC20 tokenY, uint256 _binStep); -error LBFactory__LBPairNotCreated(IERC20 tokenX, IERC20 tokenY, uint256 binStep); -error LBFactory__DecreasingPeriods(uint16 filterPeriod, uint16 decayPeriod); -error LBFactory__ReductionFactorOverflows(uint16 reductionFactor, uint256 max); -error LBFactory__VariableFeeControlOverflows(uint16 variableFeeControl, uint256 max); -error LBFactory__BaseFeesBelowMin(uint256 baseFees, uint256 minBaseFees); -error LBFactory__FeesAboveMax(uint256 fees, uint256 maxFees); -error LBFactory__FlashLoanFeeAboveMax(uint256 fees, uint256 maxFees); -error LBFactory__BinStepRequirementsBreached(uint256 lowerBound, uint16 binStep, uint256 higherBound); -error LBFactory__ProtocolShareOverflows(uint16 protocolShare, uint256 max); -error LBFactory__FunctionIsLockedForUsers(address user); -error LBFactory__FactoryLockIsAlreadyInTheSameState(); -error LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); -error LBFactory__BinStepHasNoPreset(uint256 binStep); -error LBFactory__SameFeeRecipient(address feeRecipient); -error LBFactory__SameFlashLoanFee(uint256 flashLoanFee); -error LBFactory__LBPairSafetyCheckFailed(address LBPairImplementation); -error LBFactory__SameImplementation(address LBPairImplementation); -error LBFactory__ImplementationNotSet(); - -/** - * LBPair errors - */ - -error LBPair__InsufficientAmounts(); -error LBPair__AddressZero(); -error LBPair__AddressZeroOrThis(); -error LBPair__CompositionFactorFlawed(uint256 id); -error LBPair__InsufficientLiquidityMinted(uint256 id); -error LBPair__InsufficientLiquidityBurned(uint256 id); -error LBPair__WrongLengths(); -error LBPair__OnlyStrictlyIncreasingId(); -error LBPair__OnlyFactory(); -error LBPair__DistributionsOverflow(); -error LBPair__OnlyFeeRecipient(address feeRecipient, address sender); -error LBPair__OracleNotEnoughSample(); -error LBPair__AlreadyInitialized(); -error LBPair__OracleNewSizeTooSmall(uint256 newSize, uint256 oracleSize); -error LBPair__FlashLoanCallbackFailed(); -error LBPair__FlashLoanInvalidBalance(); -error LBPair__FlashLoanInvalidToken(); - -/** - * BinHelper errors - */ - -error BinHelper__BinStepOverflows(uint256 bp); -error BinHelper__IdOverflows(); - -/** - * Math128x128 errors - */ - -error Math128x128__PowerUnderflow(uint256 x, int256 y); -error Math128x128__LogUnderflow(); - -/** - * Math512Bits errors - */ - -error Math512Bits__MulDivOverflow(uint256 prod1, uint256 denominator); -error Math512Bits__ShiftDivOverflow(uint256 prod1, uint256 denominator); -error Math512Bits__MulShiftOverflow(uint256 prod1, uint256 offset); -error Math512Bits__OffsetOverflows(uint256 offset); - -/** - * Oracle errors - */ - -error Oracle__AlreadyInitialized(uint256 _index); -error Oracle__LookUpTimestampTooOld(uint256 _minTimestamp, uint256 _lookUpTimestamp); -error Oracle__NotInitialized(); - -/** - * PendingOwnable errors - */ - -error PendingOwnable__NotOwner(); -error PendingOwnable__NotPendingOwner(); -error PendingOwnable__PendingOwnerAlreadySet(); -error PendingOwnable__NoPendingOwner(); -error PendingOwnable__AddressZero(); - -/** - * ReentrancyGuardUpgradeable errors - */ - -error ReentrancyGuardUpgradeable__ReentrantCall(); -error ReentrancyGuardUpgradeable__AlreadyInitialized(); - -/** - * SafeCast errors - */ - -error SafeCast__Exceeds256Bits(uint256 x); -error SafeCast__Exceeds248Bits(uint256 x); -error SafeCast__Exceeds240Bits(uint256 x); -error SafeCast__Exceeds232Bits(uint256 x); -error SafeCast__Exceeds224Bits(uint256 x); -error SafeCast__Exceeds216Bits(uint256 x); -error SafeCast__Exceeds208Bits(uint256 x); -error SafeCast__Exceeds200Bits(uint256 x); -error SafeCast__Exceeds192Bits(uint256 x); -error SafeCast__Exceeds184Bits(uint256 x); -error SafeCast__Exceeds176Bits(uint256 x); -error SafeCast__Exceeds168Bits(uint256 x); -error SafeCast__Exceeds160Bits(uint256 x); -error SafeCast__Exceeds152Bits(uint256 x); -error SafeCast__Exceeds144Bits(uint256 x); -error SafeCast__Exceeds136Bits(uint256 x); -error SafeCast__Exceeds128Bits(uint256 x); -error SafeCast__Exceeds120Bits(uint256 x); -error SafeCast__Exceeds112Bits(uint256 x); -error SafeCast__Exceeds104Bits(uint256 x); -error SafeCast__Exceeds96Bits(uint256 x); -error SafeCast__Exceeds88Bits(uint256 x); -error SafeCast__Exceeds80Bits(uint256 x); -error SafeCast__Exceeds72Bits(uint256 x); -error SafeCast__Exceeds64Bits(uint256 x); -error SafeCast__Exceeds56Bits(uint256 x); -error SafeCast__Exceeds48Bits(uint256 x); -error SafeCast__Exceeds40Bits(uint256 x); -error SafeCast__Exceeds32Bits(uint256 x); -error SafeCast__Exceeds24Bits(uint256 x); -error SafeCast__Exceeds16Bits(uint256 x); -error SafeCast__Exceeds8Bits(uint256 x); - -/** - * TreeMath errors - */ - -error TreeMath__ErrorDepthSearch(); - -/** - * JoeLibrary errors - */ - -error JoeLibrary__IdenticalAddresses(); -error JoeLibrary__AddressZero(); -error JoeLibrary__InsufficientAmount(); -error JoeLibrary__InsufficientLiquidity(); - -/** - * TokenHelper errors - */ - -error TokenHelper__NonContract(); -error TokenHelper__CallFailed(); -error TokenHelper__TransferFailed(); - -/** - * LBQuoter errors - */ - -error LBQuoter_InvalidLength(); diff --git a/src/LBFactory.sol b/src/LBFactory.sol index 7a74f750..ed7f1dc1 100644 --- a/src/LBFactory.sol +++ b/src/LBFactory.sol @@ -2,619 +2,619 @@ pragma solidity 0.8.10; -import "openzeppelin/proxy/Clones.sol"; -import "openzeppelin/utils/structs/EnumerableSet.sol"; - -import "./LBErrors.sol"; -import "./libraries/BinHelper.sol"; -import "./libraries/Constants.sol"; -import "./libraries/Decoder.sol"; -import "./libraries/PendingOwnable.sol"; -import "./libraries/SafeCast.sol"; -import "./interfaces/ILBFactory.sol"; - -/// @title Liquidity Book Factory -/// @author Trader Joe -/// @notice Contract used to deploy and register new LBPairs. -/// Enables setting fee parameters, flashloan fees and LBPair implementation. -/// Unless the `creationUnlocked` is `true`, only the owner of the factory can create pairs. -contract LBFactory is PendingOwnable, ILBFactory { - using SafeCast for uint256; - using Decoder for bytes32; - using EnumerableSet for EnumerableSet.AddressSet; - - uint256 public constant override MAX_FEE = 0.1e18; // 10% - - uint256 public constant override MIN_BIN_STEP = 1; // 0.01% - uint256 public constant override MAX_BIN_STEP = 100; // 1%, can't be greater than 247 for indexing reasons - - uint256 public constant override MAX_PROTOCOL_SHARE = 2_500; // 25% - - address public override LBPairImplementation; - - address public override feeRecipient; - - /// @notice Whether the createLBPair function is unlocked and can be called by anyone (true) or only by owner (false) - bool public override creationUnlocked; - - uint256 public override flashLoanFee; - - ILBPair[] public override allLBPairs; - - /// @dev Mapping from a (tokenA, tokenB, binStep) to a LBPair. The tokens are ordered to save gas, but they can be - /// in the reverse order in the actual pair. Always query one of the 2 tokens of the pair to assert the order of the 2 tokens - mapping(IERC20 => mapping(IERC20 => mapping(uint256 => LBPairInformation))) private _LBPairsInfo; - - /// @dev Whether a preset was set or not, if the bit at `index` is 1, it means that the binStep `index` was set - /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas - bytes32 private _availablePresets; - - // The parameters presets - mapping(uint256 => bytes32) private _presets; - - EnumerableSet.AddressSet private _quoteAssetWhitelist; - - /// @dev Whether a LBPair was created with a bin step, if the bit at `index` is 1, it means that the LBPair with binStep `index` exists - /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas - mapping(IERC20 => mapping(IERC20 => bytes32)) private _availableLBPairBinSteps; - - /// @notice Constructor - /// @param _feeRecipient The address of the fee recipient - /// @param _flashLoanFee The value of the fee for flash loan - constructor(address _feeRecipient, uint256 _flashLoanFee) { - if (_flashLoanFee > MAX_FEE) revert LBFactory__FlashLoanFeeAboveMax(_flashLoanFee, MAX_FEE); - - _setFeeRecipient(_feeRecipient); - - flashLoanFee = _flashLoanFee; - emit FlashLoanFeeSet(0, _flashLoanFee); - } - - /// @notice View function to return the number of LBPairs created - /// @return The number of LBPair - function getNumberOfLBPairs() external view override returns (uint256) { - return allLBPairs.length; - } - - /// @notice View function to return the number of quote assets whitelisted - /// @return The number of quote assets - function getNumberOfQuoteAssets() external view override returns (uint256) { - return _quoteAssetWhitelist.length(); - } - - /// @notice View function to return the quote asset whitelisted at index `index` - /// @param _index The index - /// @return The address of the _quoteAsset at index `index` - function getQuoteAsset(uint256 _index) external view override returns (IERC20) { - return IERC20(_quoteAssetWhitelist.at(_index)); - } - - /// @notice View function to return whether a token is a quotedAsset (true) or not (false) - /// @param _token The address of the asset - /// @return Whether the token is a quote asset or not - function isQuoteAsset(IERC20 _token) external view override returns (bool) { - return _quoteAssetWhitelist.contains(address(_token)); - } - - /// @notice Returns the LBPairInformation if it exists, - /// if not, then the address 0 is returned. The order doesn't matter - /// @param _tokenA The address of the first token of the pair - /// @param _tokenB The address of the second token of the pair - /// @param _binStep The bin step of the LBPair - /// @return The LBPairInformation - function getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep) - external - view - override - returns (LBPairInformation memory) - { - return _getLBPairInformation(_tokenA, _tokenB, _binStep); - } - - /// @notice View function to return the different parameters of the preset - /// @param _binStep The bin step of the preset - /// @return baseFactor The base factor - /// @return filterPeriod The filter period of the preset - /// @return decayPeriod The decay period of the preset - /// @return reductionFactor The reduction factor of the preset - /// @return variableFeeControl The variable fee control of the preset - /// @return protocolShare The protocol share of the preset - /// @return maxVolatilityAccumulated The max volatility accumulated of the preset - /// @return sampleLifetime The sample lifetime of the preset - function getPreset(uint16 _binStep) - external - view - override - returns ( - uint256 baseFactor, - uint256 filterPeriod, - uint256 decayPeriod, - uint256 reductionFactor, - uint256 variableFeeControl, - uint256 protocolShare, - uint256 maxVolatilityAccumulated, - uint256 sampleLifetime - ) - { - bytes32 _preset = _presets[_binStep]; - if (_preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); - - uint256 _shift; - - // Safety check - require(_binStep == _preset.decode(type(uint16).max, _shift)); - - baseFactor = _preset.decode(type(uint16).max, _shift += 16); - filterPeriod = _preset.decode(type(uint16).max, _shift += 16); - decayPeriod = _preset.decode(type(uint16).max, _shift += 16); - reductionFactor = _preset.decode(type(uint16).max, _shift += 16); - variableFeeControl = _preset.decode(type(uint24).max, _shift += 16); - protocolShare = _preset.decode(type(uint16).max, _shift += 24); - maxVolatilityAccumulated = _preset.decode(type(uint24).max, _shift += 16); - - sampleLifetime = _preset.decode(type(uint16).max, 240); - } - - /// @notice View function to return the list of available binStep with a preset - /// @return presetsBinStep The list of binStep - function getAllBinSteps() external view override returns (uint256[] memory presetsBinStep) { - unchecked { - bytes32 _avPresets = _availablePresets; - uint256 _nbPresets = _avPresets.decode(type(uint8).max, 248); - - if (_nbPresets > 0) { - presetsBinStep = new uint256[](_nbPresets); - - uint256 _index; - for (uint256 i = MIN_BIN_STEP; i <= MAX_BIN_STEP; ++i) { - if (_avPresets.decode(1, i) == 1) { - presetsBinStep[_index] = i; - if (++_index == _nbPresets) break; - } - } - } - } - } - - /// @notice View function to return all the LBPair of a pair of tokens - /// @param _tokenX The first token of the pair - /// @param _tokenY The second token of the pair - /// @return LBPairsAvailable The list of available LBPairs - function getAllLBPairs(IERC20 _tokenX, IERC20 _tokenY) - external - view - override - returns (LBPairInformation[] memory LBPairsAvailable) - { - unchecked { - (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); - - bytes32 _avLBPairBinSteps = _availableLBPairBinSteps[_tokenA][_tokenB]; - uint256 _nbAvailable = _avLBPairBinSteps.decode(type(uint8).max, 248); - - if (_nbAvailable > 0) { - LBPairsAvailable = new LBPairInformation[](_nbAvailable); - - uint256 _index; - for (uint256 i = MIN_BIN_STEP; i <= MAX_BIN_STEP; ++i) { - if (_avLBPairBinSteps.decode(1, i) == 1) { - LBPairInformation memory _LBPairInformation = _LBPairsInfo[_tokenA][_tokenB][i]; - - LBPairsAvailable[_index] = LBPairInformation({ - binStep: i.safe16(), - LBPair: _LBPairInformation.LBPair, - createdByOwner: _LBPairInformation.createdByOwner, - ignoredForRouting: _LBPairInformation.ignoredForRouting - }); - if (++_index == _nbAvailable) break; - } - } - } - } - } - - /// @notice Set the LBPair implementation address - /// @dev Needs to be called by the owner - /// @param _LBPairImplementation The address of the implementation - function setLBPairImplementation(address _LBPairImplementation) external override onlyOwner { - if (ILBPair(_LBPairImplementation).factory() != this) { - revert LBFactory__LBPairSafetyCheckFailed(_LBPairImplementation); - } - - address _oldLBPairImplementation = LBPairImplementation; - if (_oldLBPairImplementation == _LBPairImplementation) { - revert LBFactory__SameImplementation(_LBPairImplementation); - } - - LBPairImplementation = _LBPairImplementation; - - emit LBPairImplementationSet(_oldLBPairImplementation, _LBPairImplementation); - } - - /// @notice Create a liquidity bin LBPair for _tokenX and _tokenY - /// @param _tokenX The address of the first token - /// @param _tokenY The address of the second token - /// @param _activeId The active id of the pair - /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @return _LBPair The address of the newly created LBPair - function createLBPair(IERC20 _tokenX, IERC20 _tokenY, uint24 _activeId, uint16 _binStep) - external - override - returns (ILBPair _LBPair) - { - address _owner = owner(); - if (!creationUnlocked && msg.sender != _owner) revert LBFactory__FunctionIsLockedForUsers(msg.sender); - - address _LBPairImplementation = LBPairImplementation; - - if (_LBPairImplementation == address(0)) revert LBFactory__ImplementationNotSet(); - - if (!_quoteAssetWhitelist.contains(address(_tokenY))) revert LBFactory__QuoteAssetNotWhitelisted(_tokenY); - - if (_tokenX == _tokenY) revert LBFactory__IdenticalAddresses(_tokenX); - - // safety check, making sure that the price can be calculated - BinHelper.getPriceFromId(_activeId, _binStep); - - // We sort token for storage efficiency, only one input needs to be stored because they are sorted - (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); - // single check is sufficient - if (address(_tokenA) == address(0)) revert LBFactory__AddressZero(); - if (address(_LBPairsInfo[_tokenA][_tokenB][_binStep].LBPair) != address(0)) { - revert LBFactory__LBPairAlreadyExists(_tokenX, _tokenY, _binStep); - } - - bytes32 _preset = _presets[_binStep]; - if (_preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); - - uint256 _sampleLifetime = _preset.decode(type(uint16).max, 240); - // We remove the bits that are not part of the feeParameters - _preset &= bytes32(uint256(type(uint144).max)); - - bytes32 _salt = keccak256(abi.encode(_tokenA, _tokenB, _binStep)); - _LBPair = ILBPair(Clones.cloneDeterministic(_LBPairImplementation, _salt)); - - _LBPair.initialize(_tokenX, _tokenY, _activeId, uint16(_sampleLifetime), _preset); - - _LBPairsInfo[_tokenA][_tokenB][_binStep] = LBPairInformation({ - binStep: _binStep, - LBPair: _LBPair, - createdByOwner: msg.sender == _owner, - ignoredForRouting: false - }); - - allLBPairs.push(_LBPair); - - { - bytes32 _avLBPairBinSteps = _availableLBPairBinSteps[_tokenA][_tokenB]; - // We add a 1 at bit `_binStep` as this binStep is now set - _avLBPairBinSteps = bytes32(uint256(_avLBPairBinSteps) | (1 << _binStep)); - - // Increase the number of lb pairs by 1 - _avLBPairBinSteps = bytes32(uint256(_avLBPairBinSteps) + (1 << 248)); - - // Save the changes - _availableLBPairBinSteps[_tokenA][_tokenB] = _avLBPairBinSteps; - } - - emit LBPairCreated(_tokenX, _tokenY, _binStep, _LBPair, allLBPairs.length - 1); - - emit FeeParametersSet( - msg.sender, - _LBPair, - _binStep, - _preset.decode(type(uint16).max, 16), - _preset.decode(type(uint16).max, 32), - _preset.decode(type(uint16).max, 48), - _preset.decode(type(uint16).max, 64), - _preset.decode(type(uint24).max, 80), - _preset.decode(type(uint16).max, 104), - _preset.decode(type(uint24).max, 120) - ); - } - - /// @notice Function to set whether the pair is ignored or not for routing, it will make the pair unusable by the router - /// @param _tokenX The address of the first token of the pair - /// @param _tokenY The address of the second token of the pair - /// @param _binStep The bin step in basis point of the pair - /// @param _ignored Whether to ignore (true) or not (false) the pair for routing - function setLBPairIgnored(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep, bool _ignored) - external - override - onlyOwner - { - (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); - - LBPairInformation memory _LBPairInformation = _LBPairsInfo[_tokenA][_tokenB][_binStep]; - if (address(_LBPairInformation.LBPair) == address(0)) revert LBFactory__AddressZero(); - - if (_LBPairInformation.ignoredForRouting == _ignored) revert LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); - - _LBPairsInfo[_tokenA][_tokenB][_binStep].ignoredForRouting = _ignored; - - emit LBPairIgnoredStateChanged(_LBPairInformation.LBPair, _ignored); - } - - /// @notice Sets the preset parameters of a bin step - /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep - /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam - /// @param _decayPeriod The period where the accumulator value is halved - /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator - /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it - /// @param _protocolShare The share of the fees received by the protocol - /// @param _maxVolatilityAccumulated The max value of the volatility accumulated - /// @param _sampleLifetime The lifetime of an oracle's sample - function setPreset( - uint16 _binStep, - uint16 _baseFactor, - uint16 _filterPeriod, - uint16 _decayPeriod, - uint16 _reductionFactor, - uint24 _variableFeeControl, - uint16 _protocolShare, - uint24 _maxVolatilityAccumulated, - uint16 _sampleLifetime - ) external override onlyOwner { - bytes32 _packedFeeParameters = _getPackedFeeParameters( - _binStep, - _baseFactor, - _filterPeriod, - _decayPeriod, - _reductionFactor, - _variableFeeControl, - _protocolShare, - _maxVolatilityAccumulated - ); - - // The last 16 bits are reserved for sampleLifetime - bytes32 _preset = - bytes32((uint256(_packedFeeParameters) & type(uint144).max) | (uint256(_sampleLifetime) << 240)); - - _presets[_binStep] = _preset; - - bytes32 _avPresets = _availablePresets; - if (_avPresets.decode(1, _binStep) == 0) { - // We add a 1 at bit `_binStep` as this binStep is now set - _avPresets = bytes32(uint256(_avPresets) | (1 << _binStep)); - - // Increase the number of preset by 1 - _avPresets = bytes32(uint256(_avPresets) + (1 << 248)); - - // Save the changes - _availablePresets = _avPresets; - } - - emit PresetSet( - _binStep, - _baseFactor, - _filterPeriod, - _decayPeriod, - _reductionFactor, - _variableFeeControl, - _protocolShare, - _maxVolatilityAccumulated, - _sampleLifetime - ); - } - - /// @notice Remove the preset linked to a binStep - /// @param _binStep The bin step to remove - function removePreset(uint16 _binStep) external override onlyOwner { - if (_presets[_binStep] == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); - - // Set the bit `_binStep` to 0 - bytes32 _avPresets = _availablePresets; - - _avPresets &= bytes32(type(uint256).max - (1 << _binStep)); - _avPresets = bytes32(uint256(_avPresets) - (1 << 248)); - - // Save the changes - _availablePresets = _avPresets; - delete _presets[_binStep]; - - emit PresetRemoved(_binStep); - } - - /// @notice Function to set the fee parameter of a LBPair - /// @param _tokenX The address of the first token - /// @param _tokenY The address of the second token - /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep - /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam - /// @param _decayPeriod The period where the accumulator value is halved - /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator - /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it - /// @param _protocolShare The share of the fees received by the protocol - /// @param _maxVolatilityAccumulated The max value of volatility accumulated - function setFeesParametersOnPair( - IERC20 _tokenX, - IERC20 _tokenY, - uint16 _binStep, - uint16 _baseFactor, - uint16 _filterPeriod, - uint16 _decayPeriod, - uint16 _reductionFactor, - uint24 _variableFeeControl, - uint16 _protocolShare, - uint24 _maxVolatilityAccumulated - ) external override onlyOwner { - ILBPair _LBPair = _getLBPairInformation(_tokenX, _tokenY, _binStep).LBPair; - - if (address(_LBPair) == address(0)) revert LBFactory__LBPairNotCreated(_tokenX, _tokenY, _binStep); - - bytes32 _packedFeeParameters = _getPackedFeeParameters( - _binStep, - _baseFactor, - _filterPeriod, - _decayPeriod, - _reductionFactor, - _variableFeeControl, - _protocolShare, - _maxVolatilityAccumulated - ); - - _LBPair.setFeesParameters(_packedFeeParameters); - - emit FeeParametersSet( - msg.sender, - _LBPair, - _binStep, - _baseFactor, - _filterPeriod, - _decayPeriod, - _reductionFactor, - _variableFeeControl, - _protocolShare, - _maxVolatilityAccumulated - ); - } - - /// @notice Function to set the recipient of the fees. This address needs to be able to receive ERC20s - /// @param _feeRecipient The address of the recipient - function setFeeRecipient(address _feeRecipient) external override onlyOwner { - _setFeeRecipient(_feeRecipient); - } - - /// @notice Function to set the flash loan fee - /// @param _flashLoanFee The value of the fee for flash loan - function setFlashLoanFee(uint256 _flashLoanFee) external override onlyOwner { - uint256 _oldFlashLoanFee = flashLoanFee; - - if (_oldFlashLoanFee == _flashLoanFee) revert LBFactory__SameFlashLoanFee(_flashLoanFee); - if (_flashLoanFee > MAX_FEE) revert LBFactory__FlashLoanFeeAboveMax(_flashLoanFee, MAX_FEE); - - flashLoanFee = _flashLoanFee; - emit FlashLoanFeeSet(_oldFlashLoanFee, _flashLoanFee); - } - - /// @notice Function to set the creation restriction of the Factory - /// @param _locked If the creation is restricted (true) or not (false) - function setFactoryLockedState(bool _locked) external override onlyOwner { - if (creationUnlocked != _locked) revert LBFactory__FactoryLockIsAlreadyInTheSameState(); - creationUnlocked = !_locked; - emit FactoryLockedStatusUpdated(_locked); - } - - /// @notice Function to add an asset to the whitelist of quote assets - /// @param _quoteAsset The quote asset (e.g: AVAX, USDC...) - function addQuoteAsset(IERC20 _quoteAsset) external override onlyOwner { - if (!_quoteAssetWhitelist.add(address(_quoteAsset))) { - revert LBFactory__QuoteAssetAlreadyWhitelisted(_quoteAsset); - } - - emit QuoteAssetAdded(_quoteAsset); - } - - /// @notice Function to remove an asset from the whitelist of quote assets - /// @param _quoteAsset The quote asset (e.g: AVAX, USDC...) - function removeQuoteAsset(IERC20 _quoteAsset) external override onlyOwner { - if (!_quoteAssetWhitelist.remove(address(_quoteAsset))) revert LBFactory__QuoteAssetNotWhitelisted(_quoteAsset); - - emit QuoteAssetRemoved(_quoteAsset); - } - - /// @notice Internal function to set the recipient of the fee - /// @param _feeRecipient The address of the recipient - function _setFeeRecipient(address _feeRecipient) internal { - if (_feeRecipient == address(0)) revert LBFactory__AddressZero(); - - address _oldFeeRecipient = feeRecipient; - if (_oldFeeRecipient == _feeRecipient) revert LBFactory__SameFeeRecipient(_feeRecipient); - - feeRecipient = _feeRecipient; - emit FeeRecipientSet(_oldFeeRecipient, _feeRecipient); - } - - function forceDecay(ILBPair _LBPair) external override onlyOwner { - _LBPair.forceDecay(); - } - - /// @notice Internal function to set the fee parameter of a LBPair - /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep - /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam - /// @param _decayPeriod The period where the accumulator value is halved - /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator - /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it - /// @param _protocolShare The share of the fees received by the protocol - /// @param _maxVolatilityAccumulated The max value of volatility accumulated - function _getPackedFeeParameters( - uint16 _binStep, - uint16 _baseFactor, - uint16 _filterPeriod, - uint16 _decayPeriod, - uint16 _reductionFactor, - uint24 _variableFeeControl, - uint16 _protocolShare, - uint24 _maxVolatilityAccumulated - ) private pure returns (bytes32) { - if (_binStep < MIN_BIN_STEP || _binStep > MAX_BIN_STEP) { - revert LBFactory__BinStepRequirementsBreached(MIN_BIN_STEP, _binStep, MAX_BIN_STEP); - } - - if (_filterPeriod >= _decayPeriod) revert LBFactory__DecreasingPeriods(_filterPeriod, _decayPeriod); - - if (_reductionFactor > Constants.BASIS_POINT_MAX) { - revert LBFactory__ReductionFactorOverflows(_reductionFactor, Constants.BASIS_POINT_MAX); - } - - if (_protocolShare > MAX_PROTOCOL_SHARE) { - revert LBFactory__ProtocolShareOverflows(_protocolShare, MAX_PROTOCOL_SHARE); - } - - { - uint256 _baseFee = (uint256(_baseFactor) * _binStep) * 1e10; - - // Can't overflow as the max value is `max(uint24) * (max(uint24) * max(uint16)) ** 2 < max(uint104)` - // It returns 18 decimals as: - // decimals(variableFeeControl * (volatilityAccumulated * binStep)**2 / 100) = 4 + (4 + 4) * 2 - 2 = 18 - uint256 _prod = uint256(_maxVolatilityAccumulated) * _binStep; - uint256 _maxVariableFee = (_prod * _prod * _variableFeeControl) / 100; - - if (_baseFee + _maxVariableFee > MAX_FEE) { - revert LBFactory__FeesAboveMax(_baseFee + _maxVariableFee, MAX_FEE); - } - } - - /// @dev It's very important that the sum of the sizes of those values is exactly 256 bits - /// here, (112 + 24) + 16 + 24 + 16 + 16 + 16 + 16 + 16 = 256 - return bytes32( - abi.encodePacked( - uint136(_maxVolatilityAccumulated), // The first 112 bits are reserved for the dynamic parameters - _protocolShare, - _variableFeeControl, - _reductionFactor, - _decayPeriod, - _filterPeriod, - _baseFactor, - _binStep - ) - ); - } - - /// @notice Returns the LBPairInformation if it exists, - /// if not, then the address 0 is returned. The order doesn't matter - /// @param _tokenA The address of the first token of the pair - /// @param _tokenB The address of the second token of the pair - /// @param _binStep The bin step of the LBPair - /// @return The LBPairInformation - function _getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep) - private - view - returns (LBPairInformation memory) - { - (_tokenA, _tokenB) = _sortTokens(_tokenA, _tokenB); - return _LBPairsInfo[_tokenA][_tokenB][_binStep]; - } - - /// @notice Private view function to sort 2 tokens in ascending order - /// @param _tokenA The first token - /// @param _tokenB The second token - /// @return The sorted first token - /// @return The sorted second token - function _sortTokens(IERC20 _tokenA, IERC20 _tokenB) private pure returns (IERC20, IERC20) { - if (_tokenA > _tokenB) (_tokenA, _tokenB) = (_tokenB, _tokenA); - return (_tokenA, _tokenB); - } -} +// import "openzeppelin/proxy/Clones.sol"; +// import "openzeppelin/utils/structs/EnumerableSet.sol"; + +// import "./LBErrors.sol"; +// import "./libraries/BinHelper.sol"; +// import "./libraries/Constants.sol"; +// import "./libraries/Decoder.sol"; +// import "./libraries/PendingOwnable.sol"; +// import "./libraries/math/SafeCast.sol"; +// import "./interfaces/ILBFactory.sol"; + +// /// @title Liquidity Book Factory +// /// @author Trader Joe +// /// @notice Contract used to deploy and register new LBPairs. +// /// Enables setting fee parameters, flashloan fees and LBPair implementation. +// /// Unless the `creationUnlocked` is `true`, only the owner of the factory can create pairs. +// contract LBFactory is PendingOwnable, ILBFactory { +// using SafeCast for uint256; +// using Decoder for bytes32; +// using EnumerableSet for EnumerableSet.AddressSet; + +// uint256 public constant override MAX_FEE = 0.1e18; // 10% + +// uint256 public constant override MIN_BIN_STEP = 1; // 0.01% +// uint256 public constant override MAX_BIN_STEP = 100; // 1%, can't be greater than 247 for indexing reasons + +// uint256 public constant override MAX_PROTOCOL_SHARE = 2_500; // 25% + +// address public override LBPairImplementation; + +// address public override feeRecipient; + +// /// @notice Whether the createLBPair function is unlocked and can be called by anyone (true) or only by owner (false) +// bool public override creationUnlocked; + +// uint256 public override flashLoanFee; + +// ILBPair[] public override allLBPairs; + +// /// @dev Mapping from a (tokenA, tokenB, binStep) to a LBPair. The tokens are ordered to save gas, but they can be +// /// in the reverse order in the actual pair. Always query one of the 2 tokens of the pair to assert the order of the 2 tokens +// mapping(IERC20 => mapping(IERC20 => mapping(uint256 => LBPairInformation))) private _LBPairsInfo; + +// /// @dev Whether a preset was set or not, if the bit at `index` is 1, it means that the binStep `index` was set +// /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas +// bytes32 private _availablePresets; + +// // The parameters presets +// mapping(uint256 => bytes32) private _presets; + +// EnumerableSet.AddressSet private _quoteAssetWhitelist; + +// /// @dev Whether a LBPair was created with a bin step, if the bit at `index` is 1, it means that the LBPair with binStep `index` exists +// /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas +// mapping(IERC20 => mapping(IERC20 => bytes32)) private _availableLBPairBinSteps; + +// /// @notice Constructor +// /// @param _feeRecipient The address of the fee recipient +// /// @param _flashLoanFee The value of the fee for flash loan +// constructor(address _feeRecipient, uint256 _flashLoanFee) { +// if (_flashLoanFee > MAX_FEE) revert LBFactory__FlashLoanFeeAboveMax(_flashLoanFee, MAX_FEE); + +// _setFeeRecipient(_feeRecipient); + +// flashLoanFee = _flashLoanFee; +// emit FlashLoanFeeSet(0, _flashLoanFee); +// } + +// /// @notice View function to return the number of LBPairs created +// /// @return The number of LBPair +// function getNumberOfLBPairs() external view override returns (uint256) { +// return allLBPairs.length; +// } + +// /// @notice View function to return the number of quote assets whitelisted +// /// @return The number of quote assets +// function getNumberOfQuoteAssets() external view override returns (uint256) { +// return _quoteAssetWhitelist.length(); +// } + +// /// @notice View function to return the quote asset whitelisted at index `index` +// /// @param _index The index +// /// @return The address of the _quoteAsset at index `index` +// function getQuoteAsset(uint256 _index) external view override returns (IERC20) { +// return IERC20(_quoteAssetWhitelist.at(_index)); +// } + +// /// @notice View function to return whether a token is a quotedAsset (true) or not (false) +// /// @param _token The address of the asset +// /// @return Whether the token is a quote asset or not +// function isQuoteAsset(IERC20 _token) external view override returns (bool) { +// return _quoteAssetWhitelist.contains(address(_token)); +// } + +// /// @notice Returns the LBPairInformation if it exists, +// /// if not, then the address 0 is returned. The order doesn't matter +// /// @param _tokenA The address of the first token of the pair +// /// @param _tokenB The address of the second token of the pair +// /// @param _binStep The bin step of the LBPair +// /// @return The LBPairInformation +// function getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep) +// external +// view +// override +// returns (LBPairInformation memory) +// { +// return _getLBPairInformation(_tokenA, _tokenB, _binStep); +// } + +// /// @notice View function to return the different parameters of the preset +// /// @param _binStep The bin step of the preset +// /// @return baseFactor The base factor +// /// @return filterPeriod The filter period of the preset +// /// @return decayPeriod The decay period of the preset +// /// @return reductionFactor The reduction factor of the preset +// /// @return variableFeeControl The variable fee control of the preset +// /// @return protocolShare The protocol share of the preset +// /// @return maxVolatilityAccumulated The max volatility accumulated of the preset +// /// @return sampleLifetime The sample lifetime of the preset +// function getPreset(uint16 _binStep) +// external +// view +// override +// returns ( +// uint256 baseFactor, +// uint256 filterPeriod, +// uint256 decayPeriod, +// uint256 reductionFactor, +// uint256 variableFeeControl, +// uint256 protocolShare, +// uint256 maxVolatilityAccumulated, +// uint256 sampleLifetime +// ) +// { +// bytes32 _preset = _presets[_binStep]; +// if (_preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); + +// uint256 _shift; + +// // Safety check +// require(_binStep == _preset.decode(type(uint16).max, _shift)); + +// baseFactor = _preset.decode(type(uint16).max, _shift += 16); +// filterPeriod = _preset.decode(type(uint16).max, _shift += 16); +// decayPeriod = _preset.decode(type(uint16).max, _shift += 16); +// reductionFactor = _preset.decode(type(uint16).max, _shift += 16); +// variableFeeControl = _preset.decode(type(uint24).max, _shift += 16); +// protocolShare = _preset.decode(type(uint16).max, _shift += 24); +// maxVolatilityAccumulated = _preset.decode(type(uint24).max, _shift += 16); + +// sampleLifetime = _preset.decode(type(uint16).max, 240); +// } + +// /// @notice View function to return the list of available binStep with a preset +// /// @return presetsBinStep The list of binStep +// function getAllBinSteps() external view override returns (uint256[] memory presetsBinStep) { +// unchecked { +// bytes32 _avPresets = _availablePresets; +// uint256 _nbPresets = _avPresets.decode(type(uint8).max, 248); + +// if (_nbPresets > 0) { +// presetsBinStep = new uint256[](_nbPresets); + +// uint256 _index; +// for (uint256 i = MIN_BIN_STEP; i <= MAX_BIN_STEP; ++i) { +// if (_avPresets.decode(1, i) == 1) { +// presetsBinStep[_index] = i; +// if (++_index == _nbPresets) break; +// } +// } +// } +// } +// } + +// /// @notice View function to return all the LBPair of a pair of tokens +// /// @param _tokenX The first token of the pair +// /// @param _tokenY The second token of the pair +// /// @return LBPairsAvailable The list of available LBPairs +// function getAllLBPairs(IERC20 _tokenX, IERC20 _tokenY) +// external +// view +// override +// returns (LBPairInformation[] memory LBPairsAvailable) +// { +// unchecked { +// (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); + +// bytes32 _avLBPairBinSteps = _availableLBPairBinSteps[_tokenA][_tokenB]; +// uint256 _nbAvailable = _avLBPairBinSteps.decode(type(uint8).max, 248); + +// if (_nbAvailable > 0) { +// LBPairsAvailable = new LBPairInformation[](_nbAvailable); + +// uint256 _index; +// for (uint256 i = MIN_BIN_STEP; i <= MAX_BIN_STEP; ++i) { +// if (_avLBPairBinSteps.decode(1, i) == 1) { +// LBPairInformation memory _LBPairInformation = _LBPairsInfo[_tokenA][_tokenB][i]; + +// LBPairsAvailable[_index] = LBPairInformation({ +// binStep: i.safe16(), +// LBPair: _LBPairInformation.LBPair, +// createdByOwner: _LBPairInformation.createdByOwner, +// ignoredForRouting: _LBPairInformation.ignoredForRouting +// }); +// if (++_index == _nbAvailable) break; +// } +// } +// } +// } +// } + +// /// @notice Set the LBPair implementation address +// /// @dev Needs to be called by the owner +// /// @param _LBPairImplementation The address of the implementation +// function setLBPairImplementation(address _LBPairImplementation) external override onlyOwner { +// if (ILBPair(_LBPairImplementation).factory() != this) { +// revert LBFactory__LBPairSafetyCheckFailed(_LBPairImplementation); +// } + +// address _oldLBPairImplementation = LBPairImplementation; +// if (_oldLBPairImplementation == _LBPairImplementation) { +// revert LBFactory__SameImplementation(_LBPairImplementation); +// } + +// LBPairImplementation = _LBPairImplementation; + +// emit LBPairImplementationSet(_oldLBPairImplementation, _LBPairImplementation); +// } + +// /// @notice Create a liquidity bin LBPair for _tokenX and _tokenY +// /// @param _tokenX The address of the first token +// /// @param _tokenY The address of the second token +// /// @param _activeId The active id of the pair +// /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) +// /// @return _LBPair The address of the newly created LBPair +// function createLBPair(IERC20 _tokenX, IERC20 _tokenY, uint24 _activeId, uint16 _binStep) +// external +// override +// returns (ILBPair _LBPair) +// { +// address _owner = owner(); +// if (!creationUnlocked && msg.sender != _owner) revert LBFactory__FunctionIsLockedForUsers(msg.sender); + +// address _LBPairImplementation = LBPairImplementation; + +// if (_LBPairImplementation == address(0)) revert LBFactory__ImplementationNotSet(); + +// if (!_quoteAssetWhitelist.contains(address(_tokenY))) revert LBFactory__QuoteAssetNotWhitelisted(_tokenY); + +// if (_tokenX == _tokenY) revert LBFactory__IdenticalAddresses(_tokenX); + +// // safety check, making sure that the price can be calculated +// BinHelper.getPriceFromId(_activeId, _binStep); + +// // We sort token for storage efficiency, only one input needs to be stored because they are sorted +// (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); +// // single check is sufficient +// if (address(_tokenA) == address(0)) revert LBFactory__AddressZero(); +// if (address(_LBPairsInfo[_tokenA][_tokenB][_binStep].LBPair) != address(0)) { +// revert LBFactory__LBPairAlreadyExists(_tokenX, _tokenY, _binStep); +// } + +// bytes32 _preset = _presets[_binStep]; +// if (_preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); + +// uint256 _sampleLifetime = _preset.decode(type(uint16).max, 240); +// // We remove the bits that are not part of the feeParameters +// _preset &= bytes32(uint256(type(uint144).max)); + +// bytes32 _salt = keccak256(abi.encode(_tokenA, _tokenB, _binStep)); +// _LBPair = ILBPair(Clones.cloneDeterministic(_LBPairImplementation, _salt)); + +// _LBPair.initialize(_tokenX, _tokenY, _activeId, uint16(_sampleLifetime), _preset); + +// _LBPairsInfo[_tokenA][_tokenB][_binStep] = LBPairInformation({ +// binStep: _binStep, +// LBPair: _LBPair, +// createdByOwner: msg.sender == _owner, +// ignoredForRouting: false +// }); + +// allLBPairs.push(_LBPair); + +// { +// bytes32 _avLBPairBinSteps = _availableLBPairBinSteps[_tokenA][_tokenB]; +// // We add a 1 at bit `_binStep` as this binStep is now set +// _avLBPairBinSteps = bytes32(uint256(_avLBPairBinSteps) | (1 << _binStep)); + +// // Increase the number of lb pairs by 1 +// _avLBPairBinSteps = bytes32(uint256(_avLBPairBinSteps) + (1 << 248)); + +// // Save the changes +// _availableLBPairBinSteps[_tokenA][_tokenB] = _avLBPairBinSteps; +// } + +// emit LBPairCreated(_tokenX, _tokenY, _binStep, _LBPair, allLBPairs.length - 1); + +// emit FeeParametersSet( +// msg.sender, +// _LBPair, +// _binStep, +// _preset.decode(type(uint16).max, 16), +// _preset.decode(type(uint16).max, 32), +// _preset.decode(type(uint16).max, 48), +// _preset.decode(type(uint16).max, 64), +// _preset.decode(type(uint24).max, 80), +// _preset.decode(type(uint16).max, 104), +// _preset.decode(type(uint24).max, 120) +// ); +// } + +// /// @notice Function to set whether the pair is ignored or not for routing, it will make the pair unusable by the router +// /// @param _tokenX The address of the first token of the pair +// /// @param _tokenY The address of the second token of the pair +// /// @param _binStep The bin step in basis point of the pair +// /// @param _ignored Whether to ignore (true) or not (false) the pair for routing +// function setLBPairIgnored(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep, bool _ignored) +// external +// override +// onlyOwner +// { +// (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); + +// LBPairInformation memory _LBPairInformation = _LBPairsInfo[_tokenA][_tokenB][_binStep]; +// if (address(_LBPairInformation.LBPair) == address(0)) revert LBFactory__AddressZero(); + +// if (_LBPairInformation.ignoredForRouting == _ignored) revert LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); + +// _LBPairsInfo[_tokenA][_tokenB][_binStep].ignoredForRouting = _ignored; + +// emit LBPairIgnoredStateChanged(_LBPairInformation.LBPair, _ignored); +// } + +// /// @notice Sets the preset parameters of a bin step +// /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) +// /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep +// /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam +// /// @param _decayPeriod The period where the accumulator value is halved +// /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator +// /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it +// /// @param _protocolShare The share of the fees received by the protocol +// /// @param _maxVolatilityAccumulated The max value of the volatility accumulated +// /// @param _sampleLifetime The lifetime of an oracle's sample +// function setPreset( +// uint16 _binStep, +// uint16 _baseFactor, +// uint16 _filterPeriod, +// uint16 _decayPeriod, +// uint16 _reductionFactor, +// uint24 _variableFeeControl, +// uint16 _protocolShare, +// uint24 _maxVolatilityAccumulated, +// uint16 _sampleLifetime +// ) external override onlyOwner { +// bytes32 _packedFeeParameters = _getPackedFeeParameters( +// _binStep, +// _baseFactor, +// _filterPeriod, +// _decayPeriod, +// _reductionFactor, +// _variableFeeControl, +// _protocolShare, +// _maxVolatilityAccumulated +// ); + +// // The last 16 bits are reserved for sampleLifetime +// bytes32 _preset = +// bytes32((uint256(_packedFeeParameters) & type(uint144).max) | (uint256(_sampleLifetime) << 240)); + +// _presets[_binStep] = _preset; + +// bytes32 _avPresets = _availablePresets; +// if (_avPresets.decode(1, _binStep) == 0) { +// // We add a 1 at bit `_binStep` as this binStep is now set +// _avPresets = bytes32(uint256(_avPresets) | (1 << _binStep)); + +// // Increase the number of preset by 1 +// _avPresets = bytes32(uint256(_avPresets) + (1 << 248)); + +// // Save the changes +// _availablePresets = _avPresets; +// } + +// emit PresetSet( +// _binStep, +// _baseFactor, +// _filterPeriod, +// _decayPeriod, +// _reductionFactor, +// _variableFeeControl, +// _protocolShare, +// _maxVolatilityAccumulated, +// _sampleLifetime +// ); +// } + +// /// @notice Remove the preset linked to a binStep +// /// @param _binStep The bin step to remove +// function removePreset(uint16 _binStep) external override onlyOwner { +// if (_presets[_binStep] == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); + +// // Set the bit `_binStep` to 0 +// bytes32 _avPresets = _availablePresets; + +// _avPresets &= bytes32(type(uint256).max - (1 << _binStep)); +// _avPresets = bytes32(uint256(_avPresets) - (1 << 248)); + +// // Save the changes +// _availablePresets = _avPresets; +// delete _presets[_binStep]; + +// emit PresetRemoved(_binStep); +// } + +// /// @notice Function to set the fee parameter of a LBPair +// /// @param _tokenX The address of the first token +// /// @param _tokenY The address of the second token +// /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) +// /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep +// /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam +// /// @param _decayPeriod The period where the accumulator value is halved +// /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator +// /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it +// /// @param _protocolShare The share of the fees received by the protocol +// /// @param _maxVolatilityAccumulated The max value of volatility accumulated +// function setFeesParametersOnPair( +// IERC20 _tokenX, +// IERC20 _tokenY, +// uint16 _binStep, +// uint16 _baseFactor, +// uint16 _filterPeriod, +// uint16 _decayPeriod, +// uint16 _reductionFactor, +// uint24 _variableFeeControl, +// uint16 _protocolShare, +// uint24 _maxVolatilityAccumulated +// ) external override onlyOwner { +// ILBPair _LBPair = _getLBPairInformation(_tokenX, _tokenY, _binStep).LBPair; + +// if (address(_LBPair) == address(0)) revert LBFactory__LBPairNotCreated(_tokenX, _tokenY, _binStep); + +// bytes32 _packedFeeParameters = _getPackedFeeParameters( +// _binStep, +// _baseFactor, +// _filterPeriod, +// _decayPeriod, +// _reductionFactor, +// _variableFeeControl, +// _protocolShare, +// _maxVolatilityAccumulated +// ); + +// _LBPair.setFeesParameters(_packedFeeParameters); + +// emit FeeParametersSet( +// msg.sender, +// _LBPair, +// _binStep, +// _baseFactor, +// _filterPeriod, +// _decayPeriod, +// _reductionFactor, +// _variableFeeControl, +// _protocolShare, +// _maxVolatilityAccumulated +// ); +// } + +// /// @notice Function to set the recipient of the fees. This address needs to be able to receive ERC20s +// /// @param _feeRecipient The address of the recipient +// function setFeeRecipient(address _feeRecipient) external override onlyOwner { +// _setFeeRecipient(_feeRecipient); +// } + +// /// @notice Function to set the flash loan fee +// /// @param _flashLoanFee The value of the fee for flash loan +// function setFlashLoanFee(uint256 _flashLoanFee) external override onlyOwner { +// uint256 _oldFlashLoanFee = flashLoanFee; + +// if (_oldFlashLoanFee == _flashLoanFee) revert LBFactory__SameFlashLoanFee(_flashLoanFee); +// if (_flashLoanFee > MAX_FEE) revert LBFactory__FlashLoanFeeAboveMax(_flashLoanFee, MAX_FEE); + +// flashLoanFee = _flashLoanFee; +// emit FlashLoanFeeSet(_oldFlashLoanFee, _flashLoanFee); +// } + +// /// @notice Function to set the creation restriction of the Factory +// /// @param _locked If the creation is restricted (true) or not (false) +// function setFactoryLockedState(bool _locked) external override onlyOwner { +// if (creationUnlocked != _locked) revert LBFactory__FactoryLockIsAlreadyInTheSameState(); +// creationUnlocked = !_locked; +// emit FactoryLockedStatusUpdated(_locked); +// } + +// /// @notice Function to add an asset to the whitelist of quote assets +// /// @param _quoteAsset The quote asset (e.g: AVAX, USDC...) +// function addQuoteAsset(IERC20 _quoteAsset) external override onlyOwner { +// if (!_quoteAssetWhitelist.add(address(_quoteAsset))) { +// revert LBFactory__QuoteAssetAlreadyWhitelisted(_quoteAsset); +// } + +// emit QuoteAssetAdded(_quoteAsset); +// } + +// /// @notice Function to remove an asset from the whitelist of quote assets +// /// @param _quoteAsset The quote asset (e.g: AVAX, USDC...) +// function removeQuoteAsset(IERC20 _quoteAsset) external override onlyOwner { +// if (!_quoteAssetWhitelist.remove(address(_quoteAsset))) revert LBFactory__QuoteAssetNotWhitelisted(_quoteAsset); + +// emit QuoteAssetRemoved(_quoteAsset); +// } + +// /// @notice Internal function to set the recipient of the fee +// /// @param _feeRecipient The address of the recipient +// function _setFeeRecipient(address _feeRecipient) internal { +// if (_feeRecipient == address(0)) revert LBFactory__AddressZero(); + +// address _oldFeeRecipient = feeRecipient; +// if (_oldFeeRecipient == _feeRecipient) revert LBFactory__SameFeeRecipient(_feeRecipient); + +// feeRecipient = _feeRecipient; +// emit FeeRecipientSet(_oldFeeRecipient, _feeRecipient); +// } + +// function forceDecay(ILBPair _LBPair) external override onlyOwner { +// _LBPair.forceDecay(); +// } + +// /// @notice Internal function to set the fee parameter of a LBPair +// /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) +// /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep +// /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam +// /// @param _decayPeriod The period where the accumulator value is halved +// /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator +// /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it +// /// @param _protocolShare The share of the fees received by the protocol +// /// @param _maxVolatilityAccumulated The max value of volatility accumulated +// function _getPackedFeeParameters( +// uint16 _binStep, +// uint16 _baseFactor, +// uint16 _filterPeriod, +// uint16 _decayPeriod, +// uint16 _reductionFactor, +// uint24 _variableFeeControl, +// uint16 _protocolShare, +// uint24 _maxVolatilityAccumulated +// ) private pure returns (bytes32) { +// if (_binStep < MIN_BIN_STEP || _binStep > MAX_BIN_STEP) { +// revert LBFactory__BinStepRequirementsBreached(MIN_BIN_STEP, _binStep, MAX_BIN_STEP); +// } + +// if (_filterPeriod >= _decayPeriod) revert LBFactory__DecreasingPeriods(_filterPeriod, _decayPeriod); + +// if (_reductionFactor > Constants.BASIS_POINT_MAX) { +// revert LBFactory__ReductionFactorOverflows(_reductionFactor, Constants.BASIS_POINT_MAX); +// } + +// if (_protocolShare > MAX_PROTOCOL_SHARE) { +// revert LBFactory__ProtocolShareOverflows(_protocolShare, MAX_PROTOCOL_SHARE); +// } + +// { +// uint256 _baseFee = (uint256(_baseFactor) * _binStep) * 1e10; + +// // Can't overflow as the max value is `max(uint24) * (max(uint24) * max(uint16)) ** 2 < max(uint104)` +// // It returns 18 decimals as: +// // decimals(variableFeeControl * (volatilityAccumulated * binStep)**2 / 100) = 4 + (4 + 4) * 2 - 2 = 18 +// uint256 _prod = uint256(_maxVolatilityAccumulated) * _binStep; +// uint256 _maxVariableFee = (_prod * _prod * _variableFeeControl) / 100; + +// if (_baseFee + _maxVariableFee > MAX_FEE) { +// revert LBFactory__FeesAboveMax(_baseFee + _maxVariableFee, MAX_FEE); +// } +// } + +// /// @dev It's very important that the sum of the sizes of those values is exactly 256 bits +// /// here, (112 + 24) + 16 + 24 + 16 + 16 + 16 + 16 + 16 = 256 +// return bytes32( +// abi.encodePacked( +// uint136(_maxVolatilityAccumulated), // The first 112 bits are reserved for the dynamic parameters +// _protocolShare, +// _variableFeeControl, +// _reductionFactor, +// _decayPeriod, +// _filterPeriod, +// _baseFactor, +// _binStep +// ) +// ); +// } + +// /// @notice Returns the LBPairInformation if it exists, +// /// if not, then the address 0 is returned. The order doesn't matter +// /// @param _tokenA The address of the first token of the pair +// /// @param _tokenB The address of the second token of the pair +// /// @param _binStep The bin step of the LBPair +// /// @return The LBPairInformation +// function _getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep) +// private +// view +// returns (LBPairInformation memory) +// { +// (_tokenA, _tokenB) = _sortTokens(_tokenA, _tokenB); +// return _LBPairsInfo[_tokenA][_tokenB][_binStep]; +// } + +// /// @notice Private view function to sort 2 tokens in ascending order +// /// @param _tokenA The first token +// /// @param _tokenB The second token +// /// @return The sorted first token +// /// @return The sorted second token +// function _sortTokens(IERC20 _tokenA, IERC20 _tokenB) private pure returns (IERC20, IERC20) { +// if (_tokenA > _tokenB) (_tokenA, _tokenB) = (_tokenB, _tokenA); +// return (_tokenA, _tokenB); +// } +// } diff --git a/src/LBQuoter.sol b/src/LBQuoter.sol index 387b1a0a..4962fb72 100644 --- a/src/LBQuoter.sol +++ b/src/LBQuoter.sol @@ -2,220 +2,220 @@ pragma solidity 0.8.10; -import "./LBErrors.sol"; -import "./libraries/BinHelper.sol"; -import "./libraries/JoeLibrary.sol"; -import "./libraries/Math512Bits.sol"; -import "./interfaces/IJoeFactory.sol"; -import "./interfaces/IJoePair.sol"; -import "./interfaces/ILBFactory.sol"; -import "./interfaces/ILBRouter.sol"; - -/// @title Liquidity Book Quoter -/// @author Trader Joe -/// @notice Helper contract to determine best path through multiple markets -contract LBQuoter { - using Math512Bits for uint256; - - /// @notice Dex V2 router address - address public immutable routerV2; - /// @notice Dex V1 factory address - address public immutable factoryV1; - /// @notice Dex V2 factory address - address public immutable factoryV2; - - struct Quote { - address[] route; - address[] pairs; - uint256[] binSteps; - uint256[] amounts; - uint256[] virtualAmountsWithoutSlippage; - uint256[] fees; - } - - /// @notice Constructor - /// @param _routerV2 Dex V2 router address - /// @param _factoryV1 Dex V1 factory address - /// @param _factoryV2 Dex V2 factory address - constructor(address _routerV2, address _factoryV1, address _factoryV2) { - routerV2 = _routerV2; - factoryV1 = _factoryV1; - factoryV2 = _factoryV2; - } - - /// @notice Finds the best path given a list of tokens and the input amount wanted from the swap - /// @param _route List of the tokens to go through - /// @param _amountIn Swap amount in - /// @return quote The Quote structure containing the necessary element to perform the swap - function findBestPathFromAmountIn(address[] calldata _route, uint256 _amountIn) - public - view - returns (Quote memory quote) - { - if (_route.length < 2) { - revert LBQuoter_InvalidLength(); - } - - quote.route = _route; - - uint256 swapLength = _route.length - 1; - quote.pairs = new address[](swapLength); - quote.binSteps = new uint256[](swapLength); - quote.fees = new uint256[](swapLength); - quote.amounts = new uint256[](_route.length); - quote.virtualAmountsWithoutSlippage = new uint256[](_route.length); - - quote.amounts[0] = _amountIn; - quote.virtualAmountsWithoutSlippage[0] = _amountIn; - - for (uint256 i; i < swapLength; i++) { - // Fetch swap for V1 - quote.pairs[i] = IJoeFactory(factoryV1).getPair(_route[i], _route[i + 1]); - - if (quote.pairs[i] != address(0) && quote.amounts[i] > 0) { - (uint256 reserveIn, uint256 reserveOut) = _getReserves(quote.pairs[i], _route[i], _route[i + 1]); - - if (reserveIn > 0 && reserveOut > 0) { - quote.amounts[i + 1] = JoeLibrary.getAmountOut(quote.amounts[i], reserveIn, reserveOut); - quote.virtualAmountsWithoutSlippage[i + 1] = - JoeLibrary.quote(quote.virtualAmountsWithoutSlippage[i] * 997, reserveIn * 1000, reserveOut); - quote.fees[i] = 0.003e18; // 0.3% - } - } - - // Fetch swaps for V2 - ILBFactory.LBPairInformation[] memory LBPairsAvailable = - ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i]), IERC20(_route[i + 1])); - - if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { - for (uint256 j; j < LBPairsAvailable.length; j++) { - if (!LBPairsAvailable[j].ignoredForRouting) { - bool swapForY = address(LBPairsAvailable[j].LBPair.tokenY()) == _route[i + 1]; - - try ILBRouter(routerV2).getSwapOut(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) - returns (uint256 swapAmountOut, uint256 fees) { - if (swapAmountOut > quote.amounts[i + 1]) { - quote.amounts[i + 1] = swapAmountOut; - quote.pairs[i] = address(LBPairsAvailable[j].LBPair); - quote.binSteps[i] = LBPairsAvailable[j].binStep; - - // Getting current price - (,, uint256 activeId) = LBPairsAvailable[j].LBPair.getReservesAndId(); - quote.virtualAmountsWithoutSlippage[i + 1] = _getV2Quote( - quote.virtualAmountsWithoutSlippage[i] - fees, activeId, quote.binSteps[i], swapForY - ); - - quote.fees[i] = (fees * 1e18) / quote.amounts[i]; // fee percentage in amountIn - } - } catch {} - } - } - } - } - } - - /// @notice Finds the best path given a list of tokens and the output amount wanted from the swap - /// @param _route List of the tokens to go through - /// @param _amountOut Swap amount out - /// @return quote The Quote structure containing the necessary element to perform the swap - function findBestPathFromAmountOut(address[] calldata _route, uint256 _amountOut) - public - view - returns (Quote memory quote) - { - if (_route.length < 2) { - revert LBQuoter_InvalidLength(); - } - quote.route = _route; - - uint256 swapLength = _route.length - 1; - quote.pairs = new address[](swapLength); - quote.binSteps = new uint256[](swapLength); - quote.fees = new uint256[](swapLength); - quote.amounts = new uint256[](_route.length); - quote.virtualAmountsWithoutSlippage = new uint256[](_route.length); - - quote.amounts[swapLength] = _amountOut; - quote.virtualAmountsWithoutSlippage[swapLength] = _amountOut; - - for (uint256 i = swapLength; i > 0; i--) { - // Fetch swap for V1 - quote.pairs[i - 1] = IJoeFactory(factoryV1).getPair(_route[i - 1], _route[i]); - if (quote.pairs[i - 1] != address(0) && quote.amounts[i] > 0) { - (uint256 reserveIn, uint256 reserveOut) = _getReserves(quote.pairs[i - 1], _route[i - 1], _route[i]); - - if (reserveIn > 0 && reserveOut > quote.amounts[i]) { - quote.amounts[i - 1] = JoeLibrary.getAmountIn(quote.amounts[i], reserveIn, reserveOut); - quote.virtualAmountsWithoutSlippage[i - 1] = - JoeLibrary.quote(quote.virtualAmountsWithoutSlippage[i] * 1000, reserveOut * 997, reserveIn) + 1; - - quote.fees[i - 1] = 0.003e18; // 0.3% - } - } - - // Fetch swaps for V2 - ILBFactory.LBPairInformation[] memory LBPairsAvailable = - ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i - 1]), IERC20(_route[i])); - - if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { - for (uint256 j; j < LBPairsAvailable.length; j++) { - if (!LBPairsAvailable[j].ignoredForRouting) { - bool swapForY = address(LBPairsAvailable[j].LBPair.tokenY()) == _route[i]; - try ILBRouter(routerV2).getSwapIn(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) - returns (uint256 swapAmountIn, uint256 fees) { - if (swapAmountIn != 0 && (swapAmountIn < quote.amounts[i - 1] || quote.amounts[i - 1] == 0)) - { - quote.amounts[i - 1] = swapAmountIn; - quote.pairs[i - 1] = address(LBPairsAvailable[j].LBPair); - quote.binSteps[i - 1] = LBPairsAvailable[j].binStep; - - // Getting current price - (,, uint256 activeId) = LBPairsAvailable[j].LBPair.getReservesAndId(); - quote.virtualAmountsWithoutSlippage[i - 1] = _getV2Quote( - quote.virtualAmountsWithoutSlippage[i], activeId, quote.binSteps[i - 1], !swapForY - ) + fees; - - quote.fees[i - 1] = (fees * 1e18) / quote.amounts[i - 1]; // fee percentage in amountIn - } - } catch {} - } - } - } - } - } - - /// @dev Forked from JoeLibrary - /// @dev Doesn't rely on the init code hash of the factory - /// @param _pair Address of the pair - /// @param _tokenA Address of token A - /// @param _tokenB Address of token B - /// @return reserveA Reserve of token A in the pair - /// @return reserveB Reserve of token B in the pair - function _getReserves(address _pair, address _tokenA, address _tokenB) - internal - view - returns (uint256 reserveA, uint256 reserveB) - { - (address token0,) = JoeLibrary.sortTokens(_tokenA, _tokenB); - (uint256 reserve0, uint256 reserve1,) = IJoePair(_pair).getReserves(); - (reserveA, reserveB) = _tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); - } - - /// @dev Calculates a quote for a V2 pair - /// @param _amount Amount in to consider - /// @param _activeId Current active Id of the considred pair - /// @param _binStep Bin step of the considered pair - /// @param _swapForY Boolean describing if we are swapping from X to Y or the opposite - /// @return quote Amount Out if _amount was swapped with no slippage and no fees - function _getV2Quote(uint256 _amount, uint256 _activeId, uint256 _binStep, bool _swapForY) - internal - pure - returns (uint256 quote) - { - if (_swapForY) { - quote = BinHelper.getPriceFromId(_activeId, _binStep).mulShiftRoundDown(_amount, Constants.SCALE_OFFSET); - } else { - quote = _amount.shiftDivRoundDown(Constants.SCALE_OFFSET, BinHelper.getPriceFromId(_activeId, _binStep)); - } - } -} +// import "./LBErrors.sol"; +// import "./libraries/BinHelper.sol"; +// import "./libraries/JoeLibrary.sol"; +// import "./libraries/Math512Bits.sol"; +// import "./interfaces/IJoeFactory.sol"; +// import "./interfaces/IJoePair.sol"; +// import "./interfaces/ILBFactory.sol"; +// import "./interfaces/ILBRouter.sol"; + +// /// @title Liquidity Book Quoter +// /// @author Trader Joe +// /// @notice Helper contract to determine best path through multiple markets +// contract LBQuoter { +// using Math512Bits for uint256; + +// /// @notice Dex V2 router address +// address public immutable routerV2; +// /// @notice Dex V1 factory address +// address public immutable factoryV1; +// /// @notice Dex V2 factory address +// address public immutable factoryV2; + +// struct Quote { +// address[] route; +// address[] pairs; +// uint256[] binSteps; +// uint256[] amounts; +// uint256[] virtualAmountsWithoutSlippage; +// uint256[] fees; +// } + +// /// @notice Constructor +// /// @param _routerV2 Dex V2 router address +// /// @param _factoryV1 Dex V1 factory address +// /// @param _factoryV2 Dex V2 factory address +// constructor(address _routerV2, address _factoryV1, address _factoryV2) { +// routerV2 = _routerV2; +// factoryV1 = _factoryV1; +// factoryV2 = _factoryV2; +// } + +// /// @notice Finds the best path given a list of tokens and the input amount wanted from the swap +// /// @param _route List of the tokens to go through +// /// @param _amountIn Swap amount in +// /// @return quote The Quote structure containing the necessary element to perform the swap +// function findBestPathFromAmountIn(address[] calldata _route, uint256 _amountIn) +// public +// view +// returns (Quote memory quote) +// { +// if (_route.length < 2) { +// revert LBQuoter_InvalidLength(); +// } + +// quote.route = _route; + +// uint256 swapLength = _route.length - 1; +// quote.pairs = new address[](swapLength); +// quote.binSteps = new uint256[](swapLength); +// quote.fees = new uint256[](swapLength); +// quote.amounts = new uint256[](_route.length); +// quote.virtualAmountsWithoutSlippage = new uint256[](_route.length); + +// quote.amounts[0] = _amountIn; +// quote.virtualAmountsWithoutSlippage[0] = _amountIn; + +// for (uint256 i; i < swapLength; i++) { +// // Fetch swap for V1 +// quote.pairs[i] = IJoeFactory(factoryV1).getPair(_route[i], _route[i + 1]); + +// if (quote.pairs[i] != address(0) && quote.amounts[i] > 0) { +// (uint256 reserveIn, uint256 reserveOut) = _getReserves(quote.pairs[i], _route[i], _route[i + 1]); + +// if (reserveIn > 0 && reserveOut > 0) { +// quote.amounts[i + 1] = JoeLibrary.getAmountOut(quote.amounts[i], reserveIn, reserveOut); +// quote.virtualAmountsWithoutSlippage[i + 1] = +// JoeLibrary.quote(quote.virtualAmountsWithoutSlippage[i] * 997, reserveIn * 1000, reserveOut); +// quote.fees[i] = 0.003e18; // 0.3% +// } +// } + +// // Fetch swaps for V2 +// ILBFactory.LBPairInformation[] memory LBPairsAvailable = +// ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i]), IERC20(_route[i + 1])); + +// if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { +// for (uint256 j; j < LBPairsAvailable.length; j++) { +// if (!LBPairsAvailable[j].ignoredForRouting) { +// bool swapForY = address(LBPairsAvailable[j].LBPair.tokenY()) == _route[i + 1]; + +// try ILBRouter(routerV2).getSwapOut(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) +// returns (uint256 swapAmountOut, uint256 fees) { +// if (swapAmountOut > quote.amounts[i + 1]) { +// quote.amounts[i + 1] = swapAmountOut; +// quote.pairs[i] = address(LBPairsAvailable[j].LBPair); +// quote.binSteps[i] = LBPairsAvailable[j].binStep; + +// // Getting current price +// (,, uint256 activeId) = LBPairsAvailable[j].LBPair.getReservesAndId(); +// quote.virtualAmountsWithoutSlippage[i + 1] = _getV2Quote( +// quote.virtualAmountsWithoutSlippage[i] - fees, activeId, quote.binSteps[i], swapForY +// ); + +// quote.fees[i] = (fees * 1e18) / quote.amounts[i]; // fee percentage in amountIn +// } +// } catch {} +// } +// } +// } +// } +// } + +// /// @notice Finds the best path given a list of tokens and the output amount wanted from the swap +// /// @param _route List of the tokens to go through +// /// @param _amountOut Swap amount out +// /// @return quote The Quote structure containing the necessary element to perform the swap +// function findBestPathFromAmountOut(address[] calldata _route, uint256 _amountOut) +// public +// view +// returns (Quote memory quote) +// { +// if (_route.length < 2) { +// revert LBQuoter_InvalidLength(); +// } +// quote.route = _route; + +// uint256 swapLength = _route.length - 1; +// quote.pairs = new address[](swapLength); +// quote.binSteps = new uint256[](swapLength); +// quote.fees = new uint256[](swapLength); +// quote.amounts = new uint256[](_route.length); +// quote.virtualAmountsWithoutSlippage = new uint256[](_route.length); + +// quote.amounts[swapLength] = _amountOut; +// quote.virtualAmountsWithoutSlippage[swapLength] = _amountOut; + +// for (uint256 i = swapLength; i > 0; i--) { +// // Fetch swap for V1 +// quote.pairs[i - 1] = IJoeFactory(factoryV1).getPair(_route[i - 1], _route[i]); +// if (quote.pairs[i - 1] != address(0) && quote.amounts[i] > 0) { +// (uint256 reserveIn, uint256 reserveOut) = _getReserves(quote.pairs[i - 1], _route[i - 1], _route[i]); + +// if (reserveIn > 0 && reserveOut > quote.amounts[i]) { +// quote.amounts[i - 1] = JoeLibrary.getAmountIn(quote.amounts[i], reserveIn, reserveOut); +// quote.virtualAmountsWithoutSlippage[i - 1] = +// JoeLibrary.quote(quote.virtualAmountsWithoutSlippage[i] * 1000, reserveOut * 997, reserveIn) + 1; + +// quote.fees[i - 1] = 0.003e18; // 0.3% +// } +// } + +// // Fetch swaps for V2 +// ILBFactory.LBPairInformation[] memory LBPairsAvailable = +// ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i - 1]), IERC20(_route[i])); + +// if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { +// for (uint256 j; j < LBPairsAvailable.length; j++) { +// if (!LBPairsAvailable[j].ignoredForRouting) { +// bool swapForY = address(LBPairsAvailable[j].LBPair.tokenY()) == _route[i]; +// try ILBRouter(routerV2).getSwapIn(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) +// returns (uint256 swapAmountIn, uint256 fees) { +// if (swapAmountIn != 0 && (swapAmountIn < quote.amounts[i - 1] || quote.amounts[i - 1] == 0)) +// { +// quote.amounts[i - 1] = swapAmountIn; +// quote.pairs[i - 1] = address(LBPairsAvailable[j].LBPair); +// quote.binSteps[i - 1] = LBPairsAvailable[j].binStep; + +// // Getting current price +// (,, uint256 activeId) = LBPairsAvailable[j].LBPair.getReservesAndId(); +// quote.virtualAmountsWithoutSlippage[i - 1] = _getV2Quote( +// quote.virtualAmountsWithoutSlippage[i], activeId, quote.binSteps[i - 1], !swapForY +// ) + fees; + +// quote.fees[i - 1] = (fees * 1e18) / quote.amounts[i - 1]; // fee percentage in amountIn +// } +// } catch {} +// } +// } +// } +// } +// } + +// /// @dev Forked from JoeLibrary +// /// @dev Doesn't rely on the init code hash of the factory +// /// @param _pair Address of the pair +// /// @param _tokenA Address of token A +// /// @param _tokenB Address of token B +// /// @return reserveA Reserve of token A in the pair +// /// @return reserveB Reserve of token B in the pair +// function _getReserves(address _pair, address _tokenA, address _tokenB) +// internal +// view +// returns (uint256 reserveA, uint256 reserveB) +// { +// (address token0,) = JoeLibrary.sortTokens(_tokenA, _tokenB); +// (uint256 reserve0, uint256 reserve1,) = IJoePair(_pair).getReserves(); +// (reserveA, reserveB) = _tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); +// } + +// /// @dev Calculates a quote for a V2 pair +// /// @param _amount Amount in to consider +// /// @param _activeId Current active Id of the considred pair +// /// @param _binStep Bin step of the considered pair +// /// @param _swapForY Boolean describing if we are swapping from X to Y or the opposite +// /// @return quote Amount Out if _amount was swapped with no slippage and no fees +// function _getV2Quote(uint256 _amount, uint256 _activeId, uint256 _binStep, bool _swapForY) +// internal +// pure +// returns (uint256 quote) +// { +// if (_swapForY) { +// quote = BinHelper.getPriceFromId(_activeId, _binStep).mulShiftRoundDown(_amount, Constants.SCALE_OFFSET); +// } else { +// quote = _amount.shiftDivRoundDown(Constants.SCALE_OFFSET, BinHelper.getPriceFromId(_activeId, _binStep)); +// } +// } +// } diff --git a/src/LBRouter.sol b/src/LBRouter.sol index d784e147..feb1eae4 100644 --- a/src/LBRouter.sol +++ b/src/LBRouter.sol @@ -2,973 +2,973 @@ pragma solidity 0.8.10; -import "openzeppelin/token/ERC20/IERC20.sol"; - -import "./LBErrors.sol"; -import "./libraries/BinHelper.sol"; -import "./libraries/Constants.sol"; -import "./libraries/FeeHelper.sol"; -import "./libraries/JoeLibrary.sol"; -import "./libraries/Math512Bits.sol"; -import "./libraries/SwapHelper.sol"; -import "./libraries/TokenHelper.sol"; -import "./interfaces/IJoePair.sol"; -import "./interfaces/ILBToken.sol"; -import "./interfaces/ILBRouter.sol"; - -/// @title Liquidity Book Router -/// @author Trader Joe -/// @notice Main contract to interact with to swap and manage liquidity on Joe V2 exchange. -contract LBRouter is ILBRouter { - using TokenHelper for IERC20; - using TokenHelper for IWAVAX; - using FeeHelper for FeeHelper.FeeParameters; - using Math512Bits for uint256; - using SwapHelper for ILBPair.Bin; - using JoeLibrary for uint256; - - ILBFactory public immutable override factory; - IJoeFactory public immutable override oldFactory; - IWAVAX public immutable override wavax; - - modifier onlyFactoryOwner() { - if (msg.sender != factory.owner()) revert LBRouter__NotFactoryOwner(); - _; - } - - modifier ensure(uint256 _deadline) { - if (block.timestamp > _deadline) revert LBRouter__DeadlineExceeded(_deadline, block.timestamp); - _; - } - - modifier verifyInputs(uint256[] memory _pairBinSteps, IERC20[] memory _tokenPath) { - if (_pairBinSteps.length == 0 || _pairBinSteps.length + 1 != _tokenPath.length) { - revert LBRouter__LengthsMismatch(); - } - _; - } - - /// @notice Constructor - /// @param _factory LBFactory address - /// @param _oldFactory Address of old factory (Joe V1) - /// @param _wavax Address of WAVAX - constructor(ILBFactory _factory, IJoeFactory _oldFactory, IWAVAX _wavax) { - factory = _factory; - oldFactory = _oldFactory; - wavax = _wavax; - } - - /// @dev Receive function that only accept AVAX from the WAVAX contract - receive() external payable { - if (msg.sender != address(wavax)) revert LBRouter__SenderIsNotWAVAX(); - } - - /// @notice Returns the approximate id corresponding to the inputted price. - /// Warning, the returned id may be inaccurate close to the start price of a bin - /// @param _LBPair The address of the LBPair - /// @param _price The price of y per x (multiplied by 1e36) - /// @return The id corresponding to this price - function getIdFromPrice(ILBPair _LBPair, uint256 _price) external view override returns (uint24) { - return BinHelper.getIdFromPrice(_price, _LBPair.feeParameters().binStep); - } - - /// @notice Returns the price corresponding to the inputted id - /// @param _LBPair The address of the LBPair - /// @param _id The id - /// @return The price corresponding to this id - function getPriceFromId(ILBPair _LBPair, uint24 _id) external view override returns (uint256) { - return BinHelper.getPriceFromId(_id, _LBPair.feeParameters().binStep); - } - - /// @notice Simulate a swap in - /// @param _LBPair The address of the LBPair - /// @param _amountOut The amount of token to receive - /// @param _swapForY Whether you swap X for Y (true), or Y for X (false) - /// @return amountIn The amount of token to send in order to receive _amountOut token - /// @return feesIn The amount of fees paid in token sent - function getSwapIn(ILBPair _LBPair, uint256 _amountOut, bool _swapForY) - public - view - override - returns (uint256 amountIn, uint256 feesIn) - { - (uint256 _pairReserveX, uint256 _pairReserveY, uint256 _activeId) = _LBPair.getReservesAndId(); - - if (_amountOut == 0 || (_swapForY ? _amountOut > _pairReserveY : _amountOut > _pairReserveX)) { - revert LBRouter__WrongAmounts(_amountOut, _swapForY ? _pairReserveY : _pairReserveX); - } // If this is wrong, then we're sure the amounts sent are wrong - - FeeHelper.FeeParameters memory _fp = _LBPair.feeParameters(); - _fp.updateVariableFeeParameters(_activeId); - - uint256 _amountOutOfBin; - uint256 _amountInWithFees; - uint256 _reserve; - // Performs the actual swap, bin per bin - // It uses the findFirstNonEmptyBinId function to make sure the bin we're currently looking at - // has liquidity in it. - while (true) { - { - (uint256 _reserveX, uint256 _reserveY) = _LBPair.getBin(uint24(_activeId)); - _reserve = _swapForY ? _reserveY : _reserveX; - } - uint256 _price = BinHelper.getPriceFromId(_activeId, _fp.binStep); - if (_reserve != 0) { - _amountOutOfBin = _amountOut >= _reserve ? _reserve : _amountOut; - uint256 _amountInToBin = _swapForY - ? _amountOutOfBin.shiftDivRoundUp(Constants.SCALE_OFFSET, _price) - : _price.mulShiftRoundUp(_amountOutOfBin, Constants.SCALE_OFFSET); - - // We update the fee, but we don't store the new volatility reference, volatility accumulated and indexRef to not penalize traders - _fp.updateVolatilityAccumulated(_activeId); - uint256 _fee = _fp.getFeeAmount(_amountInToBin); - _amountInWithFees = _amountInToBin + _fee; - - if (_amountInWithFees + _reserve > type(uint112).max) revert LBRouter__SwapOverflows(_activeId); - amountIn += _amountInWithFees; - feesIn += _fee; - _amountOut -= _amountOutOfBin; - } - - if (_amountOut != 0) { - _activeId = _LBPair.findFirstNonEmptyBinId(uint24(_activeId), _swapForY); - } else { - break; - } - } - if (_amountOut != 0) revert LBRouter__BrokenSwapSafetyCheck(); // Safety check, but should never be false as it would have reverted on transfer - } - - /// @notice Simulate a swap out - /// @param _LBPair The address of the LBPair - /// @param _amountIn The amount of token sent - /// @param _swapForY Whether you swap X for Y (true), or Y for X (false) - /// @return amountOut The amount of token received if _amountIn tokenX are sent - /// @return feesIn The amount of fees paid in token sent - function getSwapOut(ILBPair _LBPair, uint256 _amountIn, bool _swapForY) - external - view - override - returns (uint256 amountOut, uint256 feesIn) - { - (,, uint256 _activeId) = _LBPair.getReservesAndId(); - - FeeHelper.FeeParameters memory _fp = _LBPair.feeParameters(); - _fp.updateVariableFeeParameters(_activeId); - ILBPair.Bin memory _bin; - - // Performs the actual swap, bin per bin - // It uses the findFirstNonEmptyBinId function to make sure the bin we're currently looking at - // has liquidity in it. - while (true) { - { - (uint256 _reserveX, uint256 _reserveY) = _LBPair.getBin(uint24(_activeId)); - _bin = ILBPair.Bin(uint112(_reserveX), uint112(_reserveY), 0, 0); - } - if (_bin.reserveX != 0 || _bin.reserveY != 0) { - (uint256 _amountInToBin, uint256 _amountOutOfBin, FeeHelper.FeesDistribution memory _fees) = - _bin.getAmounts(_fp, _activeId, _swapForY, _amountIn); - - if (_amountInToBin > type(uint112).max) revert LBRouter__BinReserveOverflows(_activeId); - - _amountIn -= _amountInToBin + _fees.total; - feesIn += _fees.total; - amountOut += _amountOutOfBin; - } - - if (_amountIn != 0) { - _activeId = _LBPair.findFirstNonEmptyBinId(uint24(_activeId), _swapForY); - } else { - break; - } - } - if (_amountIn != 0) revert LBRouter__TooMuchTokensIn(_amountIn); - } - - /// @notice Create a liquidity bin LBPair for _tokenX and _tokenY using the factory - /// @param _tokenX The address of the first token - /// @param _tokenY The address of the second token - /// @param _activeId The active id of the pair - /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @return pair The address of the newly created LBPair - function createLBPair(IERC20 _tokenX, IERC20 _tokenY, uint24 _activeId, uint16 _binStep) - external - override - returns (ILBPair pair) - { - pair = factory.createLBPair(_tokenX, _tokenY, _activeId, _binStep); - } - - /// @notice Add liquidity while performing safety checks - /// @dev This function is compliant with fee on transfer tokens - /// @param _liquidityParameters The liquidity parameters - /// @return depositIds Bin ids where the liquidity was actually deposited - /// @return liquidityMinted Amounts of LBToken minted for each bin - function addLiquidity(LiquidityParameters calldata _liquidityParameters) - external - override - returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) - { - ILBPair _LBPair = _getLBPairInformation( - _liquidityParameters.tokenX, _liquidityParameters.tokenY, _liquidityParameters.binStep - ); - if (_liquidityParameters.tokenX != _LBPair.tokenX()) revert LBRouter__WrongTokenOrder(); - - _liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountX); - _liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountY); - - (depositIds, liquidityMinted) = _addLiquidity(_liquidityParameters, _LBPair); - } - - /// @notice Add liquidity with AVAX while performing safety checks - /// @dev This function is compliant with fee on transfer tokens - /// @param _liquidityParameters The liquidity parameters - /// @return depositIds Bin ids where the liquidity was actually deposited - /// @return liquidityMinted Amounts of LBToken minted for each bin - function addLiquidityAVAX(LiquidityParameters calldata _liquidityParameters) - external - payable - override - returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) - { - ILBPair _LBPair = _getLBPairInformation( - _liquidityParameters.tokenX, _liquidityParameters.tokenY, _liquidityParameters.binStep - ); - if (_liquidityParameters.tokenX != _LBPair.tokenX()) revert LBRouter__WrongTokenOrder(); - - if (_liquidityParameters.tokenX == wavax && _liquidityParameters.amountX == msg.value) { - _wavaxDepositAndTransfer(address(_LBPair), msg.value); - _liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountY); - } else if (_liquidityParameters.tokenY == wavax && _liquidityParameters.amountY == msg.value) { - _liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountX); - _wavaxDepositAndTransfer(address(_LBPair), msg.value); - } else { - revert LBRouter__WrongAvaxLiquidityParameters( - address(_liquidityParameters.tokenX), - address(_liquidityParameters.tokenY), - _liquidityParameters.amountX, - _liquidityParameters.amountY, - msg.value - ); - } - - (depositIds, liquidityMinted) = _addLiquidity(_liquidityParameters, _LBPair); - } - - /// @notice Remove liquidity while performing safety checks - /// @dev This function is compliant with fee on transfer tokens - /// @param _tokenX The address of token X - /// @param _tokenY The address of token Y - /// @param _binStep The bin step of the LBPair - /// @param _amountXMin The min amount to receive of token X - /// @param _amountYMin The min amount to receive of token Y - /// @param _ids The list of ids to burn - /// @param _amounts The list of amounts to burn of each id in `_ids` - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx - /// @return amountX Amount of token X returned - /// @return amountY Amount of token Y returned - function removeLiquidity( - IERC20 _tokenX, - IERC20 _tokenY, - uint16 _binStep, - uint256 _amountXMin, - uint256 _amountYMin, - uint256[] memory _ids, - uint256[] memory _amounts, - address _to, - uint256 _deadline - ) external override ensure(_deadline) returns (uint256 amountX, uint256 amountY) { - ILBPair _LBPair = _getLBPairInformation(_tokenX, _tokenY, _binStep); - bool _isWrongOrder = _tokenX != _LBPair.tokenX(); - - if (_isWrongOrder) (_amountXMin, _amountYMin) = (_amountYMin, _amountXMin); - - (amountX, amountY) = _removeLiquidity(_LBPair, _amountXMin, _amountYMin, _ids, _amounts, _to); - - if (_isWrongOrder) (amountX, amountY) = (amountY, amountX); - } - - /// @notice Remove AVAX liquidity while performing safety checks - /// @dev This function is **NOT** compliant with fee on transfer tokens. - /// This is wanted as it would make users pays the fee on transfer twice, - /// use the `removeLiquidity` function to remove liquidity with fee on transfer tokens. - /// @param _token The address of token - /// @param _binStep The bin step of the LBPair - /// @param _amountTokenMin The min amount to receive of token - /// @param _amountAVAXMin The min amount to receive of AVAX - /// @param _ids The list of ids to burn - /// @param _amounts The list of amounts to burn of each id in `_ids` - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx - /// @return amountToken Amount of token returned - /// @return amountAVAX Amount of AVAX returned - function removeLiquidityAVAX( - IERC20 _token, - uint16 _binStep, - uint256 _amountTokenMin, - uint256 _amountAVAXMin, - uint256[] memory _ids, - uint256[] memory _amounts, - address payable _to, - uint256 _deadline - ) external override ensure(_deadline) returns (uint256 amountToken, uint256 amountAVAX) { - ILBPair _LBPair = _getLBPairInformation(_token, IERC20(wavax), _binStep); - - bool _isAVAXTokenY = IERC20(wavax) == _LBPair.tokenY(); - { - if (!_isAVAXTokenY) { - (_amountTokenMin, _amountAVAXMin) = (_amountAVAXMin, _amountTokenMin); - } - - (uint256 _amountX, uint256 _amountY) = - _removeLiquidity(_LBPair, _amountTokenMin, _amountAVAXMin, _ids, _amounts, address(this)); - - (amountToken, amountAVAX) = _isAVAXTokenY ? (_amountX, _amountY) : (_amountY, _amountX); - } - - _token.safeTransfer(_to, amountToken); - - wavax.withdraw(amountAVAX); - _safeTransferAVAX(_to, amountAVAX); - } - - /// @notice Swaps exact tokens for tokens while performing safety checks - /// @param _amountIn The amount of token to send - /// @param _amountOutMin The min amount of token to receive - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx - /// @return amountOut Output amount of the swap - function swapExactTokensForTokens( - uint256 _amountIn, - uint256 _amountOutMin, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, - address _to, - uint256 _deadline - ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - - _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); - - amountOut = _swapExactTokensForTokens(_amountIn, _pairs, _pairBinSteps, _tokenPath, _to); - - if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); - } - - /// @notice Swaps exact tokens for AVAX while performing safety checks - /// @param _amountIn The amount of token to send - /// @param _amountOutMinAVAX The min amount of AVAX to receive - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx - /// @return amountOut Output amount of the swap - function swapExactTokensForAVAX( - uint256 _amountIn, - uint256 _amountOutMinAVAX, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, - address payable _to, - uint256 _deadline - ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { - if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { - revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); - } - - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - - _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); - - amountOut = _swapExactTokensForTokens(_amountIn, _pairs, _pairBinSteps, _tokenPath, address(this)); - - if (_amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMinAVAX, amountOut); - - wavax.withdraw(amountOut); - _safeTransferAVAX(_to, amountOut); - } - - /// @notice Swaps exact AVAX for tokens while performing safety checks - /// @param _amountOutMin The min amount of token to receive - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx - /// @return amountOut Output amount of the swap - function swapExactAVAXForTokens( - uint256 _amountOutMin, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, - address _to, - uint256 _deadline - ) external payable override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { - if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); - - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - - _wavaxDepositAndTransfer(_pairs[0], msg.value); - - amountOut = _swapExactTokensForTokens(msg.value, _pairs, _pairBinSteps, _tokenPath, _to); - - if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); - } - - /// @notice Swaps tokens for exact tokens while performing safety checks - /// @param _amountOut The amount of token to receive - /// @param _amountInMax The max amount of token to send - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx - /// @return amountsIn Input amounts for every step of the swap - function swapTokensForExactTokens( - uint256 _amountOut, - uint256 _amountInMax, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, - address _to, - uint256 _deadline - ) - external - override - ensure(_deadline) - verifyInputs(_pairBinSteps, _tokenPath) - returns (uint256[] memory amountsIn) - { - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountOut); - - if (amountsIn[0] > _amountInMax) revert LBRouter__MaxAmountInExceeded(_amountInMax, amountsIn[0]); - - _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], amountsIn[0]); - - uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, _to); - - if (_amountOutReal < _amountOut) revert LBRouter__InsufficientAmountOut(_amountOut, _amountOutReal); - } - - /// @notice Swaps tokens for exact AVAX while performing safety checks - /// @param _amountAVAXOut The amount of AVAX to receive - /// @param _amountInMax The max amount of token to send - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx - /// @return amountsIn Input amounts for every step of the swap - function swapTokensForExactAVAX( - uint256 _amountAVAXOut, - uint256 _amountInMax, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, - address payable _to, - uint256 _deadline - ) - external - override - ensure(_deadline) - verifyInputs(_pairBinSteps, _tokenPath) - returns (uint256[] memory amountsIn) - { - if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { - revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); - } - - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountAVAXOut); - - if (amountsIn[0] > _amountInMax) revert LBRouter__MaxAmountInExceeded(_amountInMax, amountsIn[0]); - - _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], amountsIn[0]); - - uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, address(this)); - - if (_amountOutReal < _amountAVAXOut) revert LBRouter__InsufficientAmountOut(_amountAVAXOut, _amountOutReal); - - wavax.withdraw(_amountOutReal); - _safeTransferAVAX(_to, _amountOutReal); - } - - /// @notice Swaps AVAX for exact tokens while performing safety checks - /// @dev Will refund any AVAX amount sent in excess to `msg.sender` - /// @param _amountOut The amount of tokens to receive - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx - /// @return amountsIn Input amounts for every step of the swap - function swapAVAXForExactTokens( - uint256 _amountOut, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, - address _to, - uint256 _deadline - ) - external - payable - override - ensure(_deadline) - verifyInputs(_pairBinSteps, _tokenPath) - returns (uint256[] memory amountsIn) - { - if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); - - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountOut); - - if (amountsIn[0] > msg.value) revert LBRouter__MaxAmountInExceeded(msg.value, amountsIn[0]); - - _wavaxDepositAndTransfer(_pairs[0], amountsIn[0]); - - uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, _to); - - if (_amountOutReal < _amountOut) revert LBRouter__InsufficientAmountOut(_amountOut, _amountOutReal); - - if (msg.value > amountsIn[0]) _safeTransferAVAX(msg.sender, msg.value - amountsIn[0]); - } - - /// @notice Swaps exact tokens for tokens while performing safety checks supporting for fee on transfer tokens - /// @param _amountIn The amount of token to send - /// @param _amountOutMin The min amount of token to receive - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx - /// @return amountOut Output amount of the swap - function swapExactTokensForTokensSupportingFeeOnTransferTokens( - uint256 _amountIn, - uint256 _amountOutMin, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, - address _to, - uint256 _deadline - ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - - IERC20 _targetToken = _tokenPath[_pairs.length]; - - uint256 _balanceBefore = _targetToken.balanceOf(_to); - - _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); - - _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, _to); - - amountOut = _targetToken.balanceOf(_to) - _balanceBefore; - if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); - } - - /// @notice Swaps exact tokens for AVAX while performing safety checks supporting for fee on transfer tokens - /// @param _amountIn The amount of token to send - /// @param _amountOutMinAVAX The min amount of AVAX to receive - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx - /// @return amountOut Output amount of the swap - function swapExactTokensForAVAXSupportingFeeOnTransferTokens( - uint256 _amountIn, - uint256 _amountOutMinAVAX, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, - address payable _to, - uint256 _deadline - ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { - if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { - revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); - } - - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - - uint256 _balanceBefore = wavax.balanceOf(address(this)); - - _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); - - _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, address(this)); - - amountOut = wavax.balanceOf(address(this)) - _balanceBefore; - if (_amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMinAVAX, amountOut); - - wavax.withdraw(amountOut); - _safeTransferAVAX(_to, amountOut); - } - - /// @notice Swaps exact AVAX for tokens while performing safety checks supporting for fee on transfer tokens - /// @param _amountOutMin The min amount of token to receive - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx - /// @return amountOut Output amount of the swap - function swapExactAVAXForTokensSupportingFeeOnTransferTokens( - uint256 _amountOutMin, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, - address _to, - uint256 _deadline - ) external payable override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { - if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); - - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - - IERC20 _targetToken = _tokenPath[_pairs.length]; - - uint256 _balanceBefore = _targetToken.balanceOf(_to); - - _wavaxDepositAndTransfer(_pairs[0], msg.value); - - _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, _to); - - amountOut = _targetToken.balanceOf(_to) - _balanceBefore; - if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); - } - - /// @notice Unstuck tokens that are sent to this contract by mistake - /// @dev Only callable by the factory owner - /// @param _token The address of the token - /// @param _to The address of the user to send back the tokens - /// @param _amount The amount to send - function sweep(IERC20 _token, address _to, uint256 _amount) external override onlyFactoryOwner { - if (address(_token) == address(0)) { - if (_amount == type(uint256).max) _amount = address(this).balance; - _safeTransferAVAX(_to, _amount); - } else { - if (_amount == type(uint256).max) _amount = _token.balanceOf(address(this)); - _token.safeTransfer(_to, _amount); - } - } - - /// @notice Unstuck LBTokens that are sent to this contract by mistake - /// @dev Only callable by the factory owner - /// @param _lbToken The address of the LBToken - /// @param _to The address of the user to send back the tokens - /// @param _ids The list of token ids - /// @param _amounts The list of amounts to send - function sweepLBToken(ILBToken _lbToken, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) - external - override - onlyFactoryOwner - { - _lbToken.safeBatchTransferFrom(address(this), _to, _ids, _amounts); - } - - /// @notice Helper function to add liquidity - /// @param _liq The liquidity parameter - /// @param _LBPair LBPair where liquidity is deposited - /// @return depositIds Bin ids where the liquidity was actually deposited - /// @return liquidityMinted Amounts of LBToken minted for each bin - function _addLiquidity(LiquidityParameters calldata _liq, ILBPair _LBPair) - private - ensure(_liq.deadline) - returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) - { - unchecked { - if (_liq.deltaIds.length != _liq.distributionX.length && _liq.deltaIds.length != _liq.distributionY.length) - { - revert LBRouter__LengthsMismatch(); - } - - if (_liq.activeIdDesired > type(uint24).max || _liq.idSlippage > type(uint24).max) { - revert LBRouter__IdDesiredOverflows(_liq.activeIdDesired, _liq.idSlippage); - } - - (,, uint256 _activeId) = _LBPair.getReservesAndId(); - if ( - _liq.activeIdDesired + _liq.idSlippage < _activeId || _activeId + _liq.idSlippage < _liq.activeIdDesired - ) revert LBRouter__IdSlippageCaught(_liq.activeIdDesired, _liq.idSlippage, _activeId); - - depositIds = new uint256[](_liq.deltaIds.length); - for (uint256 i; i < depositIds.length; ++i) { - int256 _id = int256(_activeId) + _liq.deltaIds[i]; - if (_id < 0 || uint256(_id) > type(uint24).max) revert LBRouter__IdOverflows(_id); - depositIds[i] = uint256(_id); - } - - uint256 _amountXAdded; - uint256 _amountYAdded; - - (_amountXAdded, _amountYAdded, liquidityMinted) = - _LBPair.mint(depositIds, _liq.distributionX, _liq.distributionY, _liq.to); - - if (_amountXAdded < _liq.amountXMin || _amountYAdded < _liq.amountYMin) { - revert LBRouter__AmountSlippageCaught(_liq.amountXMin, _amountXAdded, _liq.amountYMin, _amountYAdded); - } - } - } - - /// @notice Helper function to return the amounts in - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _pairs The list of pairs - /// @param _tokenPath The swap path - /// @param _amountOut The amount out - /// @return amountsIn The list of amounts in - function _getAmountsIn( - uint256[] memory _pairBinSteps, - address[] memory _pairs, - IERC20[] memory _tokenPath, - uint256 _amountOut - ) private view returns (uint256[] memory amountsIn) { - amountsIn = new uint256[](_tokenPath.length); - // Avoid doing -1, as `_pairs.length == _pairBinSteps.length-1` - amountsIn[_pairs.length] = _amountOut; - - for (uint256 i = _pairs.length; i != 0; i--) { - IERC20 _token = _tokenPath[i - 1]; - uint256 _binStep = _pairBinSteps[i - 1]; - - address _pair = _pairs[i - 1]; - - if (_binStep == 0) { - (uint256 _reserveIn, uint256 _reserveOut,) = IJoePair(_pair).getReserves(); - if (_token > _tokenPath[i]) { - (_reserveIn, _reserveOut) = (_reserveOut, _reserveIn); - } - - uint256 amountOut_ = amountsIn[i]; - amountsIn[i - 1] = amountOut_.getAmountIn(_reserveIn, _reserveOut); - } else { - (amountsIn[i - 1],) = getSwapIn(ILBPair(_pair), amountsIn[i], ILBPair(_pair).tokenX() == _token); - } - } - } - - /// @notice Helper function to remove liquidity - /// @param _LBPair The address of the LBPair - /// @param _amountXMin The min amount to receive of token X - /// @param _amountYMin The min amount to receive of token Y - /// @param _ids The list of ids to burn - /// @param _amounts The list of amounts to burn of each id in `_ids` - /// @param _to The address of the recipient - /// @return amountX The amount of token X sent by the pair - /// @return amountY The amount of token Y sent by the pair - function _removeLiquidity( - ILBPair _LBPair, - uint256 _amountXMin, - uint256 _amountYMin, - uint256[] memory _ids, - uint256[] memory _amounts, - address _to - ) private returns (uint256 amountX, uint256 amountY) { - ILBToken(address(_LBPair)).safeBatchTransferFrom(msg.sender, address(_LBPair), _ids, _amounts); - (amountX, amountY) = _LBPair.burn(_ids, _amounts, _to); - if (amountX < _amountXMin || amountY < _amountYMin) { - revert LBRouter__AmountSlippageCaught(_amountXMin, amountX, _amountYMin, amountY); - } - } - - /// @notice Helper function to swap exact tokens for tokens - /// @param _amountIn The amount of token sent - /// @param _pairs The list of pairs - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` - /// @param _to The address of the recipient - /// @return amountOut The amount of token sent to `_to` - function _swapExactTokensForTokens( - uint256 _amountIn, - address[] memory _pairs, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, - address _to - ) private returns (uint256 amountOut) { - IERC20 _token; - uint256 _binStep; - address _recipient; - address _pair; - - IERC20 _tokenNext = _tokenPath[0]; - amountOut = _amountIn; - - unchecked { - for (uint256 i; i < _pairs.length; ++i) { - _pair = _pairs[i]; - _binStep = _pairBinSteps[i]; - - _token = _tokenNext; - _tokenNext = _tokenPath[i + 1]; - - _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; - - if (_binStep == 0) { - (uint256 _reserve0, uint256 _reserve1,) = IJoePair(_pair).getReserves(); - - if (_token < _tokenNext) { - amountOut = amountOut.getAmountOut(_reserve0, _reserve1); - IJoePair(_pair).swap(0, amountOut, _recipient, ""); - } else { - amountOut = amountOut.getAmountOut(_reserve1, _reserve0); - IJoePair(_pair).swap(amountOut, 0, _recipient, ""); - } - } else { - bool _swapForY = _tokenNext == ILBPair(_pair).tokenY(); - - (uint256 _amountXOut, uint256 _amountYOut) = ILBPair(_pair).swap(_swapForY, _recipient); - - if (_swapForY) amountOut = _amountYOut; - else amountOut = _amountXOut; - } - } - } - } - - /// @notice Helper function to swap tokens for exact tokens - /// @param _pairs The array of pairs - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` - /// @param _amountsIn The list of amounts in - /// @param _to The address of the recipient - /// @return amountOut The amount of token sent to `_to` - function _swapTokensForExactTokens( - address[] memory _pairs, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, - uint256[] memory _amountsIn, - address _to - ) private returns (uint256 amountOut) { - IERC20 _token; - uint256 _binStep; - address _recipient; - address _pair; - - IERC20 _tokenNext = _tokenPath[0]; - - unchecked { - for (uint256 i; i < _pairs.length; ++i) { - _pair = _pairs[i]; - _binStep = _pairBinSteps[i]; - - _token = _tokenNext; - _tokenNext = _tokenPath[i + 1]; - - _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; - - if (_binStep == 0) { - amountOut = _amountsIn[i + 1]; - if (_token < _tokenNext) { - IJoePair(_pair).swap(0, amountOut, _recipient, ""); - } else { - IJoePair(_pair).swap(amountOut, 0, _recipient, ""); - } - } else { - bool _swapForY = _tokenNext == ILBPair(_pair).tokenY(); - - (uint256 _amountXOut, uint256 _amountYOut) = ILBPair(_pair).swap(_swapForY, _recipient); - - if (_swapForY) amountOut = _amountYOut; - else amountOut = _amountXOut; - } - } - } - } - - /// @notice Helper function to swap exact tokens supporting for fee on transfer tokens - /// @param _pairs The list of pairs - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` - /// @param _to The address of the recipient - function _swapSupportingFeeOnTransferTokens( - address[] memory _pairs, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, - address _to - ) private { - IERC20 _token; - uint256 _binStep; - address _recipient; - address _pair; - - IERC20 _tokenNext = _tokenPath[0]; - - unchecked { - for (uint256 i; i < _pairs.length; ++i) { - _pair = _pairs[i]; - _binStep = _pairBinSteps[i]; - - _token = _tokenNext; - _tokenNext = _tokenPath[i + 1]; - - _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; - - if (_binStep == 0) { - (uint256 _reserve0, uint256 _reserve1,) = IJoePair(_pair).getReserves(); - if (_token < _tokenNext) { - uint256 _amountIn = _token.balanceOf(_pair) - _reserve0; - uint256 _amountOut = _amountIn.getAmountOut(_reserve0, _reserve1); - - IJoePair(_pair).swap(0, _amountOut, _recipient, ""); - } else { - uint256 _amountIn = _token.balanceOf(_pair) - _reserve1; - uint256 _amountOut = _amountIn.getAmountOut(_reserve1, _reserve0); - - IJoePair(_pair).swap(_amountOut, 0, _recipient, ""); - } - } else { - ILBPair(_pair).swap(_tokenNext == ILBPair(_pair).tokenY(), _recipient); - } - } - } - } - - /// @notice Helper function to return the address of the LBPair - /// @dev Revert if the pair is not created yet - /// @param _tokenX The address of the tokenX - /// @param _tokenY The address of the tokenY - /// @param _binStep The bin step of the LBPair - /// @return The address of the LBPair - function _getLBPairInformation(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep) private view returns (ILBPair) { - ILBPair _LBPair = factory.getLBPairInformation(_tokenX, _tokenY, _binStep).LBPair; - if (address(_LBPair) == address(0)) { - revert LBRouter__PairNotCreated(address(_tokenX), address(_tokenY), _binStep); - } - return _LBPair; - } - - /// @notice Helper function to return the address of the pair (v1 or v2, according to `_binStep`) - /// @dev Revert if the pair is not created yet - /// @param _binStep The bin step of the LBPair, 0 means using V1 pair, any other value will use V2 - /// @param _tokenX The address of the tokenX - /// @param _tokenY The address of the tokenY - /// @return _pair The address of the pair of binStep `_binStep` - function _getPair(uint256 _binStep, IERC20 _tokenX, IERC20 _tokenY) private view returns (address _pair) { - if (_binStep == 0) { - _pair = oldFactory.getPair(address(_tokenX), address(_tokenY)); - if (_pair == address(0)) revert LBRouter__PairNotCreated(address(_tokenX), address(_tokenY), _binStep); - } else { - _pair = address(_getLBPairInformation(_tokenX, _tokenY, _binStep)); - } - } - - function _getPairs(uint256[] memory _pairBinSteps, IERC20[] memory _tokenPath) - private - view - returns (address[] memory pairs) - { - pairs = new address[](_pairBinSteps.length); - - IERC20 _token; - IERC20 _tokenNext = _tokenPath[0]; - unchecked { - for (uint256 i; i < pairs.length; ++i) { - _token = _tokenNext; - _tokenNext = _tokenPath[i + 1]; - - pairs[i] = _getPair(_pairBinSteps[i], _token, _tokenNext); - } - } - } - - /// @notice Helper function to transfer AVAX - /// @param _to The address of the recipient - /// @param _amount The AVAX amount to send - function _safeTransferAVAX(address _to, uint256 _amount) private { - (bool success,) = _to.call{value: _amount}(""); - if (!success) revert LBRouter__FailedToSendAVAX(_to, _amount); - } - - /// @notice Helper function to deposit and transfer wavax - /// @param _to The address of the recipient - /// @param _amount The AVAX amount to wrap - function _wavaxDepositAndTransfer(address _to, uint256 _amount) private { - wavax.deposit{value: _amount}(); - wavax.safeTransfer(_to, _amount); - } -} +// import "openzeppelin/token/ERC20/IERC20.sol"; + +// import "./LBErrors.sol"; +// import "./libraries/BinHelper.sol"; +// import "./libraries/Constants.sol"; +// import "./libraries/FeeHelper.sol"; +// import "./libraries/JoeLibrary.sol"; +// import "./libraries/Math512Bits.sol"; +// import "./libraries/SwapHelper.sol"; +// import "./libraries/TokenHelper.sol"; +// import "./interfaces/IJoePair.sol"; +// import "./interfaces/ILBToken.sol"; +// import "./interfaces/ILBRouter.sol"; + +// /// @title Liquidity Book Router +// /// @author Trader Joe +// /// @notice Main contract to interact with to swap and manage liquidity on Joe V2 exchange. +// contract LBRouter is ILBRouter { +// using TokenHelper for IERC20; +// using TokenHelper for IWAVAX; +// using FeeHelper for FeeHelper.FeeParameters; +// using Math512Bits for uint256; +// using SwapHelper for ILBPair.Bin; +// using JoeLibrary for uint256; + +// ILBFactory public immutable override factory; +// IJoeFactory public immutable override oldFactory; +// IWAVAX public immutable override wavax; + +// modifier onlyFactoryOwner() { +// if (msg.sender != factory.owner()) revert LBRouter__NotFactoryOwner(); +// _; +// } + +// modifier ensure(uint256 _deadline) { +// if (block.timestamp > _deadline) revert LBRouter__DeadlineExceeded(_deadline, block.timestamp); +// _; +// } + +// modifier verifyInputs(uint256[] memory _pairBinSteps, IERC20[] memory _tokenPath) { +// if (_pairBinSteps.length == 0 || _pairBinSteps.length + 1 != _tokenPath.length) { +// revert LBRouter__LengthsMismatch(); +// } +// _; +// } + +// /// @notice Constructor +// /// @param _factory LBFactory address +// /// @param _oldFactory Address of old factory (Joe V1) +// /// @param _wavax Address of WAVAX +// constructor(ILBFactory _factory, IJoeFactory _oldFactory, IWAVAX _wavax) { +// factory = _factory; +// oldFactory = _oldFactory; +// wavax = _wavax; +// } + +// /// @dev Receive function that only accept AVAX from the WAVAX contract +// receive() external payable { +// if (msg.sender != address(wavax)) revert LBRouter__SenderIsNotWAVAX(); +// } + +// /// @notice Returns the approximate id corresponding to the inputted price. +// /// Warning, the returned id may be inaccurate close to the start price of a bin +// /// @param _LBPair The address of the LBPair +// /// @param _price The price of y per x (multiplied by 1e36) +// /// @return The id corresponding to this price +// function getIdFromPrice(ILBPair _LBPair, uint256 _price) external view override returns (uint24) { +// return BinHelper.getIdFromPrice(_price, _LBPair.feeParameters().binStep); +// } + +// /// @notice Returns the price corresponding to the inputted id +// /// @param _LBPair The address of the LBPair +// /// @param _id The id +// /// @return The price corresponding to this id +// function getPriceFromId(ILBPair _LBPair, uint24 _id) external view override returns (uint256) { +// return BinHelper.getPriceFromId(_id, _LBPair.feeParameters().binStep); +// } + +// /// @notice Simulate a swap in +// /// @param _LBPair The address of the LBPair +// /// @param _amountOut The amount of token to receive +// /// @param _swapForY Whether you swap X for Y (true), or Y for X (false) +// /// @return amountIn The amount of token to send in order to receive _amountOut token +// /// @return feesIn The amount of fees paid in token sent +// function getSwapIn(ILBPair _LBPair, uint256 _amountOut, bool _swapForY) +// public +// view +// override +// returns (uint256 amountIn, uint256 feesIn) +// { +// (uint256 _pairReserveX, uint256 _pairReserveY, uint256 _activeId) = _LBPair.getReservesAndId(); + +// if (_amountOut == 0 || (_swapForY ? _amountOut > _pairReserveY : _amountOut > _pairReserveX)) { +// revert LBRouter__WrongAmounts(_amountOut, _swapForY ? _pairReserveY : _pairReserveX); +// } // If this is wrong, then we're sure the amounts sent are wrong + +// FeeHelper.FeeParameters memory _fp = _LBPair.feeParameters(); +// _fp.updateVariableFeeParameters(_activeId); + +// uint256 _amountOutOfBin; +// uint256 _amountInWithFees; +// uint256 _reserve; +// // Performs the actual swap, bin per bin +// // It uses the findFirstNonEmptyBinId function to make sure the bin we're currently looking at +// // has liquidity in it. +// while (true) { +// { +// (uint256 _reserveX, uint256 _reserveY) = _LBPair.getBin(uint24(_activeId)); +// _reserve = _swapForY ? _reserveY : _reserveX; +// } +// uint256 _price = BinHelper.getPriceFromId(_activeId, _fp.binStep); +// if (_reserve != 0) { +// _amountOutOfBin = _amountOut >= _reserve ? _reserve : _amountOut; +// uint256 _amountInToBin = _swapForY +// ? _amountOutOfBin.shiftDivRoundUp(Constants.SCALE_OFFSET, _price) +// : _price.mulShiftRoundUp(_amountOutOfBin, Constants.SCALE_OFFSET); + +// // We update the fee, but we don't store the new volatility reference, volatility accumulated and indexRef to not penalize traders +// _fp.updateVolatilityAccumulated(_activeId); +// uint256 _fee = _fp.getFeeAmount(_amountInToBin); +// _amountInWithFees = _amountInToBin + _fee; + +// if (_amountInWithFees + _reserve > type(uint112).max) revert LBRouter__SwapOverflows(_activeId); +// amountIn += _amountInWithFees; +// feesIn += _fee; +// _amountOut -= _amountOutOfBin; +// } + +// if (_amountOut != 0) { +// _activeId = _LBPair.findFirstNonEmptyBinId(uint24(_activeId), _swapForY); +// } else { +// break; +// } +// } +// if (_amountOut != 0) revert LBRouter__BrokenSwapSafetyCheck(); // Safety check, but should never be false as it would have reverted on transfer +// } + +// /// @notice Simulate a swap out +// /// @param _LBPair The address of the LBPair +// /// @param _amountIn The amount of token sent +// /// @param _swapForY Whether you swap X for Y (true), or Y for X (false) +// /// @return amountOut The amount of token received if _amountIn tokenX are sent +// /// @return feesIn The amount of fees paid in token sent +// function getSwapOut(ILBPair _LBPair, uint256 _amountIn, bool _swapForY) +// external +// view +// override +// returns (uint256 amountOut, uint256 feesIn) +// { +// (,, uint256 _activeId) = _LBPair.getReservesAndId(); + +// FeeHelper.FeeParameters memory _fp = _LBPair.feeParameters(); +// _fp.updateVariableFeeParameters(_activeId); +// ILBPair.Bin memory _bin; + +// // Performs the actual swap, bin per bin +// // It uses the findFirstNonEmptyBinId function to make sure the bin we're currently looking at +// // has liquidity in it. +// while (true) { +// { +// (uint256 _reserveX, uint256 _reserveY) = _LBPair.getBin(uint24(_activeId)); +// _bin = ILBPair.Bin(uint112(_reserveX), uint112(_reserveY), 0, 0); +// } +// if (_bin.reserveX != 0 || _bin.reserveY != 0) { +// (uint256 _amountInToBin, uint256 _amountOutOfBin, FeeHelper.FeesDistribution memory _fees) = +// _bin.getAmounts(_fp, _activeId, _swapForY, _amountIn); + +// if (_amountInToBin > type(uint112).max) revert LBRouter__BinReserveOverflows(_activeId); + +// _amountIn -= _amountInToBin + _fees.total; +// feesIn += _fees.total; +// amountOut += _amountOutOfBin; +// } + +// if (_amountIn != 0) { +// _activeId = _LBPair.findFirstNonEmptyBinId(uint24(_activeId), _swapForY); +// } else { +// break; +// } +// } +// if (_amountIn != 0) revert LBRouter__TooMuchTokensIn(_amountIn); +// } + +// /// @notice Create a liquidity bin LBPair for _tokenX and _tokenY using the factory +// /// @param _tokenX The address of the first token +// /// @param _tokenY The address of the second token +// /// @param _activeId The active id of the pair +// /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) +// /// @return pair The address of the newly created LBPair +// function createLBPair(IERC20 _tokenX, IERC20 _tokenY, uint24 _activeId, uint16 _binStep) +// external +// override +// returns (ILBPair pair) +// { +// pair = factory.createLBPair(_tokenX, _tokenY, _activeId, _binStep); +// } + +// /// @notice Add liquidity while performing safety checks +// /// @dev This function is compliant with fee on transfer tokens +// /// @param _liquidityParameters The liquidity parameters +// /// @return depositIds Bin ids where the liquidity was actually deposited +// /// @return liquidityMinted Amounts of LBToken minted for each bin +// function addLiquidity(LiquidityParameters calldata _liquidityParameters) +// external +// override +// returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) +// { +// ILBPair _LBPair = _getLBPairInformation( +// _liquidityParameters.tokenX, _liquidityParameters.tokenY, _liquidityParameters.binStep +// ); +// if (_liquidityParameters.tokenX != _LBPair.tokenX()) revert LBRouter__WrongTokenOrder(); + +// _liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountX); +// _liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountY); + +// (depositIds, liquidityMinted) = _addLiquidity(_liquidityParameters, _LBPair); +// } + +// /// @notice Add liquidity with AVAX while performing safety checks +// /// @dev This function is compliant with fee on transfer tokens +// /// @param _liquidityParameters The liquidity parameters +// /// @return depositIds Bin ids where the liquidity was actually deposited +// /// @return liquidityMinted Amounts of LBToken minted for each bin +// function addLiquidityAVAX(LiquidityParameters calldata _liquidityParameters) +// external +// payable +// override +// returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) +// { +// ILBPair _LBPair = _getLBPairInformation( +// _liquidityParameters.tokenX, _liquidityParameters.tokenY, _liquidityParameters.binStep +// ); +// if (_liquidityParameters.tokenX != _LBPair.tokenX()) revert LBRouter__WrongTokenOrder(); + +// if (_liquidityParameters.tokenX == wavax && _liquidityParameters.amountX == msg.value) { +// _wavaxDepositAndTransfer(address(_LBPair), msg.value); +// _liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountY); +// } else if (_liquidityParameters.tokenY == wavax && _liquidityParameters.amountY == msg.value) { +// _liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountX); +// _wavaxDepositAndTransfer(address(_LBPair), msg.value); +// } else { +// revert LBRouter__WrongAvaxLiquidityParameters( +// address(_liquidityParameters.tokenX), +// address(_liquidityParameters.tokenY), +// _liquidityParameters.amountX, +// _liquidityParameters.amountY, +// msg.value +// ); +// } + +// (depositIds, liquidityMinted) = _addLiquidity(_liquidityParameters, _LBPair); +// } + +// /// @notice Remove liquidity while performing safety checks +// /// @dev This function is compliant with fee on transfer tokens +// /// @param _tokenX The address of token X +// /// @param _tokenY The address of token Y +// /// @param _binStep The bin step of the LBPair +// /// @param _amountXMin The min amount to receive of token X +// /// @param _amountYMin The min amount to receive of token Y +// /// @param _ids The list of ids to burn +// /// @param _amounts The list of amounts to burn of each id in `_ids` +// /// @param _to The address of the recipient +// /// @param _deadline The deadline of the tx +// /// @return amountX Amount of token X returned +// /// @return amountY Amount of token Y returned +// function removeLiquidity( +// IERC20 _tokenX, +// IERC20 _tokenY, +// uint16 _binStep, +// uint256 _amountXMin, +// uint256 _amountYMin, +// uint256[] memory _ids, +// uint256[] memory _amounts, +// address _to, +// uint256 _deadline +// ) external override ensure(_deadline) returns (uint256 amountX, uint256 amountY) { +// ILBPair _LBPair = _getLBPairInformation(_tokenX, _tokenY, _binStep); +// bool _isWrongOrder = _tokenX != _LBPair.tokenX(); + +// if (_isWrongOrder) (_amountXMin, _amountYMin) = (_amountYMin, _amountXMin); + +// (amountX, amountY) = _removeLiquidity(_LBPair, _amountXMin, _amountYMin, _ids, _amounts, _to); + +// if (_isWrongOrder) (amountX, amountY) = (amountY, amountX); +// } + +// /// @notice Remove AVAX liquidity while performing safety checks +// /// @dev This function is **NOT** compliant with fee on transfer tokens. +// /// This is wanted as it would make users pays the fee on transfer twice, +// /// use the `removeLiquidity` function to remove liquidity with fee on transfer tokens. +// /// @param _token The address of token +// /// @param _binStep The bin step of the LBPair +// /// @param _amountTokenMin The min amount to receive of token +// /// @param _amountAVAXMin The min amount to receive of AVAX +// /// @param _ids The list of ids to burn +// /// @param _amounts The list of amounts to burn of each id in `_ids` +// /// @param _to The address of the recipient +// /// @param _deadline The deadline of the tx +// /// @return amountToken Amount of token returned +// /// @return amountAVAX Amount of AVAX returned +// function removeLiquidityAVAX( +// IERC20 _token, +// uint16 _binStep, +// uint256 _amountTokenMin, +// uint256 _amountAVAXMin, +// uint256[] memory _ids, +// uint256[] memory _amounts, +// address payable _to, +// uint256 _deadline +// ) external override ensure(_deadline) returns (uint256 amountToken, uint256 amountAVAX) { +// ILBPair _LBPair = _getLBPairInformation(_token, IERC20(wavax), _binStep); + +// bool _isAVAXTokenY = IERC20(wavax) == _LBPair.tokenY(); +// { +// if (!_isAVAXTokenY) { +// (_amountTokenMin, _amountAVAXMin) = (_amountAVAXMin, _amountTokenMin); +// } + +// (uint256 _amountX, uint256 _amountY) = +// _removeLiquidity(_LBPair, _amountTokenMin, _amountAVAXMin, _ids, _amounts, address(this)); + +// (amountToken, amountAVAX) = _isAVAXTokenY ? (_amountX, _amountY) : (_amountY, _amountX); +// } + +// _token.safeTransfer(_to, amountToken); + +// wavax.withdraw(amountAVAX); +// _safeTransferAVAX(_to, amountAVAX); +// } + +// /// @notice Swaps exact tokens for tokens while performing safety checks +// /// @param _amountIn The amount of token to send +// /// @param _amountOutMin The min amount of token to receive +// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) +// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` +// /// @param _to The address of the recipient +// /// @param _deadline The deadline of the tx +// /// @return amountOut Output amount of the swap +// function swapExactTokensForTokens( +// uint256 _amountIn, +// uint256 _amountOutMin, +// uint256[] memory _pairBinSteps, +// IERC20[] memory _tokenPath, +// address _to, +// uint256 _deadline +// ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { +// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + +// _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + +// amountOut = _swapExactTokensForTokens(_amountIn, _pairs, _pairBinSteps, _tokenPath, _to); + +// if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); +// } + +// /// @notice Swaps exact tokens for AVAX while performing safety checks +// /// @param _amountIn The amount of token to send +// /// @param _amountOutMinAVAX The min amount of AVAX to receive +// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) +// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` +// /// @param _to The address of the recipient +// /// @param _deadline The deadline of the tx +// /// @return amountOut Output amount of the swap +// function swapExactTokensForAVAX( +// uint256 _amountIn, +// uint256 _amountOutMinAVAX, +// uint256[] memory _pairBinSteps, +// IERC20[] memory _tokenPath, +// address payable _to, +// uint256 _deadline +// ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { +// if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { +// revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); +// } + +// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + +// _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + +// amountOut = _swapExactTokensForTokens(_amountIn, _pairs, _pairBinSteps, _tokenPath, address(this)); + +// if (_amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMinAVAX, amountOut); + +// wavax.withdraw(amountOut); +// _safeTransferAVAX(_to, amountOut); +// } + +// /// @notice Swaps exact AVAX for tokens while performing safety checks +// /// @param _amountOutMin The min amount of token to receive +// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) +// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` +// /// @param _to The address of the recipient +// /// @param _deadline The deadline of the tx +// /// @return amountOut Output amount of the swap +// function swapExactAVAXForTokens( +// uint256 _amountOutMin, +// uint256[] memory _pairBinSteps, +// IERC20[] memory _tokenPath, +// address _to, +// uint256 _deadline +// ) external payable override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { +// if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); + +// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + +// _wavaxDepositAndTransfer(_pairs[0], msg.value); + +// amountOut = _swapExactTokensForTokens(msg.value, _pairs, _pairBinSteps, _tokenPath, _to); + +// if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); +// } + +// /// @notice Swaps tokens for exact tokens while performing safety checks +// /// @param _amountOut The amount of token to receive +// /// @param _amountInMax The max amount of token to send +// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) +// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` +// /// @param _to The address of the recipient +// /// @param _deadline The deadline of the tx +// /// @return amountsIn Input amounts for every step of the swap +// function swapTokensForExactTokens( +// uint256 _amountOut, +// uint256 _amountInMax, +// uint256[] memory _pairBinSteps, +// IERC20[] memory _tokenPath, +// address _to, +// uint256 _deadline +// ) +// external +// override +// ensure(_deadline) +// verifyInputs(_pairBinSteps, _tokenPath) +// returns (uint256[] memory amountsIn) +// { +// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); +// amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountOut); + +// if (amountsIn[0] > _amountInMax) revert LBRouter__MaxAmountInExceeded(_amountInMax, amountsIn[0]); + +// _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], amountsIn[0]); + +// uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, _to); + +// if (_amountOutReal < _amountOut) revert LBRouter__InsufficientAmountOut(_amountOut, _amountOutReal); +// } + +// /// @notice Swaps tokens for exact AVAX while performing safety checks +// /// @param _amountAVAXOut The amount of AVAX to receive +// /// @param _amountInMax The max amount of token to send +// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) +// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` +// /// @param _to The address of the recipient +// /// @param _deadline The deadline of the tx +// /// @return amountsIn Input amounts for every step of the swap +// function swapTokensForExactAVAX( +// uint256 _amountAVAXOut, +// uint256 _amountInMax, +// uint256[] memory _pairBinSteps, +// IERC20[] memory _tokenPath, +// address payable _to, +// uint256 _deadline +// ) +// external +// override +// ensure(_deadline) +// verifyInputs(_pairBinSteps, _tokenPath) +// returns (uint256[] memory amountsIn) +// { +// if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { +// revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); +// } + +// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); +// amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountAVAXOut); + +// if (amountsIn[0] > _amountInMax) revert LBRouter__MaxAmountInExceeded(_amountInMax, amountsIn[0]); + +// _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], amountsIn[0]); + +// uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, address(this)); + +// if (_amountOutReal < _amountAVAXOut) revert LBRouter__InsufficientAmountOut(_amountAVAXOut, _amountOutReal); + +// wavax.withdraw(_amountOutReal); +// _safeTransferAVAX(_to, _amountOutReal); +// } + +// /// @notice Swaps AVAX for exact tokens while performing safety checks +// /// @dev Will refund any AVAX amount sent in excess to `msg.sender` +// /// @param _amountOut The amount of tokens to receive +// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) +// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` +// /// @param _to The address of the recipient +// /// @param _deadline The deadline of the tx +// /// @return amountsIn Input amounts for every step of the swap +// function swapAVAXForExactTokens( +// uint256 _amountOut, +// uint256[] memory _pairBinSteps, +// IERC20[] memory _tokenPath, +// address _to, +// uint256 _deadline +// ) +// external +// payable +// override +// ensure(_deadline) +// verifyInputs(_pairBinSteps, _tokenPath) +// returns (uint256[] memory amountsIn) +// { +// if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); + +// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); +// amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountOut); + +// if (amountsIn[0] > msg.value) revert LBRouter__MaxAmountInExceeded(msg.value, amountsIn[0]); + +// _wavaxDepositAndTransfer(_pairs[0], amountsIn[0]); + +// uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, _to); + +// if (_amountOutReal < _amountOut) revert LBRouter__InsufficientAmountOut(_amountOut, _amountOutReal); + +// if (msg.value > amountsIn[0]) _safeTransferAVAX(msg.sender, msg.value - amountsIn[0]); +// } + +// /// @notice Swaps exact tokens for tokens while performing safety checks supporting for fee on transfer tokens +// /// @param _amountIn The amount of token to send +// /// @param _amountOutMin The min amount of token to receive +// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) +// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` +// /// @param _to The address of the recipient +// /// @param _deadline The deadline of the tx +// /// @return amountOut Output amount of the swap +// function swapExactTokensForTokensSupportingFeeOnTransferTokens( +// uint256 _amountIn, +// uint256 _amountOutMin, +// uint256[] memory _pairBinSteps, +// IERC20[] memory _tokenPath, +// address _to, +// uint256 _deadline +// ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { +// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + +// IERC20 _targetToken = _tokenPath[_pairs.length]; + +// uint256 _balanceBefore = _targetToken.balanceOf(_to); + +// _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + +// _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, _to); + +// amountOut = _targetToken.balanceOf(_to) - _balanceBefore; +// if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); +// } + +// /// @notice Swaps exact tokens for AVAX while performing safety checks supporting for fee on transfer tokens +// /// @param _amountIn The amount of token to send +// /// @param _amountOutMinAVAX The min amount of AVAX to receive +// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) +// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` +// /// @param _to The address of the recipient +// /// @param _deadline The deadline of the tx +// /// @return amountOut Output amount of the swap +// function swapExactTokensForAVAXSupportingFeeOnTransferTokens( +// uint256 _amountIn, +// uint256 _amountOutMinAVAX, +// uint256[] memory _pairBinSteps, +// IERC20[] memory _tokenPath, +// address payable _to, +// uint256 _deadline +// ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { +// if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { +// revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); +// } + +// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + +// uint256 _balanceBefore = wavax.balanceOf(address(this)); + +// _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + +// _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, address(this)); + +// amountOut = wavax.balanceOf(address(this)) - _balanceBefore; +// if (_amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMinAVAX, amountOut); + +// wavax.withdraw(amountOut); +// _safeTransferAVAX(_to, amountOut); +// } + +// /// @notice Swaps exact AVAX for tokens while performing safety checks supporting for fee on transfer tokens +// /// @param _amountOutMin The min amount of token to receive +// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) +// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` +// /// @param _to The address of the recipient +// /// @param _deadline The deadline of the tx +// /// @return amountOut Output amount of the swap +// function swapExactAVAXForTokensSupportingFeeOnTransferTokens( +// uint256 _amountOutMin, +// uint256[] memory _pairBinSteps, +// IERC20[] memory _tokenPath, +// address _to, +// uint256 _deadline +// ) external payable override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { +// if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); + +// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + +// IERC20 _targetToken = _tokenPath[_pairs.length]; + +// uint256 _balanceBefore = _targetToken.balanceOf(_to); + +// _wavaxDepositAndTransfer(_pairs[0], msg.value); + +// _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, _to); + +// amountOut = _targetToken.balanceOf(_to) - _balanceBefore; +// if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); +// } + +// /// @notice Unstuck tokens that are sent to this contract by mistake +// /// @dev Only callable by the factory owner +// /// @param _token The address of the token +// /// @param _to The address of the user to send back the tokens +// /// @param _amount The amount to send +// function sweep(IERC20 _token, address _to, uint256 _amount) external override onlyFactoryOwner { +// if (address(_token) == address(0)) { +// if (_amount == type(uint256).max) _amount = address(this).balance; +// _safeTransferAVAX(_to, _amount); +// } else { +// if (_amount == type(uint256).max) _amount = _token.balanceOf(address(this)); +// _token.safeTransfer(_to, _amount); +// } +// } + +// /// @notice Unstuck LBTokens that are sent to this contract by mistake +// /// @dev Only callable by the factory owner +// /// @param _lbToken The address of the LBToken +// /// @param _to The address of the user to send back the tokens +// /// @param _ids The list of token ids +// /// @param _amounts The list of amounts to send +// function sweepLBToken(ILBToken _lbToken, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) +// external +// override +// onlyFactoryOwner +// { +// _lbToken.safeBatchTransferFrom(address(this), _to, _ids, _amounts); +// } + +// /// @notice Helper function to add liquidity +// /// @param _liq The liquidity parameter +// /// @param _LBPair LBPair where liquidity is deposited +// /// @return depositIds Bin ids where the liquidity was actually deposited +// /// @return liquidityMinted Amounts of LBToken minted for each bin +// function _addLiquidity(LiquidityParameters calldata _liq, ILBPair _LBPair) +// private +// ensure(_liq.deadline) +// returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) +// { +// unchecked { +// if (_liq.deltaIds.length != _liq.distributionX.length && _liq.deltaIds.length != _liq.distributionY.length) +// { +// revert LBRouter__LengthsMismatch(); +// } + +// if (_liq.activeIdDesired > type(uint24).max || _liq.idSlippage > type(uint24).max) { +// revert LBRouter__IdDesiredOverflows(_liq.activeIdDesired, _liq.idSlippage); +// } + +// (,, uint256 _activeId) = _LBPair.getReservesAndId(); +// if ( +// _liq.activeIdDesired + _liq.idSlippage < _activeId || _activeId + _liq.idSlippage < _liq.activeIdDesired +// ) revert LBRouter__IdSlippageCaught(_liq.activeIdDesired, _liq.idSlippage, _activeId); + +// depositIds = new uint256[](_liq.deltaIds.length); +// for (uint256 i; i < depositIds.length; ++i) { +// int256 _id = int256(_activeId) + _liq.deltaIds[i]; +// if (_id < 0 || uint256(_id) > type(uint24).max) revert LBRouter__IdOverflows(_id); +// depositIds[i] = uint256(_id); +// } + +// uint256 _amountXAdded; +// uint256 _amountYAdded; + +// (_amountXAdded, _amountYAdded, liquidityMinted) = +// _LBPair.mint(depositIds, _liq.distributionX, _liq.distributionY, _liq.to); + +// if (_amountXAdded < _liq.amountXMin || _amountYAdded < _liq.amountYMin) { +// revert LBRouter__AmountSlippageCaught(_liq.amountXMin, _amountXAdded, _liq.amountYMin, _amountYAdded); +// } +// } +// } + +// /// @notice Helper function to return the amounts in +// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) +// /// @param _pairs The list of pairs +// /// @param _tokenPath The swap path +// /// @param _amountOut The amount out +// /// @return amountsIn The list of amounts in +// function _getAmountsIn( +// uint256[] memory _pairBinSteps, +// address[] memory _pairs, +// IERC20[] memory _tokenPath, +// uint256 _amountOut +// ) private view returns (uint256[] memory amountsIn) { +// amountsIn = new uint256[](_tokenPath.length); +// // Avoid doing -1, as `_pairs.length == _pairBinSteps.length-1` +// amountsIn[_pairs.length] = _amountOut; + +// for (uint256 i = _pairs.length; i != 0; i--) { +// IERC20 _token = _tokenPath[i - 1]; +// uint256 _binStep = _pairBinSteps[i - 1]; + +// address _pair = _pairs[i - 1]; + +// if (_binStep == 0) { +// (uint256 _reserveIn, uint256 _reserveOut,) = IJoePair(_pair).getReserves(); +// if (_token > _tokenPath[i]) { +// (_reserveIn, _reserveOut) = (_reserveOut, _reserveIn); +// } + +// uint256 amountOut_ = amountsIn[i]; +// amountsIn[i - 1] = amountOut_.getAmountIn(_reserveIn, _reserveOut); +// } else { +// (amountsIn[i - 1],) = getSwapIn(ILBPair(_pair), amountsIn[i], ILBPair(_pair).tokenX() == _token); +// } +// } +// } + +// /// @notice Helper function to remove liquidity +// /// @param _LBPair The address of the LBPair +// /// @param _amountXMin The min amount to receive of token X +// /// @param _amountYMin The min amount to receive of token Y +// /// @param _ids The list of ids to burn +// /// @param _amounts The list of amounts to burn of each id in `_ids` +// /// @param _to The address of the recipient +// /// @return amountX The amount of token X sent by the pair +// /// @return amountY The amount of token Y sent by the pair +// function _removeLiquidity( +// ILBPair _LBPair, +// uint256 _amountXMin, +// uint256 _amountYMin, +// uint256[] memory _ids, +// uint256[] memory _amounts, +// address _to +// ) private returns (uint256 amountX, uint256 amountY) { +// ILBToken(address(_LBPair)).safeBatchTransferFrom(msg.sender, address(_LBPair), _ids, _amounts); +// (amountX, amountY) = _LBPair.burn(_ids, _amounts, _to); +// if (amountX < _amountXMin || amountY < _amountYMin) { +// revert LBRouter__AmountSlippageCaught(_amountXMin, amountX, _amountYMin, amountY); +// } +// } + +// /// @notice Helper function to swap exact tokens for tokens +// /// @param _amountIn The amount of token sent +// /// @param _pairs The list of pairs +// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) +// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` +// /// @param _to The address of the recipient +// /// @return amountOut The amount of token sent to `_to` +// function _swapExactTokensForTokens( +// uint256 _amountIn, +// address[] memory _pairs, +// uint256[] memory _pairBinSteps, +// IERC20[] memory _tokenPath, +// address _to +// ) private returns (uint256 amountOut) { +// IERC20 _token; +// uint256 _binStep; +// address _recipient; +// address _pair; + +// IERC20 _tokenNext = _tokenPath[0]; +// amountOut = _amountIn; + +// unchecked { +// for (uint256 i; i < _pairs.length; ++i) { +// _pair = _pairs[i]; +// _binStep = _pairBinSteps[i]; + +// _token = _tokenNext; +// _tokenNext = _tokenPath[i + 1]; + +// _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; + +// if (_binStep == 0) { +// (uint256 _reserve0, uint256 _reserve1,) = IJoePair(_pair).getReserves(); + +// if (_token < _tokenNext) { +// amountOut = amountOut.getAmountOut(_reserve0, _reserve1); +// IJoePair(_pair).swap(0, amountOut, _recipient, ""); +// } else { +// amountOut = amountOut.getAmountOut(_reserve1, _reserve0); +// IJoePair(_pair).swap(amountOut, 0, _recipient, ""); +// } +// } else { +// bool _swapForY = _tokenNext == ILBPair(_pair).tokenY(); + +// (uint256 _amountXOut, uint256 _amountYOut) = ILBPair(_pair).swap(_swapForY, _recipient); + +// if (_swapForY) amountOut = _amountYOut; +// else amountOut = _amountXOut; +// } +// } +// } +// } + +// /// @notice Helper function to swap tokens for exact tokens +// /// @param _pairs The array of pairs +// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) +// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` +// /// @param _amountsIn The list of amounts in +// /// @param _to The address of the recipient +// /// @return amountOut The amount of token sent to `_to` +// function _swapTokensForExactTokens( +// address[] memory _pairs, +// uint256[] memory _pairBinSteps, +// IERC20[] memory _tokenPath, +// uint256[] memory _amountsIn, +// address _to +// ) private returns (uint256 amountOut) { +// IERC20 _token; +// uint256 _binStep; +// address _recipient; +// address _pair; + +// IERC20 _tokenNext = _tokenPath[0]; + +// unchecked { +// for (uint256 i; i < _pairs.length; ++i) { +// _pair = _pairs[i]; +// _binStep = _pairBinSteps[i]; + +// _token = _tokenNext; +// _tokenNext = _tokenPath[i + 1]; + +// _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; + +// if (_binStep == 0) { +// amountOut = _amountsIn[i + 1]; +// if (_token < _tokenNext) { +// IJoePair(_pair).swap(0, amountOut, _recipient, ""); +// } else { +// IJoePair(_pair).swap(amountOut, 0, _recipient, ""); +// } +// } else { +// bool _swapForY = _tokenNext == ILBPair(_pair).tokenY(); + +// (uint256 _amountXOut, uint256 _amountYOut) = ILBPair(_pair).swap(_swapForY, _recipient); + +// if (_swapForY) amountOut = _amountYOut; +// else amountOut = _amountXOut; +// } +// } +// } +// } + +// /// @notice Helper function to swap exact tokens supporting for fee on transfer tokens +// /// @param _pairs The list of pairs +// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) +// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` +// /// @param _to The address of the recipient +// function _swapSupportingFeeOnTransferTokens( +// address[] memory _pairs, +// uint256[] memory _pairBinSteps, +// IERC20[] memory _tokenPath, +// address _to +// ) private { +// IERC20 _token; +// uint256 _binStep; +// address _recipient; +// address _pair; + +// IERC20 _tokenNext = _tokenPath[0]; + +// unchecked { +// for (uint256 i; i < _pairs.length; ++i) { +// _pair = _pairs[i]; +// _binStep = _pairBinSteps[i]; + +// _token = _tokenNext; +// _tokenNext = _tokenPath[i + 1]; + +// _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; + +// if (_binStep == 0) { +// (uint256 _reserve0, uint256 _reserve1,) = IJoePair(_pair).getReserves(); +// if (_token < _tokenNext) { +// uint256 _amountIn = _token.balanceOf(_pair) - _reserve0; +// uint256 _amountOut = _amountIn.getAmountOut(_reserve0, _reserve1); + +// IJoePair(_pair).swap(0, _amountOut, _recipient, ""); +// } else { +// uint256 _amountIn = _token.balanceOf(_pair) - _reserve1; +// uint256 _amountOut = _amountIn.getAmountOut(_reserve1, _reserve0); + +// IJoePair(_pair).swap(_amountOut, 0, _recipient, ""); +// } +// } else { +// ILBPair(_pair).swap(_tokenNext == ILBPair(_pair).tokenY(), _recipient); +// } +// } +// } +// } + +// /// @notice Helper function to return the address of the LBPair +// /// @dev Revert if the pair is not created yet +// /// @param _tokenX The address of the tokenX +// /// @param _tokenY The address of the tokenY +// /// @param _binStep The bin step of the LBPair +// /// @return The address of the LBPair +// function _getLBPairInformation(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep) private view returns (ILBPair) { +// ILBPair _LBPair = factory.getLBPairInformation(_tokenX, _tokenY, _binStep).LBPair; +// if (address(_LBPair) == address(0)) { +// revert LBRouter__PairNotCreated(address(_tokenX), address(_tokenY), _binStep); +// } +// return _LBPair; +// } + +// /// @notice Helper function to return the address of the pair (v1 or v2, according to `_binStep`) +// /// @dev Revert if the pair is not created yet +// /// @param _binStep The bin step of the LBPair, 0 means using V1 pair, any other value will use V2 +// /// @param _tokenX The address of the tokenX +// /// @param _tokenY The address of the tokenY +// /// @return _pair The address of the pair of binStep `_binStep` +// function _getPair(uint256 _binStep, IERC20 _tokenX, IERC20 _tokenY) private view returns (address _pair) { +// if (_binStep == 0) { +// _pair = oldFactory.getPair(address(_tokenX), address(_tokenY)); +// if (_pair == address(0)) revert LBRouter__PairNotCreated(address(_tokenX), address(_tokenY), _binStep); +// } else { +// _pair = address(_getLBPairInformation(_tokenX, _tokenY, _binStep)); +// } +// } + +// function _getPairs(uint256[] memory _pairBinSteps, IERC20[] memory _tokenPath) +// private +// view +// returns (address[] memory pairs) +// { +// pairs = new address[](_pairBinSteps.length); + +// IERC20 _token; +// IERC20 _tokenNext = _tokenPath[0]; +// unchecked { +// for (uint256 i; i < pairs.length; ++i) { +// _token = _tokenNext; +// _tokenNext = _tokenPath[i + 1]; + +// pairs[i] = _getPair(_pairBinSteps[i], _token, _tokenNext); +// } +// } +// } + +// /// @notice Helper function to transfer AVAX +// /// @param _to The address of the recipient +// /// @param _amount The AVAX amount to send +// function _safeTransferAVAX(address _to, uint256 _amount) private { +// (bool success,) = _to.call{value: _amount}(""); +// if (!success) revert LBRouter__FailedToSendAVAX(_to, _amount); +// } + +// /// @notice Helper function to deposit and transfer wavax +// /// @param _to The address of the recipient +// /// @param _amount The AVAX amount to wrap +// function _wavaxDepositAndTransfer(address _to, uint256 _amount) private { +// wavax.deposit{value: _amount}(); +// wavax.safeTransfer(_to, _amount); +// } +// } diff --git a/src/interfaces/ILBFactory.sol b/src/interfaces/ILBFactory.sol index ef54c033..36b870ab 100644 --- a/src/interfaces/ILBFactory.sol +++ b/src/interfaces/ILBFactory.sol @@ -11,159 +11,163 @@ import "./IPendingOwnable.sol"; /// @author Trader Joe /// @notice Required interface of LBFactory contract interface ILBFactory is IPendingOwnable { - /// @dev Structure to store the LBPair information, such as: - /// - binStep: The bin step of the LBPair - /// - LBPair: The address of the LBPair - /// - createdByOwner: Whether the pair was created by the owner of the factory - /// - ignoredForRouting: Whether the pair is ignored for routing or not. An ignored pair will not be explored during routes finding - struct LBPairInformation { - uint16 binStep; - ILBPair LBPair; - bool createdByOwner; - bool ignoredForRouting; - } + function getProtocolFeeRecipient() external view returns (address); - event LBPairCreated( - IERC20 indexed tokenX, IERC20 indexed tokenY, uint256 indexed binStep, ILBPair LBPair, uint256 pid - ); + function getFlashLoanFee() external view returns (uint128); - event FeeRecipientSet(address oldRecipient, address newRecipient); + // /// @dev Structure to store the LBPair information, such as: + // /// - binStep: The bin step of the LBPair + // /// - LBPair: The address of the LBPair + // /// - createdByOwner: Whether the pair was created by the owner of the factory + // /// - ignoredForRouting: Whether the pair is ignored for routing or not. An ignored pair will not be explored during routes finding + // struct LBPairInformation { + // uint16 binStep; + // ILBPair LBPair; + // bool createdByOwner; + // bool ignoredForRouting; + // } - event FlashLoanFeeSet(uint256 oldFlashLoanFee, uint256 newFlashLoanFee); + // event LBPairCreated( + // IERC20 indexed tokenX, IERC20 indexed tokenY, uint256 indexed binStep, ILBPair LBPair, uint256 pid + // ); - event FeeParametersSet( - address indexed sender, - ILBPair indexed LBPair, - uint256 binStep, - uint256 baseFactor, - uint256 filterPeriod, - uint256 decayPeriod, - uint256 reductionFactor, - uint256 variableFeeControl, - uint256 protocolShare, - uint256 maxVolatilityAccumulated - ); + // event FeeRecipientSet(address oldRecipient, address newRecipient); - event FactoryLockedStatusUpdated(bool unlocked); + // event FlashLoanFeeSet(uint256 oldFlashLoanFee, uint256 newFlashLoanFee); - event LBPairImplementationSet(address oldLBPairImplementation, address LBPairImplementation); + // event FeeParametersSet( + // address indexed sender, + // ILBPair indexed LBPair, + // uint256 binStep, + // uint256 baseFactor, + // uint256 filterPeriod, + // uint256 decayPeriod, + // uint256 reductionFactor, + // uint256 variableFeeControl, + // uint256 protocolShare, + // uint256 maxVolatilityAccumulated + // ); - event LBPairIgnoredStateChanged(ILBPair indexed LBPair, bool ignored); + // event FactoryLockedStatusUpdated(bool unlocked); - event PresetSet( - uint256 indexed binStep, - uint256 baseFactor, - uint256 filterPeriod, - uint256 decayPeriod, - uint256 reductionFactor, - uint256 variableFeeControl, - uint256 protocolShare, - uint256 maxVolatilityAccumulated, - uint256 sampleLifetime - ); + // event LBPairImplementationSet(address oldLBPairImplementation, address LBPairImplementation); - event PresetRemoved(uint256 indexed binStep); + // event LBPairIgnoredStateChanged(ILBPair indexed LBPair, bool ignored); - event QuoteAssetAdded(IERC20 indexed quoteAsset); + // event PresetSet( + // uint256 indexed binStep, + // uint256 baseFactor, + // uint256 filterPeriod, + // uint256 decayPeriod, + // uint256 reductionFactor, + // uint256 variableFeeControl, + // uint256 protocolShare, + // uint256 maxVolatilityAccumulated, + // uint256 sampleLifetime + // ); - event QuoteAssetRemoved(IERC20 indexed quoteAsset); + // event PresetRemoved(uint256 indexed binStep); - function MAX_FEE() external pure returns (uint256); + // event QuoteAssetAdded(IERC20 indexed quoteAsset); - function MIN_BIN_STEP() external pure returns (uint256); + // event QuoteAssetRemoved(IERC20 indexed quoteAsset); - function MAX_BIN_STEP() external pure returns (uint256); + // function MAX_FEE() external pure returns (uint256); - function MAX_PROTOCOL_SHARE() external pure returns (uint256); + // function MIN_BIN_STEP() external pure returns (uint256); - function LBPairImplementation() external view returns (address); + // function MAX_BIN_STEP() external pure returns (uint256); - function getNumberOfQuoteAssets() external view returns (uint256); + // function MAX_PROTOCOL_SHARE() external pure returns (uint256); - function getQuoteAsset(uint256 index) external view returns (IERC20); + // function LBPairImplementation() external view returns (address); - function isQuoteAsset(IERC20 token) external view returns (bool); + // function getNumberOfQuoteAssets() external view returns (uint256); - function feeRecipient() external view returns (address); + // function getQuoteAsset(uint256 index) external view returns (IERC20); - function flashLoanFee() external view returns (uint256); + // function isQuoteAsset(IERC20 token) external view returns (bool); - function creationUnlocked() external view returns (bool); + // function feeRecipient() external view returns (address); - function allLBPairs(uint256 id) external returns (ILBPair); + // function flashLoanFee() external view returns (uint256); - function getNumberOfLBPairs() external view returns (uint256); + // function creationUnlocked() external view returns (bool); - function getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep) - external - view - returns (LBPairInformation memory); + // function allLBPairs(uint256 id) external returns (ILBPair); - function getPreset(uint16 binStep) - external - view - returns ( - uint256 baseFactor, - uint256 filterPeriod, - uint256 decayPeriod, - uint256 reductionFactor, - uint256 variableFeeControl, - uint256 protocolShare, - uint256 maxAccumulator, - uint256 sampleLifetime - ); + // function getNumberOfLBPairs() external view returns (uint256); - function getAllBinSteps() external view returns (uint256[] memory presetsBinStep); + // function getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep) + // external + // view + // returns (LBPairInformation memory); - function getAllLBPairs(IERC20 tokenX, IERC20 tokenY) - external - view - returns (LBPairInformation[] memory LBPairsBinStep); + // function getPreset(uint16 binStep) + // external + // view + // returns ( + // uint256 baseFactor, + // uint256 filterPeriod, + // uint256 decayPeriod, + // uint256 reductionFactor, + // uint256 variableFeeControl, + // uint256 protocolShare, + // uint256 maxAccumulator, + // uint256 sampleLifetime + // ); - function setLBPairImplementation(address LBPairImplementation) external; + // function getAllBinSteps() external view returns (uint256[] memory presetsBinStep); - function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) - external - returns (ILBPair pair); + // function getAllLBPairs(IERC20 tokenX, IERC20 tokenY) + // external + // view + // returns (LBPairInformation[] memory LBPairsBinStep); - function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) external; + // function setLBPairImplementation(address LBPairImplementation) external; - function setPreset( - uint16 binStep, - uint16 baseFactor, - uint16 filterPeriod, - uint16 decayPeriod, - uint16 reductionFactor, - uint24 variableFeeControl, - uint16 protocolShare, - uint24 maxVolatilityAccumulated, - uint16 sampleLifetime - ) external; + // function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) + // external + // returns (ILBPair pair); - function removePreset(uint16 binStep) external; + // function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) external; - function setFeesParametersOnPair( - IERC20 tokenX, - IERC20 tokenY, - uint16 binStep, - uint16 baseFactor, - uint16 filterPeriod, - uint16 decayPeriod, - uint16 reductionFactor, - uint24 variableFeeControl, - uint16 protocolShare, - uint24 maxVolatilityAccumulated - ) external; + // function setPreset( + // uint16 binStep, + // uint16 baseFactor, + // uint16 filterPeriod, + // uint16 decayPeriod, + // uint16 reductionFactor, + // uint24 variableFeeControl, + // uint16 protocolShare, + // uint24 maxVolatilityAccumulated, + // uint16 sampleLifetime + // ) external; - function setFeeRecipient(address feeRecipient) external; + // function removePreset(uint16 binStep) external; - function setFlashLoanFee(uint256 flashLoanFee) external; + // function setFeesParametersOnPair( + // IERC20 tokenX, + // IERC20 tokenY, + // uint16 binStep, + // uint16 baseFactor, + // uint16 filterPeriod, + // uint16 decayPeriod, + // uint16 reductionFactor, + // uint24 variableFeeControl, + // uint16 protocolShare, + // uint24 maxVolatilityAccumulated + // ) external; - function setFactoryLockedState(bool locked) external; + // function setFeeRecipient(address feeRecipient) external; - function addQuoteAsset(IERC20 quoteAsset) external; + // function setFlashLoanFee(uint256 flashLoanFee) external; - function removeQuoteAsset(IERC20 quoteAsset) external; + // function setFactoryLockedState(bool locked) external; - function forceDecay(ILBPair LBPair) external; + // function addQuoteAsset(IERC20 quoteAsset) external; + + // function removeQuoteAsset(IERC20 quoteAsset) external; + + // function forceDecay(ILBPair LBPair) external; } diff --git a/src/interfaces/ILBRouter.sol b/src/interfaces/ILBRouter.sol index b6a4c833..95f03021 100644 --- a/src/interfaces/ILBRouter.sol +++ b/src/interfaces/ILBRouter.sol @@ -2,183 +2,183 @@ pragma solidity 0.8.10; -import "./IJoeFactory.sol"; -import "./ILBPair.sol"; -import "./ILBToken.sol"; -import "./IWAVAX.sol"; - -/// @title Liquidity Book Router Interface -/// @author Trader Joe -/// @notice Required interface of LBRouter contract -interface ILBRouter { - /// @dev The liquidity parameters, such as: - /// - tokenX: The address of token X - /// - tokenY: The address of token Y - /// - binStep: The bin step of the pair - /// - amountX: The amount to send of token X - /// - amountY: The amount to send of token Y - /// - amountXMin: The min amount of token X added to liquidity - /// - amountYMin: The min amount of token Y added to liquidity - /// - activeIdDesired: The active id that user wants to add liquidity from - /// - idSlippage: The number of id that are allowed to slip - /// - deltaIds: The list of delta ids to add liquidity (`deltaId = activeId - desiredId`) - /// - distributionX: The distribution of tokenX with sum(distributionX) = 100e18 (100%) or 0 (0%) - /// - distributionY: The distribution of tokenY with sum(distributionY) = 100e18 (100%) or 0 (0%) - /// - to: The address of the recipient - /// - deadline: The deadline of the tx - struct LiquidityParameters { - IERC20 tokenX; - IERC20 tokenY; - uint256 binStep; - uint256 amountX; - uint256 amountY; - uint256 amountXMin; - uint256 amountYMin; - uint256 activeIdDesired; - uint256 idSlippage; - int256[] deltaIds; - uint256[] distributionX; - uint256[] distributionY; - address to; - uint256 deadline; - } - - function factory() external view returns (ILBFactory); - - function oldFactory() external view returns (IJoeFactory); - - function wavax() external view returns (IWAVAX); - - function getIdFromPrice(ILBPair LBPair, uint256 price) external view returns (uint24); - - function getPriceFromId(ILBPair LBPair, uint24 id) external view returns (uint256); - - function getSwapIn(ILBPair LBPair, uint256 amountOut, bool swapForY) - external - view - returns (uint256 amountIn, uint256 feesIn); - - function getSwapOut(ILBPair LBPair, uint256 amountIn, bool swapForY) - external - view - returns (uint256 amountOut, uint256 feesIn); - - function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) - external - returns (ILBPair pair); - - function addLiquidity(LiquidityParameters calldata liquidityParameters) - external - returns (uint256[] memory depositIds, uint256[] memory liquidityMinted); - - function addLiquidityAVAX(LiquidityParameters calldata liquidityParameters) - external - payable - returns (uint256[] memory depositIds, uint256[] memory liquidityMinted); - - function removeLiquidity( - IERC20 tokenX, - IERC20 tokenY, - uint16 binStep, - uint256 amountXMin, - uint256 amountYMin, - uint256[] memory ids, - uint256[] memory amounts, - address to, - uint256 deadline - ) external returns (uint256 amountX, uint256 amountY); - - function removeLiquidityAVAX( - IERC20 token, - uint16 binStep, - uint256 amountTokenMin, - uint256 amountAVAXMin, - uint256[] memory ids, - uint256[] memory amounts, - address payable to, - uint256 deadline - ) external returns (uint256 amountToken, uint256 amountAVAX); - - function swapExactTokensForTokens( - uint256 amountIn, - uint256 amountOutMin, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, - address to, - uint256 deadline - ) external returns (uint256 amountOut); - - function swapExactTokensForAVAX( - uint256 amountIn, - uint256 amountOutMinAVAX, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, - address payable to, - uint256 deadline - ) external returns (uint256 amountOut); - - function swapExactAVAXForTokens( - uint256 amountOutMin, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, - address to, - uint256 deadline - ) external payable returns (uint256 amountOut); - - function swapTokensForExactTokens( - uint256 amountOut, - uint256 amountInMax, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, - address to, - uint256 deadline - ) external returns (uint256[] memory amountsIn); - - function swapTokensForExactAVAX( - uint256 amountOut, - uint256 amountInMax, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, - address payable to, - uint256 deadline - ) external returns (uint256[] memory amountsIn); - - function swapAVAXForExactTokens( - uint256 amountOut, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, - address to, - uint256 deadline - ) external payable returns (uint256[] memory amountsIn); - - function swapExactTokensForTokensSupportingFeeOnTransferTokens( - uint256 amountIn, - uint256 amountOutMin, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, - address to, - uint256 deadline - ) external returns (uint256 amountOut); - - function swapExactTokensForAVAXSupportingFeeOnTransferTokens( - uint256 amountIn, - uint256 amountOutMinAVAX, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, - address payable to, - uint256 deadline - ) external returns (uint256 amountOut); - - function swapExactAVAXForTokensSupportingFeeOnTransferTokens( - uint256 amountOutMin, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, - address to, - uint256 deadline - ) external payable returns (uint256 amountOut); - - function sweep(IERC20 token, address to, uint256 amount) external; - - function sweepLBToken(ILBToken _lbToken, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) - external; -} +// import "./IJoeFactory.sol"; +// import "./ILBPair.sol"; +// import "./ILBToken.sol"; +// import "./IWAVAX.sol"; + +// /// @title Liquidity Book Router Interface +// /// @author Trader Joe +// /// @notice Required interface of LBRouter contract +// interface ILBRouter { +// /// @dev The liquidity parameters, such as: +// /// - tokenX: The address of token X +// /// - tokenY: The address of token Y +// /// - binStep: The bin step of the pair +// /// - amountX: The amount to send of token X +// /// - amountY: The amount to send of token Y +// /// - amountXMin: The min amount of token X added to liquidity +// /// - amountYMin: The min amount of token Y added to liquidity +// /// - activeIdDesired: The active id that user wants to add liquidity from +// /// - idSlippage: The number of id that are allowed to slip +// /// - deltaIds: The list of delta ids to add liquidity (`deltaId = activeId - desiredId`) +// /// - distributionX: The distribution of tokenX with sum(distributionX) = 100e18 (100%) or 0 (0%) +// /// - distributionY: The distribution of tokenY with sum(distributionY) = 100e18 (100%) or 0 (0%) +// /// - to: The address of the recipient +// /// - deadline: The deadline of the tx +// struct LiquidityParameters { +// IERC20 tokenX; +// IERC20 tokenY; +// uint256 binStep; +// uint256 amountX; +// uint256 amountY; +// uint256 amountXMin; +// uint256 amountYMin; +// uint256 activeIdDesired; +// uint256 idSlippage; +// int256[] deltaIds; +// uint256[] distributionX; +// uint256[] distributionY; +// address to; +// uint256 deadline; +// } + +// function factory() external view returns (ILBFactory); + +// function oldFactory() external view returns (IJoeFactory); + +// function wavax() external view returns (IWAVAX); + +// function getIdFromPrice(ILBPair LBPair, uint256 price) external view returns (uint24); + +// function getPriceFromId(ILBPair LBPair, uint24 id) external view returns (uint256); + +// function getSwapIn(ILBPair LBPair, uint256 amountOut, bool swapForY) +// external +// view +// returns (uint256 amountIn, uint256 feesIn); + +// function getSwapOut(ILBPair LBPair, uint256 amountIn, bool swapForY) +// external +// view +// returns (uint256 amountOut, uint256 feesIn); + +// function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) +// external +// returns (ILBPair pair); + +// function addLiquidity(LiquidityParameters calldata liquidityParameters) +// external +// returns (uint256[] memory depositIds, uint256[] memory liquidityMinted); + +// function addLiquidityAVAX(LiquidityParameters calldata liquidityParameters) +// external +// payable +// returns (uint256[] memory depositIds, uint256[] memory liquidityMinted); + +// function removeLiquidity( +// IERC20 tokenX, +// IERC20 tokenY, +// uint16 binStep, +// uint256 amountXMin, +// uint256 amountYMin, +// uint256[] memory ids, +// uint256[] memory amounts, +// address to, +// uint256 deadline +// ) external returns (uint256 amountX, uint256 amountY); + +// function removeLiquidityAVAX( +// IERC20 token, +// uint16 binStep, +// uint256 amountTokenMin, +// uint256 amountAVAXMin, +// uint256[] memory ids, +// uint256[] memory amounts, +// address payable to, +// uint256 deadline +// ) external returns (uint256 amountToken, uint256 amountAVAX); + +// function swapExactTokensForTokens( +// uint256 amountIn, +// uint256 amountOutMin, +// uint256[] memory pairBinSteps, +// IERC20[] memory tokenPath, +// address to, +// uint256 deadline +// ) external returns (uint256 amountOut); + +// function swapExactTokensForAVAX( +// uint256 amountIn, +// uint256 amountOutMinAVAX, +// uint256[] memory pairBinSteps, +// IERC20[] memory tokenPath, +// address payable to, +// uint256 deadline +// ) external returns (uint256 amountOut); + +// function swapExactAVAXForTokens( +// uint256 amountOutMin, +// uint256[] memory pairBinSteps, +// IERC20[] memory tokenPath, +// address to, +// uint256 deadline +// ) external payable returns (uint256 amountOut); + +// function swapTokensForExactTokens( +// uint256 amountOut, +// uint256 amountInMax, +// uint256[] memory pairBinSteps, +// IERC20[] memory tokenPath, +// address to, +// uint256 deadline +// ) external returns (uint256[] memory amountsIn); + +// function swapTokensForExactAVAX( +// uint256 amountOut, +// uint256 amountInMax, +// uint256[] memory pairBinSteps, +// IERC20[] memory tokenPath, +// address payable to, +// uint256 deadline +// ) external returns (uint256[] memory amountsIn); + +// function swapAVAXForExactTokens( +// uint256 amountOut, +// uint256[] memory pairBinSteps, +// IERC20[] memory tokenPath, +// address to, +// uint256 deadline +// ) external payable returns (uint256[] memory amountsIn); + +// function swapExactTokensForTokensSupportingFeeOnTransferTokens( +// uint256 amountIn, +// uint256 amountOutMin, +// uint256[] memory pairBinSteps, +// IERC20[] memory tokenPath, +// address to, +// uint256 deadline +// ) external returns (uint256 amountOut); + +// function swapExactTokensForAVAXSupportingFeeOnTransferTokens( +// uint256 amountIn, +// uint256 amountOutMinAVAX, +// uint256[] memory pairBinSteps, +// IERC20[] memory tokenPath, +// address payable to, +// uint256 deadline +// ) external returns (uint256 amountOut); + +// function swapExactAVAXForTokensSupportingFeeOnTransferTokens( +// uint256 amountOutMin, +// uint256[] memory pairBinSteps, +// IERC20[] memory tokenPath, +// address to, +// uint256 deadline +// ) external payable returns (uint256 amountOut); + +// function sweep(IERC20 token, address to, uint256 amount) external; + +// function sweepLBToken(ILBToken _lbToken, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) +// external; +// } diff --git a/src/libraries/Buffer.sol b/src/libraries/Buffer.sol deleted file mode 100644 index fbe99837..00000000 --- a/src/libraries/Buffer.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -/// @title Liquidity Book Buffer Library -/// @author Trader Joe -/// @notice Helper contract used for modulo calculation -library Buffer { - /// @notice Internal function to do positive (x - 1) % n - /// @param x The value - /// @param n The modulo value - /// @return result The result - function before(uint256 x, uint256 n) internal pure returns (uint256 result) { - assembly { - if gt(n, 0) { - switch x - case 0 { result := sub(n, 1) } - default { result := mod(sub(x, 1), n) } - } - } - } -} diff --git a/src/libraries/Decoder.sol b/src/libraries/Decoder.sol deleted file mode 100644 index 4bca46ed..00000000 --- a/src/libraries/Decoder.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -/// @title Liquidity Book Decoder Library -/// @author Trader Joe -/// @notice Helper contract used for decoding bytes32 sample -library Decoder { - /// @notice Internal function to decode a bytes32 sample using a mask and offset - /// @dev This function can overflow - /// @param _sample The sample as a bytes32 - /// @param _mask The mask - /// @param _offset The offset - /// @return value The decoded value - function decode(bytes32 _sample, uint256 _mask, uint256 _offset) internal pure returns (uint256 value) { - assembly { - value := and(shr(_offset, _sample), _mask) - } - } -} diff --git a/src/libraries/Encoder.sol b/src/libraries/Encoder.sol deleted file mode 100644 index 50da0973..00000000 --- a/src/libraries/Encoder.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -/// @title Liquidity Book Encoder Library -/// @author Trader Joe -/// @notice Helper contract used for encoding uint256 value -library Encoder { - /// @notice Internal function to encode a uint256 value using a mask and offset - /// @dev This function can underflow - /// @param _value The value as a uint256 - /// @param _mask The mask - /// @param _offset The offset - /// @return sample The encoded bytes32 sample - function encode(uint256 _value, uint256 _mask, uint256 _offset) internal pure returns (bytes32 sample) { - assembly { - sample := shl(_offset, and(_value, _mask)) - } - } -} diff --git a/src/libraries/FeeDistributionHelper.sol b/src/libraries/FeeDistributionHelper.sol deleted file mode 100644 index ca5c9c1c..00000000 --- a/src/libraries/FeeDistributionHelper.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import "../LBErrors.sol"; -import "./Constants.sol"; -import "./FeeHelper.sol"; - -/// @title Liquidity Book Fee Distribution Helper Library -/// @author Trader Joe -/// @notice Helper contract used for fees distribution calculations -library FeeDistributionHelper { - /// @notice Calculate the tokenPerShare when fees are added - /// @param _fees The fees received by the pair - /// @param _totalSupply the total supply of a specific bin - function getTokenPerShare(FeeHelper.FeesDistribution memory _fees, uint256 _totalSupply) - internal - pure - returns (uint256) - { - unchecked { - // This can't overflow as `totalFees >= protocolFees`, - // shift can't overflow as we shift fees that are a uint128, by 128 bits. - // The result will always be smaller than max(uint256) - return ((uint256(_fees.total) - _fees.protocol) << Constants.SCALE_OFFSET) / _totalSupply; - } - } -} diff --git a/src/libraries/Oracle.sol b/src/libraries/Oracle.sol deleted file mode 100644 index 3d3844cf..00000000 --- a/src/libraries/Oracle.sol +++ /dev/null @@ -1,185 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import "../LBErrors.sol"; -import "./Buffer.sol"; -import "./Samples.sol"; - -/// @title Liquidity Book Oracle Library -/// @author Trader Joe -/// @notice Helper contract for oracle -library Oracle { - using Samples for bytes32; - using Buffer for uint256; - - struct Sample { - uint256 timestamp; - uint256 cumulativeId; - uint256 cumulativeVolatilityAccumulated; - uint256 cumulativeBinCrossed; - } - - /// @notice View function to get the oracle's sample at `_ago` seconds - /// @dev Return a linearized sample, the weighted average of 2 neighboring samples - /// @param _oracle The oracle storage pointer - /// @param _activeSize The size of the oracle (without empty data) - /// @param _activeId The active index of the oracle - /// @param _lookUpTimestamp The looked up date - /// @return timestamp The timestamp of the sample - /// @return cumulativeId The weighted average cumulative id - /// @return cumulativeVolatilityAccumulated The weighted average cumulative volatility accumulated - /// @return cumulativeBinCrossed The weighted average cumulative bin crossed - function getSampleAt( - bytes32[65_535] storage _oracle, - uint256 _activeSize, - uint256 _activeId, - uint256 _lookUpTimestamp - ) - internal - view - returns ( - uint256 timestamp, - uint256 cumulativeId, - uint256 cumulativeVolatilityAccumulated, - uint256 cumulativeBinCrossed - ) - { - if (_activeSize == 0) revert Oracle__NotInitialized(); - - // Oldest sample - uint256 _nextId; - assembly { - _nextId := addmod(_activeId, 1, _activeSize) - } - bytes32 _sample = _oracle[_nextId]; - timestamp = _sample.timestamp(); - if (timestamp > _lookUpTimestamp) revert Oracle__LookUpTimestampTooOld(timestamp, _lookUpTimestamp); - - // Most recent sample - if (_activeSize != 1) { - _sample = _oracle[_activeId]; - timestamp = _sample.timestamp(); - - if (timestamp > _lookUpTimestamp) { - bytes32 _next; - (_sample, _next) = binarySearch(_oracle, _activeId, _lookUpTimestamp, _activeSize); - - if (_sample != _next) { - uint256 _weightPrev = _next.timestamp() - _lookUpTimestamp; // _next.timestamp() - _sample.timestamp() - (_lookUpTimestamp - _sample.timestamp()) - uint256 _weightNext = _lookUpTimestamp - _sample.timestamp(); // _next.timestamp() - _sample.timestamp() - (_next.timestamp() - _lookUpTimestamp) - uint256 _totalWeight = _weightPrev + _weightNext; // _next.timestamp() - _sample.timestamp() - - cumulativeId = - (_sample.cumulativeId() * _weightPrev + _next.cumulativeId() * _weightNext) / _totalWeight; - cumulativeVolatilityAccumulated = ( - _sample.cumulativeVolatilityAccumulated() * _weightPrev - + _next.cumulativeVolatilityAccumulated() * _weightNext - ) / _totalWeight; - cumulativeBinCrossed = ( - _sample.cumulativeBinCrossed() * _weightPrev + _next.cumulativeBinCrossed() * _weightNext - ) / _totalWeight; - return (_lookUpTimestamp, cumulativeId, cumulativeVolatilityAccumulated, cumulativeBinCrossed); - } - } - } - - timestamp = _sample.timestamp(); - cumulativeId = _sample.cumulativeId(); - cumulativeVolatilityAccumulated = _sample.cumulativeVolatilityAccumulated(); - cumulativeBinCrossed = _sample.cumulativeBinCrossed(); - } - - /// @notice Function to update a sample - /// @param _oracle The oracle storage pointer - /// @param _size The size of the oracle (last ids can be empty) - /// @param _sampleLifetime The lifetime of a sample, it accumulates information for up to this timestamp - /// @param _lastTimestamp The timestamp of the creation of the oracle's latest sample - /// @param _lastIndex The index of the oracle's latest sample - /// @param _activeId The active index of the pair during the latest swap - /// @param _volatilityAccumulated The volatility accumulated of the pair during the latest swap - /// @param _binCrossed The bin crossed during the latest swap - /// @return updatedIndex The oracle updated index, it is either the same as before, or the next one - function update( - bytes32[65_535] storage _oracle, - uint256 _size, - uint256 _sampleLifetime, - uint256 _lastTimestamp, - uint256 _lastIndex, - uint256 _activeId, - uint256 _volatilityAccumulated, - uint256 _binCrossed - ) internal returns (uint256 updatedIndex) { - bytes32 _updatedPackedSample = _oracle[_lastIndex].update(_activeId, _volatilityAccumulated, _binCrossed); - - if (block.timestamp - _lastTimestamp >= _sampleLifetime && _lastTimestamp != 0) { - assembly { - updatedIndex := addmod(_lastIndex, 1, _size) - } - } else { - updatedIndex = _lastIndex; - } - - _oracle[updatedIndex] = _updatedPackedSample; - } - - /// @notice Initialize the sample - /// @param _oracle The oracle storage pointer - /// @param _index The index to initialize - function initialize(bytes32[65_535] storage _oracle, uint256 _index) internal { - _oracle[_index] |= bytes32(uint256(1)); - } - - /// @notice Binary search on oracle samples and return the 2 samples (as bytes32) that surrounds the `lookUpTimestamp` - /// @dev The oracle needs to be in increasing order `{_index + 1, _index + 2 ..., _index + _activeSize} % _activeSize`. - /// The sample that aren't initialized yet will be skipped as _activeSize only contains the samples that are initialized. - /// This function works only if `timestamp(_oracle[_index + 1 % _activeSize] <= _lookUpTimestamp <= timestamp(_oracle[_index]`. - /// The edge cases needs to be handled before - /// @param _oracle The oracle storage pointer - /// @param _index The current index of the oracle - /// @param _lookUpTimestamp The looked up timestamp - /// @param _activeSize The size of the oracle (without empty data) - /// @return prev The last sample with a timestamp lower than the lookUpTimestamp - /// @return next The first sample with a timestamp greater than the lookUpTimestamp - function binarySearch( - bytes32[65_535] storage _oracle, - uint256 _index, - uint256 _lookUpTimestamp, - uint256 _activeSize - ) private view returns (bytes32 prev, bytes32 next) { - // The sample with the lowest timestamp is the one right after _index - uint256 _low = 1; - uint256 _high = _activeSize; - - uint256 _middle; - uint256 _id; - - bytes32 _sample; - uint256 _sampleTimestamp; - while (_high >= _low) { - unchecked { - _middle = (_low + _high) >> 1; - assembly { - _id := addmod(_middle, _index, _activeSize) - } - _sample = _oracle[_id]; - _sampleTimestamp = _sample.timestamp(); - if (_sampleTimestamp < _lookUpTimestamp) { - _low = _middle + 1; - } else if (_sampleTimestamp > _lookUpTimestamp) { - _high = _middle - 1; - } else { - return (_sample, _sample); - } - } - } - if (_sampleTimestamp < _lookUpTimestamp) { - assembly { - _id := addmod(_id, 1, _activeSize) - } - (prev, next) = (_sample, _oracle[_id]); - } else { - (prev, next) = (_oracle[_id.before(_activeSize)], _sample); - } - } -} diff --git a/src/libraries/SafeMath.sol b/src/libraries/SafeMath.sol deleted file mode 100644 index 0452aa16..00000000 --- a/src/libraries/SafeMath.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -/// @title Liquidity Book Safe Math Helper Library -/// @author Trader Joe -/// @notice Helper contract used for calculating absolute value safely -library SafeMath { - /// @notice absSub, can't underflow or overflow - /// @param x The first value - /// @param y The second value - /// @return The result of abs(x - y) - function absSub(uint256 x, uint256 y) internal pure returns (uint256) { - unchecked { - return x > y ? x - y : y - x; - } - } -} diff --git a/src/libraries/Samples.sol b/src/libraries/Samples.sol deleted file mode 100644 index fad23a30..00000000 --- a/src/libraries/Samples.sol +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import "./Decoder.sol"; -import "./Encoder.sol"; - -/// @title Liquidity Book Sample Helper Library -/// @author Trader Joe -/// @notice Helper contract used for oracle samples operations -library Samples { - using Encoder for uint256; - using Decoder for bytes32; - - /// [ cumulativeBinCrossed | cumulativeVolatilityAccumulated | cumulativeId | timestamp | initialized ] - /// [ uint87 | uint64 | uint64 | uint40 | bool1 ] - /// MSB LSB - - uint256 private constant _OFFSET_INITIALIZED = 0; - uint256 private constant _MASK_INITIALIZED = 1; - - uint256 private constant _OFFSET_TIMESTAMP = 1; - uint256 private constant _MASK_TIMESTAMP = type(uint40).max; - - uint256 private constant _OFFSET_CUMULATIVE_ID = 41; - uint256 private constant _MASK_CUMULATIVE_ID = type(uint64).max; - - uint256 private constant _OFFSET_CUMULATIVE_VolatilityAccumulated = 105; - uint256 private constant _MASK_CUMULATIVE_VolatilityAccumulated = type(uint64).max; - - uint256 private constant _OFFSET_CUMULATIVE_BIN_CROSSED = 169; - uint256 private constant _MASK_CUMULATIVE_BIN_CROSSED = 0x7fffffffffffffffffffff; - - /// @notice Function to update a sample - /// @param _lastSample The latest sample of the oracle - /// @param _activeId The active index of the pair during the latest swap - /// @param _volatilityAccumulated The volatility accumulated of the pair during the latest swap - /// @param _binCrossed The bin crossed during the latest swap - /// @return packedSample The packed sample as bytes32 - function update(bytes32 _lastSample, uint256 _activeId, uint256 _volatilityAccumulated, uint256 _binCrossed) - internal - view - returns (bytes32 packedSample) - { - uint256 _deltaTime = block.timestamp - timestamp(_lastSample); - - // cumulative can overflow without any issue as what matter is the delta cumulative. - // It would be an issue if 2 overflows would happen but way too much time should elapsed for it to happen. - // The delta calculation needs to be unchecked math to allow for it to overflow again. - unchecked { - uint256 _cumulativeId = cumulativeId(_lastSample) + _activeId * _deltaTime; - uint256 _cumulativeVolatilityAccumulated = - cumulativeVolatilityAccumulated(_lastSample) + _volatilityAccumulated * _deltaTime; - uint256 _cumulativeBinCrossed = cumulativeBinCrossed(_lastSample) + _binCrossed * _deltaTime; - - return pack(_cumulativeBinCrossed, _cumulativeVolatilityAccumulated, _cumulativeId, block.timestamp, 1); - } - } - - /// @notice Function to pack cumulative values - /// @param _cumulativeBinCrossed The cumulative bin crossed - /// @param _cumulativeVolatilityAccumulated The cumulative volatility accumulated - /// @param _cumulativeId The cumulative index - /// @param _timestamp The timestamp - /// @param _initialized The initialized value - /// @return packedSample The packed sample as bytes32 - function pack( - uint256 _cumulativeBinCrossed, - uint256 _cumulativeVolatilityAccumulated, - uint256 _cumulativeId, - uint256 _timestamp, - uint256 _initialized - ) internal pure returns (bytes32 packedSample) { - return _cumulativeBinCrossed.encode(_MASK_CUMULATIVE_BIN_CROSSED, _OFFSET_CUMULATIVE_BIN_CROSSED) - | _cumulativeVolatilityAccumulated.encode( - _MASK_CUMULATIVE_VolatilityAccumulated, _OFFSET_CUMULATIVE_VolatilityAccumulated - ) | _cumulativeId.encode(_MASK_CUMULATIVE_ID, _OFFSET_CUMULATIVE_ID) - | _timestamp.encode(_MASK_TIMESTAMP, _OFFSET_TIMESTAMP) - | _initialized.encode(_MASK_INITIALIZED, _OFFSET_INITIALIZED); - } - - /// @notice View function to return the initialized value - /// @param _packedSample The packed sample - /// @return The initialized value - function initialized(bytes32 _packedSample) internal pure returns (uint256) { - return _packedSample.decode(_MASK_INITIALIZED, _OFFSET_INITIALIZED); - } - - /// @notice View function to return the timestamp value - /// @param _packedSample The packed sample - /// @return The timestamp value - function timestamp(bytes32 _packedSample) internal pure returns (uint256) { - return _packedSample.decode(_MASK_TIMESTAMP, _OFFSET_TIMESTAMP); - } - - /// @notice View function to return the cumulative id value - /// @param _packedSample The packed sample - /// @return The cumulative id value - function cumulativeId(bytes32 _packedSample) internal pure returns (uint256) { - return _packedSample.decode(_MASK_CUMULATIVE_ID, _OFFSET_CUMULATIVE_ID); - } - - /// @notice View function to return the cumulative volatility accumulated value - /// @param _packedSample The packed sample - /// @return The cumulative volatility accumulated value - function cumulativeVolatilityAccumulated(bytes32 _packedSample) internal pure returns (uint256) { - return _packedSample.decode(_MASK_CUMULATIVE_VolatilityAccumulated, _OFFSET_CUMULATIVE_VolatilityAccumulated); - } - - /// @notice View function to return the cumulative bin crossed value - /// @param _packedSample The packed sample - /// @return The cumulative bin crossed value - function cumulativeBinCrossed(bytes32 _packedSample) internal pure returns (uint256) { - return _packedSample.decode(_MASK_CUMULATIVE_BIN_CROSSED, _OFFSET_CUMULATIVE_BIN_CROSSED); - } -} diff --git a/src/libraries/SwapHelper.sol b/src/libraries/SwapHelper.sol deleted file mode 100644 index cd30f375..00000000 --- a/src/libraries/SwapHelper.sol +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import "./BinHelper.sol"; -import "./Constants.sol"; -import "./FeeDistributionHelper.sol"; -import "./FeeHelper.sol"; -import "./Math512Bits.sol"; -import "./SafeMath.sol"; -import "../interfaces/ILBPair.sol"; - -/// @title Liquidity Book Swap Helper Library -/// @author Trader Joe -/// @notice Helper contract used for calculating swaps, fees and reserves changes -library SwapHelper { - using Math512Bits for uint256; - using FeeHelper for FeeHelper.FeeParameters; - using SafeMath for uint256; - using FeeDistributionHelper for FeeHelper.FeesDistribution; - - /// @notice Returns the swap amounts in the current bin - /// @param bin The bin information - /// @param fp The fee parameters - /// @param activeId The active id of the pair - /// @param swapForY Whether you've swapping token X for token Y (true) or token Y for token X (false) - /// @param amountIn The amount sent by the user - /// @return amountInToBin The amount of token that is added to the bin without the fees - /// @return amountOutOfBin The amount of token that is removed from the bin - /// @return fees The swap fees - function getAmounts( - ILBPair.Bin memory bin, - FeeHelper.FeeParameters memory fp, - uint256 activeId, - bool swapForY, - uint256 amountIn - ) internal pure returns (uint256 amountInToBin, uint256 amountOutOfBin, FeeHelper.FeesDistribution memory fees) { - uint256 _price = BinHelper.getPriceFromId(activeId, fp.binStep); - - uint256 _reserve; - uint256 _maxAmountInToBin; - if (swapForY) { - _reserve = bin.reserveY; - _maxAmountInToBin = _reserve.shiftDivRoundUp(Constants.SCALE_OFFSET, _price); - } else { - _reserve = bin.reserveX; - _maxAmountInToBin = _price.mulShiftRoundUp(_reserve, Constants.SCALE_OFFSET); - } - - fp.updateVolatilityAccumulated(activeId); - fees = fp.getFeeAmountDistribution(fp.getFeeAmount(_maxAmountInToBin)); - - if (_maxAmountInToBin + fees.total <= amountIn) { - amountInToBin = _maxAmountInToBin; - amountOutOfBin = _reserve; - } else { - fees = fp.getFeeAmountDistribution(fp.getFeeAmountFrom(amountIn)); - amountInToBin = amountIn - fees.total; - amountOutOfBin = swapForY - ? _price.mulShiftRoundDown(amountInToBin, Constants.SCALE_OFFSET) - : amountInToBin.shiftDivRoundDown(Constants.SCALE_OFFSET, _price); - // Safety check in case rounding returns a higher value than expected - if (amountOutOfBin > _reserve) amountOutOfBin = _reserve; - } - } - - /// @notice Update the fees of the pair and accumulated token per share of the bin - /// @param bin The bin information - /// @param pairFees The current fees of the pair information - /// @param fees The fees amounts added to the pairFees - /// @param swapForY whether the token sent was Y (true) or X (false) - /// @param totalSupply The total supply of the token id - function updateFees( - ILBPair.Bin memory bin, - FeeHelper.FeesDistribution memory pairFees, - FeeHelper.FeesDistribution memory fees, - bool swapForY, - uint256 totalSupply - ) internal pure { - pairFees.total += fees.total; - // unsafe math is fine because total >= protocol - unchecked { - pairFees.protocol += fees.protocol; - } - - if (swapForY) { - bin.accTokenXPerShare += fees.getTokenPerShare(totalSupply); - } else { - bin.accTokenYPerShare += fees.getTokenPerShare(totalSupply); - } - } - - /// @notice Update reserves - /// @param bin The bin information - /// @param pair The pair information - /// @param swapForY whether the token sent was Y (true) or X (false) - /// @param amountInToBin The amount of token that is added to the bin without fees - /// @param amountOutOfBin The amount of token that is removed from the bin - function updateReserves( - ILBPair.Bin memory bin, - ILBPair.PairInformation memory pair, - bool swapForY, - uint112 amountInToBin, - uint112 amountOutOfBin - ) internal pure { - if (swapForY) { - bin.reserveX += amountInToBin; - - unchecked { - bin.reserveY -= amountOutOfBin; - pair.reserveX += uint136(amountInToBin); - pair.reserveY -= uint136(amountOutOfBin); - } - } else { - bin.reserveY += amountInToBin; - - unchecked { - bin.reserveX -= amountOutOfBin; - pair.reserveX -= uint136(amountOutOfBin); - pair.reserveY += uint136(amountInToBin); - } - } - } -} diff --git a/src/libraries/TreeMath.sol b/src/libraries/TreeMath.sol deleted file mode 100644 index d8f0ff2b..00000000 --- a/src/libraries/TreeMath.sol +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import "../LBErrors.sol"; -import "./BitMath.sol"; - -/// @title Liquidity Book Tree Math Library -/// @author Trader Joe -/// @notice Helper contract used for finding closest bin with liquidity -library TreeMath { - using BitMath for uint256; - - /// @notice Returns the first id that is non zero, corresponding to a bin with - /// liquidity in it - /// @param _tree The storage slot of the tree - /// @param _binId the binId to start searching - /// @param _rightSide Whether we're searching in the right side of the tree (true) or the left side (false) - /// for the closest non zero bit on the right or the left - /// @return The closest non zero bit on the right (or left) side of the tree - function findFirstBin(mapping(uint256 => uint256)[3] storage _tree, uint24 _binId, bool _rightSide) - internal - view - returns (uint24) - { - unchecked { - uint256 current; - uint256 bit; - - (_binId, bit) = _getIdsFromAbove(_binId); - - // Search in depth 2 - if ((_rightSide && bit != 0) || (!_rightSide && bit != 255)) { - current = _tree[2][_binId]; - bit = current.closestBit(uint8(bit), _rightSide); - - if (bit != type(uint256).max) { - return _getBottomId(_binId, uint24(bit)); - } - } - - (_binId, bit) = _getIdsFromAbove(_binId); - - // Search in depth 1 - if ((_rightSide && bit != 0) || (!_rightSide && bit != 255)) { - current = _tree[1][_binId]; - bit = current.closestBit(uint8(bit), _rightSide); - - if (bit != type(uint256).max) { - _binId = _getBottomId(_binId, uint24(bit)); - current = _tree[2][_binId]; - - return _getBottomId(_binId, current.significantBit(_rightSide)); - } - } - - // Search in depth 0 - current = _tree[0][0]; - bit = current.closestBit(uint8(_binId), _rightSide); - if (bit == type(uint256).max) revert TreeMath__ErrorDepthSearch(); - - current = _tree[1][bit]; - _binId = _getBottomId(uint24(bit), current.significantBit(_rightSide)); - - current = _tree[2][_binId]; - return _getBottomId(_binId, current.significantBit(_rightSide)); - } - } - - function addToTree(mapping(uint256 => uint256)[3] storage _tree, uint256 _id) internal { - // add 1 at the right indices - uint256 _idDepth2 = _id >> 8; - uint256 _idDepth1 = _id >> 16; - - _tree[2][_idDepth2] |= 1 << (_id & 255); - _tree[1][_idDepth1] |= 1 << (_idDepth2 & 255); - _tree[0][0] |= 1 << _idDepth1; - } - - function removeFromTree(mapping(uint256 => uint256)[3] storage _tree, uint256 _id) internal { - unchecked { - // removes 1 at the right indices - uint256 _idDepth2 = _id >> 8; - // Optimization of `_tree[2][_idDepth2] & (type(uint256).max - (1 << (_id & 255)))` - uint256 _newLeafValue = _tree[2][_idDepth2] & (type(uint256).max ^ (1 << (_id & 255))); - _tree[2][_idDepth2] = _newLeafValue; - if (_newLeafValue == 0) { - uint256 _idDepth1 = _id >> 16; - // Optimization of `_tree[1][_idDepth1] & (type(uint256).max - (1 << (_idDepth2 & 255)))` - _newLeafValue = _tree[1][_idDepth1] & (type(uint256).max ^ (1 << (_idDepth2 & 255))); - _tree[1][_idDepth1] = _newLeafValue; - if (_newLeafValue == 0) { - // Optimization of `type(uint256).max - (1 << _idDepth1)` - _tree[0][0] &= type(uint256).max ^ (1 << _idDepth1); - } - } - } - } - - /// @notice Private pure function to return the ids from above - /// @param _id The current id - /// @return The branch id from above - /// @return The leaf id from above - function _getIdsFromAbove(uint24 _id) private pure returns (uint24, uint24) { - // Optimization of `(_id / 256, _id % 256)` - return (_id >> 8, _id & 255); - } - - /// @notice Private pure function to return the bottom id - /// @param _branchId The branch id - /// @param _leafId The leaf id - /// @return The bottom branchId - function _getBottomId(uint24 _branchId, uint24 _leafId) private pure returns (uint24) { - // Optimization of `_branchId * 256 + _leafId` - // Can't overflow as _leafId would fit in uint8, but kept as uint24 to optimize castings - unchecked { - return (_branchId << 8) + _leafId; - } - } -} diff --git a/test/BinHelper.T.sol b/test/BinHelper.T.sol index 45c4aa96..ea6fc5f1 100644 --- a/test/BinHelper.T.sol +++ b/test/BinHelper.T.sol @@ -2,20 +2,20 @@ pragma solidity 0.8.10; -import "./helpers/TestHelper.sol"; +// import "./helpers/TestHelper.sol"; -contract BinHelperTest is TestHelper { - function testInversePriceForOppositeBins() public { - assertApproxEqAbs( - (getPriceFromId(ID_ONE + 10) * getPriceFromId(ID_ONE - 10)) / Constants.SCALE, Constants.SCALE, 1 - ); +// contract BinHelperTest is TestHelper { +// function testInversePriceForOppositeBins() public { +// assertApproxEqAbs( +// (getPriceFromId(ID_ONE + 10) * getPriceFromId(ID_ONE - 10)) / Constants.SCALE, Constants.SCALE, 1 +// ); - assertApproxEqAbs( - (getPriceFromId(ID_ONE + 1_000) * getPriceFromId(ID_ONE - 1_000)) / Constants.SCALE, Constants.SCALE, 1 - ); +// assertApproxEqAbs( +// (getPriceFromId(ID_ONE + 1_000) * getPriceFromId(ID_ONE - 1_000)) / Constants.SCALE, Constants.SCALE, 1 +// ); - assertApproxEqAbs( - (getPriceFromId(ID_ONE + 10_000) * getPriceFromId(ID_ONE - 10_000)) / Constants.SCALE, Constants.SCALE, 1 - ); - } -} +// assertApproxEqAbs( +// (getPriceFromId(ID_ONE + 10_000) * getPriceFromId(ID_ONE - 10_000)) / Constants.SCALE, Constants.SCALE, 1 +// ); +// } +// } diff --git a/test/helpers/TestHelper.sol b/test/helpers/TestHelper.sol index 36647225..2cc65b25 100644 --- a/test/helpers/TestHelper.sol +++ b/test/helpers/TestHelper.sol @@ -2,279 +2,278 @@ pragma solidity 0.8.10; -import "forge-std/Test.sol"; - -import "src/LBFactory.sol"; -import "src/LBPair.sol"; -import "src/LBRouter.sol"; -import "src/LBQuoter.sol"; -import "src/LBErrors.sol"; -import "src/interfaces/ILBRouter.sol"; -import "src/interfaces/IJoeRouter02.sol"; -import "src/LBToken.sol"; -import "src/libraries/Math512Bits.sol"; -import "src/libraries/Constants.sol"; - -import "test/mocks/WAVAX.sol"; -import "test/mocks/ERC20.sol"; -import "test/mocks/FlashloanBorrower.sol"; -import "test/mocks/ERC20TransferTax.sol"; - -abstract contract TestHelper is Test, IERC165 { - using Math512Bits for uint256; - - uint24 internal constant ID_ONE = 2 ** 23; - uint256 internal constant BASIS_POINT_MAX = 10_000; - - // Avalanche market config for 10bps - uint16 internal constant DEFAULT_BIN_STEP = 10; - uint16 internal constant DEFAULT_BASE_FACTOR = 1000; - uint16 internal constant DEFAULT_FILTER_PERIOD = 30; - uint16 internal constant DEFAULT_DECAY_PERIOD = 600; - uint16 internal constant DEFAULT_REDUCTION_FACTOR = 5_000; - uint24 internal constant DEFAULT_VARIABLE_FEE_CONTROL = 40_000; - uint16 internal constant DEFAULT_PROTOCOL_SHARE = 1_000; - uint24 internal constant DEFAULT_MAX_VOLATILITY_ACCUMULATED = 350_000; - uint16 internal constant DEFAULT_SAMPLE_LIFETIME = 120; - uint256 internal constant DEFAULT_FLASHLOAN_FEE = 8e14; - - address payable immutable DEV = payable(address(this)); - address payable immutable ALICE = payable(makeAddr("alice")); - address payable immutable BOB = payable(makeAddr("bob")); - - // Wrapped Native - WAVAX internal wavax; - - // 6 decimals - ERC20Mock internal usdc; - ERC20Mock internal usdt; - - // 8 decimals - ERC20Mock internal wbtc; - - // 18 decimals - ERC20Mock internal link; - ERC20Mock internal bnb; - ERC20Mock internal weth; - - // Tax tokens (18 decimals) - ERC20TransferTaxMock internal taxToken; - - LBFactory internal factory; - LBRouter internal router; - LBPair internal pair; - LBPair internal pairWavax; - LBQuoter internal quoter; - - function setUp() public virtual { - // Create mocks - wavax = new WAVAX(); - usdc = new ERC20Mock(6); - usdt = new ERC20Mock(6); - wbtc = new ERC20Mock(8); - weth = new ERC20Mock(18); - link = new ERC20Mock(18); - bnb = new ERC20Mock(18); - taxToken = new ERC20TransferTaxMock(); - - // Label mocks - vm.label(address(wavax), "wavax"); - vm.label(address(usdc), "usdc"); - vm.label(address(usdt), "usdt"); - vm.label(address(wbtc), "wbtc"); - vm.label(address(weth), "weth"); - vm.label(address(link), "link"); - vm.label(address(bnb), "bnb"); - vm.label(address(taxToken), "taxToken"); - - // Create factory - factory = new LBFactory(DEV, DEFAULT_FLASHLOAN_FEE); - ILBPair LBPairImplementation = new LBPair(factory); - - // Setup factory - factory.setLBPairImplementation(address(LBPairImplementation)); - addAllAssetsToQuoteWhitelist(); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - - // Create router - router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(0))); - - // Label deployed contracts - vm.label(address(factory), "factory"); - vm.label(address(router), "router"); - vm.label(address(LBPairImplementation), "LBPairImplementation"); - } - - function supportsInterface(bytes4 interfaceId) external view virtual returns (bool) { - return interfaceId == type(ILBToken).interfaceId; - } - - function getPriceFromId(uint24 id) internal pure returns (uint256 price) { - price = BinHelper.getPriceFromId(id, DEFAULT_BIN_STEP); - } - - function getIdFromPrice(uint256 price) internal pure returns (uint24 id) { - id = BinHelper.getIdFromPrice(price, DEFAULT_BIN_STEP); - } - - function createLBPair(IERC20 tokenX, IERC20 tokenY) internal returns (LBPair newPair) { - newPair = createLBPairFromStartId(tokenX, tokenY, ID_ONE); - } - - function setDefaultFactoryPresets(uint16 binStep) internal { - factory.setPreset( - binStep, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME - ); - } - - function createLBPairFromStartId(IERC20 tokenX, IERC20 tokenY, uint24 startId) internal returns (LBPair newPair) { - newPair = createLBPairFromStartIdAndBinStep(tokenX, tokenY, startId, DEFAULT_BIN_STEP); - } - - function createLBPairFromStartIdAndBinStep(IERC20 tokenX, IERC20 tokenY, uint24 startId, uint16 binStep) - internal - returns (LBPair newPair) - { - newPair = LBPair(address(factory.createLBPair(tokenX, tokenY, startId, binStep))); - } - - function convertRelativeIdsToAbsolute(int256[] memory relativeIds, uint24 startId) - internal - pure - returns (uint256[] memory absoluteIds) - { - absoluteIds = new uint256[](relativeIds.length); - for (uint256 i = 0; i < relativeIds.length; i++) { - int256 id = int256(uint256(startId)) + relativeIds[i]; - require(id >= 0, "Id conversion: id must be positive"); - absoluteIds[i] = uint256(id); - } - } - - function convertAbsoluteIdsToRelative(uint256[] memory absoluteIds, uint24 startId) - internal - pure - returns (int256[] memory relativeIds) - { - relativeIds = new int256[](absoluteIds.length); - for (uint256 i = 0; i < absoluteIds.length; i++) { - relativeIds[i] = int256(absoluteIds[i]) - int256(uint256(startId)); - } - } - - function addLiquidityAndReturnAbsoluteIds( - ERC20Mock tokenX, - ERC20Mock tokenY, - uint256 amountYIn, - uint24 startId, - uint24 numberBins, - uint24 gap - ) - internal - returns ( - uint256[] memory ids, - uint256[] memory distributionX, - uint256[] memory distributionY, - uint256 amountXIn - ) - { - (ids, distributionX, distributionY, amountXIn) = - addLiquidity(tokenX, tokenY, amountYIn, startId, numberBins, gap); - } - - function addLiquidityAndReturnRelativeIds( - ERC20Mock tokenX, - ERC20Mock tokenY, - uint256 amountYIn, - uint24 startId, - uint24 numberBins, - uint24 gap - ) - internal - returns (int256[] memory ids, uint256[] memory distributionX, uint256[] memory distributionY, uint256 amountXIn) - { - uint256[] memory absoluteIds; - (absoluteIds, distributionX, distributionY, amountXIn) = - addLiquidity(tokenX, tokenY, amountYIn, startId, numberBins, gap); - ids = convertAbsoluteIdsToRelative(absoluteIds, startId); - } - - function addLiquidity( - ERC20Mock tokenX, - ERC20Mock tokenY, - uint256 amountYIn, - uint24 startId, - uint24 numberBins, - uint24 gap - ) - internal - returns ( - uint256[] memory ids, - uint256[] memory distributionX, - uint256[] memory distributionY, - uint256 amountXIn - ) - { - (ids, distributionX, distributionY, amountXIn) = spreadLiquidity(amountYIn, startId, numberBins, gap); - - tokenX.mint(address(pair), amountXIn); - tokenY.mint(address(pair), amountYIn); - - pair.mint(ids, distributionX, distributionY, DEV); - } - - function spreadLiquidity(uint256 amountYIn, uint24 startId, uint24 numberBins, uint24 gap) - internal - pure - returns ( - uint256[] memory ids, - uint256[] memory distributionX, - uint256[] memory distributionY, - uint256 amountXIn - ) - { - if (numberBins % 2 == 0) { - revert("Pls put an uneven number of bins"); - } - - uint24 spread = numberBins / 2; - ids = new uint256[](numberBins); - - distributionX = new uint256[](numberBins); - distributionY = new uint256[](numberBins); - uint256 binDistribution = Constants.PRECISION / (spread + 1); - uint256 binLiquidity = amountYIn / (spread + 1); - - for (uint256 i; i < numberBins; i++) { - ids[i] = startId - spread * (1 + gap) + i * (1 + gap); - - if (i <= spread) { - distributionY[i] = binDistribution; - } - if (i >= spread) { - distributionX[i] = binDistribution; - amountXIn += - binLiquidity > 0 ? (binLiquidity * Constants.SCALE - 1) / getPriceFromId(uint24(ids[i])) + 1 : 0; - } - } - } - - function addAllAssetsToQuoteWhitelist() internal { - if (address(wavax) != address(0)) factory.addQuoteAsset(wavax); - if (address(usdc) != address(0)) factory.addQuoteAsset(usdc); - if (address(usdt) != address(0)) factory.addQuoteAsset(usdt); - if (address(wbtc) != address(0)) factory.addQuoteAsset(wbtc); - if (address(weth) != address(0)) factory.addQuoteAsset(weth); - if (address(link) != address(0)) factory.addQuoteAsset(link); - if (address(bnb) != address(0)) factory.addQuoteAsset(bnb); - if (address(taxToken) != address(0)) factory.addQuoteAsset(taxToken); - } -} +// import "forge-std/Test.sol"; + +// import "src/LBFactory.sol"; +// import "src/LBPair.sol"; +// import "src/LBRouter.sol"; +// import "src/LBQuoter.sol"; +// import "src/interfaces/ILBRouter.sol"; +// import "src/interfaces/IJoeRouter02.sol"; +// import "src/LBToken.sol"; +// import "src/libraries/math/Uint256x256Math.sol"; +// import "src/libraries/Constants.sol"; + +// import "test/mocks/WAVAX.sol"; +// import "test/mocks/ERC20.sol"; +// import "test/mocks/FlashloanBorrower.sol"; +// import "test/mocks/ERC20TransferTax.sol"; + +// abstract contract TestHelper is Test, IERC165 { +// using Uint256x256Math for uint256; + +// uint24 internal constant ID_ONE = 2 ** 23; +// uint256 internal constant BASIS_POINT_MAX = 10_000; + +// // Avalanche market config for 10bps +// uint16 internal constant DEFAULT_BIN_STEP = 10; +// uint16 internal constant DEFAULT_BASE_FACTOR = 1000; +// uint16 internal constant DEFAULT_FILTER_PERIOD = 30; +// uint16 internal constant DEFAULT_DECAY_PERIOD = 600; +// uint16 internal constant DEFAULT_REDUCTION_FACTOR = 5_000; +// uint24 internal constant DEFAULT_VARIABLE_FEE_CONTROL = 40_000; +// uint16 internal constant DEFAULT_PROTOCOL_SHARE = 1_000; +// uint24 internal constant DEFAULT_MAX_VOLATILITY_ACCUMULATED = 350_000; +// uint16 internal constant DEFAULT_SAMPLE_LIFETIME = 120; +// uint256 internal constant DEFAULT_FLASHLOAN_FEE = 8e14; + +// address payable immutable DEV = payable(address(this)); +// address payable immutable ALICE = payable(makeAddr("alice")); +// address payable immutable BOB = payable(makeAddr("bob")); + +// // Wrapped Native +// WAVAX internal wavax; + +// // 6 decimals +// ERC20Mock internal usdc; +// ERC20Mock internal usdt; + +// // 8 decimals +// ERC20Mock internal wbtc; + +// // 18 decimals +// ERC20Mock internal link; +// ERC20Mock internal bnb; +// ERC20Mock internal weth; + +// // Tax tokens (18 decimals) +// ERC20TransferTaxMock internal taxToken; + +// LBFactory internal factory; +// LBRouter internal router; +// LBPair internal pair; +// LBPair internal pairWavax; +// LBQuoter internal quoter; + +// function setUp() public virtual { +// // Create mocks +// wavax = new WAVAX(); +// usdc = new ERC20Mock(6); +// usdt = new ERC20Mock(6); +// wbtc = new ERC20Mock(8); +// weth = new ERC20Mock(18); +// link = new ERC20Mock(18); +// bnb = new ERC20Mock(18); +// taxToken = new ERC20TransferTaxMock(); + +// // Label mocks +// vm.label(address(wavax), "wavax"); +// vm.label(address(usdc), "usdc"); +// vm.label(address(usdt), "usdt"); +// vm.label(address(wbtc), "wbtc"); +// vm.label(address(weth), "weth"); +// vm.label(address(link), "link"); +// vm.label(address(bnb), "bnb"); +// vm.label(address(taxToken), "taxToken"); + +// // Create factory +// factory = new LBFactory(DEV, DEFAULT_FLASHLOAN_FEE); +// ILBPair LBPairImplementation = new LBPair(factory); + +// // Setup factory +// factory.setLBPairImplementation(address(LBPairImplementation)); +// addAllAssetsToQuoteWhitelist(); +// setDefaultFactoryPresets(DEFAULT_BIN_STEP); + +// // Create router +// router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(0))); + +// // Label deployed contracts +// vm.label(address(factory), "factory"); +// vm.label(address(router), "router"); +// vm.label(address(LBPairImplementation), "LBPairImplementation"); +// } + +// function supportsInterface(bytes4 interfaceId) external view virtual returns (bool) { +// return interfaceId == type(ILBToken).interfaceId; +// } + +// function getPriceFromId(uint24 id) internal pure returns (uint256 price) { +// price = BinHelper.getPriceFromId(id, DEFAULT_BIN_STEP); +// } + +// function getIdFromPrice(uint256 price) internal pure returns (uint24 id) { +// id = BinHelper.getIdFromPrice(price, DEFAULT_BIN_STEP); +// } + +// function createLBPair(IERC20 tokenX, IERC20 tokenY) internal returns (LBPair newPair) { +// newPair = createLBPairFromStartId(tokenX, tokenY, ID_ONE); +// } + +// function setDefaultFactoryPresets(uint16 binStep) internal { +// factory.setPreset( +// binStep, +// DEFAULT_BASE_FACTOR, +// DEFAULT_FILTER_PERIOD, +// DEFAULT_DECAY_PERIOD, +// DEFAULT_REDUCTION_FACTOR, +// DEFAULT_VARIABLE_FEE_CONTROL, +// DEFAULT_PROTOCOL_SHARE, +// DEFAULT_MAX_VOLATILITY_ACCUMULATED, +// DEFAULT_SAMPLE_LIFETIME +// ); +// } + +// function createLBPairFromStartId(IERC20 tokenX, IERC20 tokenY, uint24 startId) internal returns (LBPair newPair) { +// newPair = createLBPairFromStartIdAndBinStep(tokenX, tokenY, startId, DEFAULT_BIN_STEP); +// } + +// function createLBPairFromStartIdAndBinStep(IERC20 tokenX, IERC20 tokenY, uint24 startId, uint16 binStep) +// internal +// returns (LBPair newPair) +// { +// newPair = LBPair(address(factory.createLBPair(tokenX, tokenY, startId, binStep))); +// } + +// function convertRelativeIdsToAbsolute(int256[] memory relativeIds, uint24 startId) +// internal +// pure +// returns (uint256[] memory absoluteIds) +// { +// absoluteIds = new uint256[](relativeIds.length); +// for (uint256 i = 0; i < relativeIds.length; i++) { +// int256 id = int256(uint256(startId)) + relativeIds[i]; +// require(id >= 0, "Id conversion: id must be positive"); +// absoluteIds[i] = uint256(id); +// } +// } + +// function convertAbsoluteIdsToRelative(uint256[] memory absoluteIds, uint24 startId) +// internal +// pure +// returns (int256[] memory relativeIds) +// { +// relativeIds = new int256[](absoluteIds.length); +// for (uint256 i = 0; i < absoluteIds.length; i++) { +// relativeIds[i] = int256(absoluteIds[i]) - int256(uint256(startId)); +// } +// } + +// function addLiquidityAndReturnAbsoluteIds( +// ERC20Mock tokenX, +// ERC20Mock tokenY, +// uint256 amountYIn, +// uint24 startId, +// uint24 numberBins, +// uint24 gap +// ) +// internal +// returns ( +// uint256[] memory ids, +// uint256[] memory distributionX, +// uint256[] memory distributionY, +// uint256 amountXIn +// ) +// { +// (ids, distributionX, distributionY, amountXIn) = +// addLiquidity(tokenX, tokenY, amountYIn, startId, numberBins, gap); +// } + +// function addLiquidityAndReturnRelativeIds( +// ERC20Mock tokenX, +// ERC20Mock tokenY, +// uint256 amountYIn, +// uint24 startId, +// uint24 numberBins, +// uint24 gap +// ) +// internal +// returns (int256[] memory ids, uint256[] memory distributionX, uint256[] memory distributionY, uint256 amountXIn) +// { +// uint256[] memory absoluteIds; +// (absoluteIds, distributionX, distributionY, amountXIn) = +// addLiquidity(tokenX, tokenY, amountYIn, startId, numberBins, gap); +// ids = convertAbsoluteIdsToRelative(absoluteIds, startId); +// } + +// function addLiquidity( +// ERC20Mock tokenX, +// ERC20Mock tokenY, +// uint256 amountYIn, +// uint24 startId, +// uint24 numberBins, +// uint24 gap +// ) +// internal +// returns ( +// uint256[] memory ids, +// uint256[] memory distributionX, +// uint256[] memory distributionY, +// uint256 amountXIn +// ) +// { +// (ids, distributionX, distributionY, amountXIn) = spreadLiquidity(amountYIn, startId, numberBins, gap); + +// tokenX.mint(address(pair), amountXIn); +// tokenY.mint(address(pair), amountYIn); + +// pair.mint(ids, distributionX, distributionY, DEV); +// } + +// function spreadLiquidity(uint256 amountYIn, uint24 startId, uint24 numberBins, uint24 gap) +// internal +// pure +// returns ( +// uint256[] memory ids, +// uint256[] memory distributionX, +// uint256[] memory distributionY, +// uint256 amountXIn +// ) +// { +// if (numberBins % 2 == 0) { +// revert("Pls put an uneven number of bins"); +// } + +// uint24 spread = numberBins / 2; +// ids = new uint256[](numberBins); + +// distributionX = new uint256[](numberBins); +// distributionY = new uint256[](numberBins); +// uint256 binDistribution = Constants.PRECISION / (spread + 1); +// uint256 binLiquidity = amountYIn / (spread + 1); + +// for (uint256 i; i < numberBins; i++) { +// ids[i] = startId - spread * (1 + gap) + i * (1 + gap); + +// if (i <= spread) { +// distributionY[i] = binDistribution; +// } +// if (i >= spread) { +// distributionX[i] = binDistribution; +// amountXIn += +// binLiquidity > 0 ? (binLiquidity * Constants.SCALE - 1) / getPriceFromId(uint24(ids[i])) + 1 : 0; +// } +// } +// } + +// function addAllAssetsToQuoteWhitelist() internal { +// if (address(wavax) != address(0)) factory.addQuoteAsset(wavax); +// if (address(usdc) != address(0)) factory.addQuoteAsset(usdc); +// if (address(usdt) != address(0)) factory.addQuoteAsset(usdt); +// if (address(wbtc) != address(0)) factory.addQuoteAsset(wbtc); +// if (address(weth) != address(0)) factory.addQuoteAsset(weth); +// if (address(link) != address(0)) factory.addQuoteAsset(link); +// if (address(bnb) != address(0)) factory.addQuoteAsset(bnb); +// if (address(taxToken) != address(0)) factory.addQuoteAsset(taxToken); +// } +// } diff --git a/test/mocks/FlashloanBorrower.sol b/test/mocks/FlashloanBorrower.sol index 64a2286a..49ada687 100644 --- a/test/mocks/FlashloanBorrower.sol +++ b/test/mocks/FlashloanBorrower.sol @@ -30,50 +30,53 @@ contract FlashBorrower is ILBFlashLoanCallback { _owner = msg.sender; _lender = lender_; - (_tokenX, _tokenY) = (lender_.tokenX(), lender_.tokenY()); + (_tokenX, _tokenY) = (lender_.getTokenX(), lender_.getTokenY()); } - function LBFlashLoanCallback(address, IERC20 token, uint256 amount, uint256 fee, bytes calldata data) - external - override - returns (bytes32) - { - if (msg.sender != address(_lender)) { - revert FlashBorrower__UntrustedLender(); - } - (Action action, bool isReentrant) = abi.decode(data, (Action, bool)); - if (isReentrant) { - _lender.flashLoan(this, token, amount, data); - } - if (action == Action.NORMAL) { - emit CalldataTransmitted(); - } - - token.transfer(address(_lender), amount + fee); - - return Constants.CALLBACK_SUCCESS; + function LBFlashLoanCallback( + address, + IERC20 tokenX, + IERC20 tokenY, + bytes32 amounts, + bytes32 totalFees, + bytes calldata data + ) external override returns (bytes32) { + // if (msg.sender != address(_lender)) { + // revert FlashBorrower__UntrustedLender(); + // } + // (Action action, bool isReentrant) = abi.decode(data, (Action, bool)); + // if (isReentrant) { + // _lender.flashLoan(this, token, amount, data); + // } + // if (action == Action.NORMAL) { + // emit CalldataTransmitted(); + // } + + // token.transfer(address(_lender), amount + fee); + + // return Constants.CALLBACK_SUCCESS; } /// @dev Initiate a flash loan function flashBorrow(uint256 amountXBorrowed, uint256 amountYBorrowed) public { bytes memory data = abi.encode(Action.NORMAL, false); - if (amountXBorrowed > 0) { - _lender.flashLoan(this, _tokenX, amountXBorrowed, data); - } - if (amountYBorrowed > 0) { - _lender.flashLoan(this, _tokenY, amountYBorrowed, data); - } + // if (amountXBorrowed > 0) { + // _lender.flashLoan(this, _tokenX, amountXBorrowed, data); + // } + // if (amountYBorrowed > 0) { + // _lender.flashLoan(this, _tokenY, amountYBorrowed, data); + // } } function flashBorrowWithReentrancy(uint256 amountXBorrowed, uint256 amountYBorrowed) public { bytes memory data = abi.encode(Action.NORMAL, true); - if (amountXBorrowed > 0) { - _lender.flashLoan(this, _tokenX, amountXBorrowed, data); - } - if (amountYBorrowed > 0) { - _lender.flashLoan(this, _tokenY, amountYBorrowed, data); - } + // if (amountXBorrowed > 0) { + // _lender.flashLoan(this, _tokenX, amountXBorrowed, data); + // } + // if (amountYBorrowed > 0) { + // _lender.flashLoan(this, _tokenY, amountYBorrowed, data); + // } } } From 456c8e6f513e4c11e64468f943c0c470bff801df Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 25 Jan 2023 17:19:00 +0100 Subject: [PATCH 03/47] pair v2.1 --- src/LBPair.sol | 1469 +++++++---------- src/LBToken.sol | 378 +++-- src/interfaces/ILBFlashLoanCallback.sol | 11 +- src/interfaces/ILBPair.sol | 267 ++- src/interfaces/ILBToken.sol | 32 +- src/interfaces/IPendingOwnable.sol | 15 +- src/interfaces/IWAVAX.sol | 6 +- src/libraries/AddressesHelper.sol | 58 + src/libraries/BinHelper.sol | 211 ++- src/libraries/BitMath.sol | 133 -- src/libraries/Clone.sol | 124 ++ src/libraries/Constants.sol | 12 +- src/libraries/FeeHelper.sol | 185 +-- src/libraries/ImmutableClone.sol | 252 +++ src/libraries/JoeLibrary.sol | 15 +- src/libraries/Math512Bits.sol | 232 --- src/libraries/OracleHelper.sol | 232 +++ src/libraries/PairParameterHelper.sol | 442 +++++ src/libraries/PendingOwnable.sol | 109 +- src/libraries/PriceHelper.sol | 69 + src/libraries/ReentrancyGuardUpgradeable.sol | 51 +- src/libraries/SafeCast.sol | 227 --- src/libraries/TokenHelper.sol | 117 +- src/libraries/math/BitMath.sol | 131 ++ .../math/LiquidityConfigurations.sol | 95 ++ src/libraries/math/PackedUint128Math.sol | 302 ++++ src/libraries/math/SafeCast.sol | 321 ++++ src/libraries/math/SampleMath.sol | 240 +++ src/libraries/math/TreeMath.sol | 230 +++ .../Uint128x128Math.sol} | 151 +- src/libraries/math/Uint256x256Math.sol | 242 +++ test/BitMath.t.sol | 77 + test/Faucet.t.sol | 17 +- test/LBToken.t.sol | 370 +++++ test/LiquidityConfigurations.t.sol | 63 + test/PackedUint128Math.t.sol | 193 +++ test/PriceHelper.t.sol | 12 + test/SafeCast.t.sol | 290 ++++ test/SampleMath.t.sol | 191 +++ test/TestImmutableClone.sol | 91 + test/TreeMath.t.sol | 82 + test/Uint128x128Math.t.sol | 39 + test/Uint256x256Math.t.sol | 224 +++ 43 files changed, 5726 insertions(+), 2282 deletions(-) create mode 100644 src/libraries/AddressesHelper.sol delete mode 100644 src/libraries/BitMath.sol create mode 100644 src/libraries/Clone.sol create mode 100644 src/libraries/ImmutableClone.sol delete mode 100644 src/libraries/Math512Bits.sol create mode 100644 src/libraries/OracleHelper.sol create mode 100644 src/libraries/PairParameterHelper.sol create mode 100644 src/libraries/PriceHelper.sol delete mode 100644 src/libraries/SafeCast.sol create mode 100644 src/libraries/math/BitMath.sol create mode 100644 src/libraries/math/LiquidityConfigurations.sol create mode 100644 src/libraries/math/PackedUint128Math.sol create mode 100644 src/libraries/math/SafeCast.sol create mode 100644 src/libraries/math/SampleMath.sol create mode 100644 src/libraries/math/TreeMath.sol rename src/libraries/{Math128x128.sol => math/Uint128x128Math.sol} (56%) create mode 100644 src/libraries/math/Uint256x256Math.sol create mode 100644 test/BitMath.t.sol create mode 100644 test/LBToken.t.sol create mode 100644 test/LiquidityConfigurations.t.sol create mode 100644 test/PackedUint128Math.t.sol create mode 100644 test/PriceHelper.t.sol create mode 100644 test/SafeCast.t.sol create mode 100644 test/SampleMath.t.sol create mode 100644 test/TestImmutableClone.sol create mode 100644 test/TreeMath.t.sol create mode 100644 test/Uint128x128Math.t.sol create mode 100644 test/Uint256x256Math.t.sol diff --git a/src/LBPair.sol b/src/LBPair.sol index fb5b5cbb..2acf3b0d 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -2,1107 +2,764 @@ pragma solidity 0.8.10; -/** - * Imports * - */ - -import "./LBErrors.sol"; -import "./LBToken.sol"; -import "./libraries/BinHelper.sol"; -import "./libraries/Constants.sol"; -import "./libraries/Decoder.sol"; -import "./libraries/FeeDistributionHelper.sol"; -import "./libraries/Math512Bits.sol"; -import "./libraries/Oracle.sol"; -import "./libraries/ReentrancyGuardUpgradeable.sol"; -import "./libraries/SafeCast.sol"; -import "./libraries/SafeMath.sol"; -import "./libraries/SwapHelper.sol"; -import "./libraries/TokenHelper.sol"; -import "./libraries/TreeMath.sol"; -import "./interfaces/ILBPair.sol"; - -/// @title Liquidity Book Pair -/// @author Trader Joe -/// @notice This contract is the implementation of Liquidity Book Pair that also acts as the receipt token for liquidity positions -contract LBPair is LBToken, ReentrancyGuardUpgradeable, ILBPair { - /** - * Libraries * - */ - - using Math512Bits for uint256; - using TreeMath for mapping(uint256 => uint256)[3]; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; + +import {BinHelper} from "./libraries/BinHelper.sol"; +import {Clone} from "./libraries/Clone.sol"; +import {Constants} from "./libraries/Constants.sol"; +import {LiquidityConfigurations} from "./libraries/math/LiquidityConfigurations.sol"; +import {ILBFactory} from "./interfaces/ILBFactory.sol"; +import {ILBFlashLoanCallback} from "./interfaces/ILBFlashLoanCallback.sol"; +import {ILBPair} from "./interfaces/ILBPair.sol"; +import {LBToken} from "./LBToken.sol"; +import {OracleHelper} from "./libraries/OracleHelper.sol"; +import {PackedUint128Math} from "./libraries/math/PackedUint128Math.sol"; +import {PairParameterHelper} from "./libraries/PairParameterHelper.sol"; +import {PriceHelper} from "./libraries/PriceHelper.sol"; +import {ReentrancyGuardUpgradeable} from "./libraries/ReentrancyGuardUpgradeable.sol"; +import {SafeCast} from "./libraries/math/SafeCast.sol"; +import {SampleMath} from "./libraries/math/SampleMath.sol"; +import {TreeMath} from "./libraries/math/TreeMath.sol"; +import {Uint256x256Math} from "./libraries/math/Uint256x256Math.sol"; + +contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { + using BinHelper for bytes32; + using LiquidityConfigurations for bytes32; + using OracleHelper for OracleHelper.Oracle; + using PackedUint128Math for bytes32; + using PackedUint128Math for uint128; + using PairParameterHelper for bytes32; + using PriceHelper for uint24; using SafeCast for uint256; - using SafeMath for uint256; - using TokenHelper for IERC20; - using FeeHelper for FeeHelper.FeeParameters; - using SwapHelper for Bin; - using Decoder for bytes32; - using FeeDistributionHelper for FeeHelper.FeesDistribution; - using Oracle for bytes32[65_535]; - - /** - * Modifiers * - */ + using TreeMath for TreeMath.TreeUint24; + using Uint256x256Math for uint256; - /// @notice Checks if the caller is the factory modifier onlyFactory() { - if (msg.sender != address(factory)) revert LBPair__OnlyFactory(); + if (msg.sender != address(_factory)) revert LBPair__OnlyFactory(); _; } - /** - * Public immutable variables * - */ + modifier onlyProtocolFeeReceiver() { + if (msg.sender != _factory.getProtocolFeeRecipient()) revert LBPair__OnlyProtocolFeeReceiver(); + _; + } - /// @notice The factory contract that created this pair - ILBFactory public immutable override factory; + ILBFactory private immutable _factory; - /** - * Public variables * - */ + bytes32 private _parameters; - /// @notice The token that is used as the base currency for the pair - IERC20 public override tokenX; + bytes32 private _reserves; + bytes32 private _protocolFees; - /// @notice The token that is used as the quote currency for the pair - IERC20 public override tokenY; + mapping(uint256 => bytes32) private _bins; + + TreeMath.TreeUint24 private _tree; + OracleHelper.Oracle private _oracle; /** - * Private variables * + * @dev Constructor for the Liquidity Book Pair contract that sets the Liquidity Book Factory + * @param factory_ The Liquidity Book Factory */ + constructor(ILBFactory factory_) { + _factory = factory_; + } - /// @dev The pair information that is used to track reserves, active ids, - /// fees and oracle parameters - PairInformation private _pairInformation; - - /// @dev The fee parameters that are used to calculate fees - FeeHelper.FeeParameters private _feeParameters; - - /// @dev The reserves of tokens for every bin. This is the amount - /// of tokenY if `id < _pairInformation.activeId`; of tokenX if `id > _pairInformation.activeId` - /// and a mix of both if `id == _pairInformation.activeId` - mapping(uint256 => Bin) private _bins; - - /// @dev Tree to find bins with non zero liquidity - - /// @dev The tree that is used to find the first bin with non zero liquidity - mapping(uint256 => uint256)[3] private _tree; - - /// @dev The mapping from account to user's unclaimed fees. The first 128 bits are tokenX and the last are for tokenY - mapping(address => bytes32) private _unclaimedFees; + /** + * @notice Initialize the Liquidity Book Pair fee parameters and active id + * @dev Can only be called by the Liquidity Book Factory + * @param baseFactor The base factor for the static fee + * @param filterPeriod The filter period for the static fee + * @param decayPeriod The decay period for the static fee + * @param reductionFactor The reduction factor for the static fee + * @param variableFeeControl The variable fee control for the static fee + * @param protocolShare The protocol share for the static fee + * @param maxVolatilityAccumulated The max volatility accumulated for the static fee + * @param activeId The active id of the Liquidity Book Pair + */ + function initialize( + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated, + uint24 activeId + ) external override onlyFactory { + bytes32 parameters = _parameters; + if (parameters != 0) revert LBPair__AlreadyInitialized(); - /// @dev The mapping from account to id to user's accruedDebt - mapping(address => mapping(uint256 => Debts)) private _accruedDebts; + __ReentrancyGuard_init(); - /// @dev The oracle samples that are used to calculate the time weighted average data - bytes32[65_535] private _oracle; + _setStaticFeeParameters( + parameters.setActiveId(activeId), + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated + ); + } /** - * OffSets + * @notice Returns the Liquidity Book Factory + * @return factory The Liquidity Book Factory */ - - uint256 private constant _OFFSET_PAIR_RESERVE_X = 24; - uint256 private constant _OFFSET_PROTOCOL_FEE = 128; - uint256 private constant _OFFSET_BIN_RESERVE_Y = 112; - uint256 private constant _OFFSET_VARIABLE_FEE_PARAMETERS = 144; - uint256 private constant _OFFSET_ORACLE_SAMPLE_LIFETIME = 136; - uint256 private constant _OFFSET_ORACLE_SIZE = 152; - uint256 private constant _OFFSET_ORACLE_ACTIVE_SIZE = 168; - uint256 private constant _OFFSET_ORACLE_LAST_TIMESTAMP = 184; - uint256 private constant _OFFSET_ORACLE_ID = 224; + function getFactory() external view override returns (ILBFactory factory) { + return _factory; + } /** - * Constructor * + * @notice Returns the token X of the Liquidity Book Pair + * @return tokenX The address of the token X */ - - /// @notice Set the factory address - /// @param _factory The address of the factory - constructor(ILBFactory _factory) LBToken() { - if (address(_factory) == address(0)) revert LBPair__AddressZero(); - factory = _factory; + function getTokenX() external pure override returns (IERC20 tokenX) { + return _tokenX(); } - /// @notice Initialize the parameters of the LBPair - /// @dev The different parameters needs to be validated very cautiously - /// It is highly recommended to never call this function directly, use the factory - /// as it validates the different parameters - /// @param _tokenX The address of the tokenX. Can't be address 0 - /// @param _tokenY The address of the tokenY. Can't be address 0 - /// @param _activeId The active id of the pair - /// @param _sampleLifetime The lifetime of a sample. It's the min time between 2 oracle's sample - /// @param _packedFeeParameters The fee parameters packed in a single 256 bits slot - function initialize( - IERC20 _tokenX, - IERC20 _tokenY, - uint24 _activeId, - uint16 _sampleLifetime, - bytes32 _packedFeeParameters - ) external override onlyFactory { - if (address(_tokenX) == address(0) || address(_tokenY) == address(0)) revert LBPair__AddressZero(); - if (address(tokenX) != address(0)) revert LBPair__AlreadyInitialized(); - - __ReentrancyGuard_init(); + /** + * @notice Returns the token Y of the Liquidity Book Pair + * @return tokenY The address of the token Y + */ + function getTokenY() external pure override returns (IERC20 tokenY) { + return _tokenY(); + } - tokenX = _tokenX; - tokenY = _tokenY; + /** + * @notice Returns the bin step of the Liquidity Book Pair + * @dev The bin step is the increase in price between two consecutive bins, in 20_000th. + * For example, a bin step of 1 means that the price of the next bin is 0.005% higher than the price of the previous bin. + * The maximum bin step is 200, which means that the price of the next bin is 1% higher than the price of the previous bin. + * @return binStep The bin step of the Liquidity Book Pair, in 20_000th + */ + function getBinStep() external pure override returns (uint8) { + return _binStep(); + } - _pairInformation.activeId = _activeId; - _pairInformation.oracleSampleLifetime = _sampleLifetime; + /** + * @notice Returns the reserves of the Liquidity Book Pair + * This is the sum of the reserves of all bins, minus the protocol fees. + * @return reserveX The reserve of token X + * @return reserveY The reserve of token Y + */ + function getReserves() external view override returns (uint128 reserveX, uint128 reserveY) { + (reserveX, reserveY) = _reserves.sub(_protocolFees).decode(); + } - _setFeesParameters(_packedFeeParameters); - _increaseOracle(2); + /** + * @notice Returns the active id of the Liquidity Book Pair + * @dev The active id is the id of the bin that is currently being used for swaps. + * The price of the active bin is the price of the Liquidity Book Pair and can be calculated as follows: + * `price = (1 + binStep / 20_000) ^ (activeId - 2^23)` + * @return activeId The active id of the Liquidity Book Pair + */ + function getActiveId() external view override returns (uint24 activeId) { + activeId = _parameters.getActiveId(); } /** - * External View Functions * + * @notice Returns the reserves of a bin + * @param id The id of the bin + * @return binReserveX The reserve of token X in the bin + * @return binReserveY The reserve of token Y in the bin */ + function getBin(uint24 id) external view override returns (uint128 binReserveX, uint128 binReserveY) { + (binReserveX, binReserveY) = _bins[id].decode(); + } - /// @notice View function to get the reserves and active id - /// @return reserveX The reserve of asset X - /// @return reserveY The reserve of asset Y - /// @return activeId The active id of the pair - function getReservesAndId() external view override returns (uint256 reserveX, uint256 reserveY, uint256 activeId) { - return _getReservesAndId(); + /** + * @notice Returns the next non-empty bin + * @dev The next non-empty bin is the bin with a higher (if swapForY is true) or lower (if swapForY is false) + * id that has a non-zero reserve of token X or Y. + * @param swapForY Whether the swap is for token Y (true) or token X (false + * @param id The id of the bin + * @return nextId The id of the next non-empty bin + */ + function getNextNonEmptyBin(bool swapForY, uint24 id) external view override returns (uint24 nextId) { + nextId = _getNextNonEmptyBin(swapForY, id); } - /// @notice View function to get the total fees and the protocol fees of each tokens - /// @return feesXTotal The total fees of tokenX - /// @return feesYTotal The total fees of tokenY - /// @return feesXProtocol The protocol fees of tokenX - /// @return feesYProtocol The protocol fees of tokenY - function getGlobalFees() - external - view - override - returns (uint128 feesXTotal, uint128 feesYTotal, uint128 feesXProtocol, uint128 feesYProtocol) - { - return _getGlobalFees(); + /** + * @notice Returns the protocol fees of the Liquidity Book Pair + * @return protocolFeeX The protocol fees of token X + * @return protocolFeeY The protocol fees of token Y + */ + function getProtocolFees() external view override returns (uint128 protocolFeeX, uint128 protocolFeeY) { + (protocolFeeX, protocolFeeY) = _protocolFees.decode(); } - /// @notice View function to get the oracle parameters - /// @return oracleSampleLifetime The lifetime of a sample, it accumulates information for up to this timestamp - /// @return oracleSize The size of the oracle (last ids can be empty) - /// @return oracleActiveSize The active size of the oracle (no empty data) - /// @return oracleLastTimestamp The timestamp of the creation of the oracle's latest sample - /// @return oracleId The index of the oracle's latest sample - /// @return min The min delta time of two samples - /// @return max The safe max delta time of two samples - function getOracleParameters() + /** + * @notice Returns the static fee parameters of the Liquidity Book Pair + * @return baseFactor The base factor for the static fee + * @return filterPeriod The filter period for the static fee + * @return decayPeriod The decay period for the static fee + * @return reductionFactor The reduction factor for the static fee + * @return variableFeeControl The variable fee control for the static fee + * @return protocolShare The protocol share for the static fee + * @return maxVolatilityAccumulated The maximum volatility accumulated for the static fee + */ + function getStaticFeeParameters() external view override returns ( - uint256 oracleSampleLifetime, - uint256 oracleSize, - uint256 oracleActiveSize, - uint256 oracleLastTimestamp, - uint256 oracleId, - uint256 min, - uint256 max + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated ) { - (oracleSampleLifetime, oracleSize, oracleActiveSize, oracleLastTimestamp, oracleId) = _getOracleParameters(); - min = oracleActiveSize == 0 ? 0 : oracleSampleLifetime; - max = oracleSampleLifetime * oracleActiveSize; + bytes32 parameters = _parameters; + + baseFactor = parameters.getBaseFactor(); + filterPeriod = parameters.getFilterPeriod(); + decayPeriod = parameters.getDecayPeriod(); + reductionFactor = parameters.getReductionFactor(); + variableFeeControl = parameters.getVariableFeeControl(); + protocolShare = parameters.getProtocolShare(); + maxVolatilityAccumulated = parameters.getMaxVolatilityAccumulated(); } - /// @notice View function to get the oracle's sample at `_timeDelta` seconds - /// @dev Return a linearized sample, the weighted average of 2 neighboring samples - /// @param _timeDelta The number of seconds before the current timestamp - /// @return cumulativeId The weighted average cumulative id - /// @return cumulativeVolatilityAccumulated The weighted average cumulative volatility accumulated - /// @return cumulativeBinCrossed The weighted average cumulative bin crossed - function getOracleSampleFrom(uint256 _timeDelta) + /** + * @notice Returns the variable fee parameters of the Liquidity Book Pair + * @return volatilityAccumulated The volatility accumulated for the variable fee + * @return volatilityReference The volatility reference for the variable fee + * @return idReference The id reference for the variable fee + * @return timeOfLastUpdate The time of last update for the variable fee + */ + function getVariableFeeParameters() external view override - returns (uint256 cumulativeId, uint256 cumulativeVolatilityAccumulated, uint256 cumulativeBinCrossed) + returns (uint24 volatilityAccumulated, uint24 volatilityReference, uint24 idReference, uint40 timeOfLastUpdate) { - uint256 _lookUpTimestamp = block.timestamp - _timeDelta; - - (,, uint256 _oracleActiveSize,, uint256 _oracleId) = _getOracleParameters(); - - uint256 timestamp; - (timestamp, cumulativeId, cumulativeVolatilityAccumulated, cumulativeBinCrossed) = - _oracle.getSampleAt(_oracleActiveSize, _oracleId, _lookUpTimestamp); - - if (timestamp < _lookUpTimestamp) { - FeeHelper.FeeParameters memory _fp = _feeParameters; - uint256 _activeId = _pairInformation.activeId; - _fp.updateVariableFeeParameters(_activeId); + bytes32 parameters = _parameters; - unchecked { - uint256 _deltaT = _lookUpTimestamp - timestamp; - - cumulativeId += _activeId * _deltaT; - cumulativeVolatilityAccumulated += uint256(_fp.volatilityAccumulated) * _deltaT; - } - } - } - - /// @notice View function to get the fee parameters - /// @return The fee parameters - function feeParameters() external view override returns (FeeHelper.FeeParameters memory) { - return _feeParameters; - } - - /// @notice View function to get the first bin that isn't empty, will not be `_id` itself - /// @param _id The bin id - /// @param _swapForY Whether you've swapping token X for token Y (true) or token Y for token X (false) - /// @return The id of the non empty bin - function findFirstNonEmptyBinId(uint24 _id, bool _swapForY) external view override returns (uint24) { - return _tree.findFirstBin(_id, _swapForY); + volatilityAccumulated = parameters.getVolatilityAccumulated(); + volatilityReference = parameters.getVolatilityReference(); + idReference = parameters.getIdReference(); + timeOfLastUpdate = parameters.getTimeOfLastUpdate(); } - /// @notice View function to get the bin at `id` - /// @param _id The bin id - /// @return reserveX The reserve of tokenX of the bin - /// @return reserveY The reserve of tokenY of the bin - function getBin(uint24 _id) external view override returns (uint256 reserveX, uint256 reserveY) { - return _getBin(_id); - } - - /// @notice View function to get the pending fees of a user - /// @dev The array must be strictly increasing to ensure uniqueness - /// @param _account The address of the user - /// @param _ids The list of ids - /// @return amountX The amount of tokenX pending - /// @return amountY The amount of tokenY pending - function pendingFees(address _account, uint256[] calldata _ids) + /** + * @notice Returns the cumulative values of the Liquidity Book Pair at a given timestamp + * @dev The cumulative values are the cumulative id, the cumulative volatility and the cumulative bin crossed. + * @param lookupTimestamp The timestamp at which to look up the cumulative values + * @return cumulativeId The cumulative id of the Liquidity Book Pair at the given timestamp + * @return cumulativeVolatility The cumulative volatility of the Liquidity Book Pair at the given timestamp + * @return cumulativeBinCrossed The cumulative bin crossed of the Liquidity Book Pair at the given timestamp + */ + function getOracleSampleAt(uint40 lookupTimestamp) external view override - returns (uint256 amountX, uint256 amountY) + returns (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) { - if (_account == address(this) || _account == address(0)) return (0, 0); - - bytes32 _unclaimedData = _unclaimedFees[_account]; - - amountX = _unclaimedData.decode(type(uint128).max, 0); - amountY = _unclaimedData.decode(type(uint128).max, 128); - - uint256 _lastId; - // Iterate over the ids to get the pending fees of the user for each bin - unchecked { - for (uint256 i; i < _ids.length; ++i) { - uint256 _id = _ids[i]; - - // Ensures uniqueness of ids - if (_lastId >= _id && i != 0) revert LBPair__OnlyStrictlyIncreasingId(); + bytes32 parameters = _parameters; - uint256 _balance = balanceOf(_account, _id); + if (lookupTimestamp > block.timestamp) return (0, 0, 0); - if (_balance != 0) { - Bin memory _bin = _bins[_id]; + uint40 timeOfLastUpdate; + (timeOfLastUpdate, cumulativeId, cumulativeVolatility, cumulativeBinCrossed) = + _oracle.getSampleAt(parameters.getOracleId(), lookupTimestamp); - (uint128 _amountX, uint128 _amountY) = _getPendingFees(_bin, _account, _id, _balance); + if (timeOfLastUpdate < lookupTimestamp) { + parameters.updateVolatilityParameters(parameters.getActiveId()); - amountX += _amountX; - amountY += _amountY; - } + uint40 deltaTime = lookupTimestamp - timeOfLastUpdate; - _lastId = _id; - } + cumulativeId += uint64(parameters.getIdReference()) * deltaTime; + cumulativeVolatility += uint64(parameters.getVolatilityAccumulated()) * deltaTime; } } - /// @notice Returns whether this contract implements the interface defined by - /// `interfaceId` (true) or not (false) - /// @param _interfaceId The interface identifier - /// @return Whether the interface is supported (true) or not (false) - function supportsInterface(bytes4 _interfaceId) public view override returns (bool) { - return super.supportsInterface(_interfaceId) || _interfaceId == type(ILBPair).interfaceId; - } - /** - * External Functions * + * @notice Swap tokens iterating over the bins until the entire amount is swapped. + * Token X will be swapped for token Y if `swapForY` is true, and token Y for token X if `swapForY` is false. + * This function will not transfer the tokens from the caller, it is expected that the tokens have already been + * transferred to this contract through another contract, most likely the router. + * That is why this function shouldn't be called directly, but only through one of the swap functions of a router + * that will also perform safety checks, such as minimum amounts and slippage. + * The variable fee is updated throughout the swap, it increases with the number of bins crossed. + * The oracle is updated at the end of the swap. + * @param swapForY Whether you're swapping token X for token Y (true) or token Y for token X (false) + * @param to The address to send the tokens to + * @return amountsOut The encoded amounts of token X and token Y sent to `to` */ + function swap(bool swapForY, address to) external override nonReentrant returns (bytes32 amountsOut) { + bytes32 reserves = _reserves; + bytes32 protocolFees = _protocolFees; - /// @notice Swap tokens iterating over the bins until the entire amount is swapped. - /// Will swap token X for token Y if `_swapForY` is true, and token Y for token X if `_swapForY` is false. - /// This function will not transfer the tokens from the caller, it is expected that the tokens have already been - /// transferred to this contract through another contract. - /// That is why this function shouldn't be called directly, but through one of the swap functions of the router - /// that will also perform safety checks. - /// - /// The variable fee is updated throughout the swap, it increases with the number of bins crossed. - /// @param _swapForY Whether you've swapping token X for token Y (true) or token Y for token X (false) - /// @param _to The address to send the tokens to - /// @return amountXOut The amount of token X sent to `_to` - /// @return amountYOut The amount of token Y sent to `_to` - function swap(bool _swapForY, address _to) - external - override - nonReentrant - returns (uint256 amountXOut, uint256 amountYOut) - { - PairInformation memory _pair = _pairInformation; + bytes32 amountsLeft = swapForY ? reserves.receivedX(_tokenX()) : reserves.receivedY(_tokenY()); - uint256 _amountIn = _swapForY - ? tokenX.received(_pair.reserveX, _pair.feesX.total) - : tokenY.received(_pair.reserveY, _pair.feesY.total); + if (amountsLeft == 0) revert LBPair__InsufficientAmountIn(); - if (_amountIn == 0) revert LBPair__InsufficientAmounts(); + bytes32 parameters = _parameters; + uint8 binStep = _binStep(); - FeeHelper.FeeParameters memory _fp = _feeParameters; + uint24 activeId = parameters.getActiveId(); - uint256 _startId = _pair.activeId; - _fp.updateVariableFeeParameters(_startId); + parameters = parameters.updateReferences(); - uint256 _amountOut; - /// Performs the actual swap, iterating over the bins until the entire amount is swapped. - /// It uses the tree to find the next bin to have a non zero reserve of the token we're swapping for. - /// It will also update the variable fee parameters. while (true) { - Bin memory _bin = _bins[_pair.activeId]; - if ((!_swapForY && _bin.reserveX != 0) || (_swapForY && _bin.reserveY != 0)) { - (uint256 _amountInToBin, uint256 _amountOutOfBin, FeeHelper.FeesDistribution memory _fees) = - _bin.getAmounts(_fp, _pair.activeId, _swapForY, _amountIn); - - _bin.updateFees(_swapForY ? _pair.feesX : _pair.feesY, _fees, _swapForY, totalSupply(_pair.activeId)); - - _bin.updateReserves(_pair, _swapForY, _amountInToBin.safe112(), _amountOutOfBin.safe112()); - - _amountIn -= _amountInToBin + _fees.total; - _amountOut += _amountOutOfBin; - - _bins[_pair.activeId] = _bin; - - // Avoids stack too deep error - _emitSwap( - _to, - _pair.activeId, - _swapForY, - _amountInToBin, - _amountOutOfBin, - _fp.volatilityAccumulated, - _fees.total - ); + bytes32 binReserves = _bins[activeId]; + if (!binReserves.isEmpty(swapForY)) { + parameters = parameters.updateVolatilityAccumulated(activeId); + + (bytes32 amountsInToBin, bytes32 amountsOutOfBin, bytes32 totalFees) = + binReserves.getAmounts(parameters, binStep, swapForY, activeId, amountsLeft); + + if (amountsInToBin > 0) { + amountsLeft = amountsLeft.sub(amountsInToBin); + reserves.add(amountsInToBin.add(totalFees)); + + amountsOut = amountsOut.add(amountsOutOfBin); + + bytes32 pFees = totalFees.scalarMulDivBasisPointRoundDown(parameters.getProtocolShare()); + protocolFees = protocolFees.add(pFees); + + _bins[activeId] = binReserves.add(amountsInToBin).sub(amountsOutOfBin); + + emit Swap( + msg.sender, + to, + activeId, + amountsInToBin, + amountsOutOfBin, + parameters.getVolatilityAccumulated(), + totalFees, + pFees + ); + } } - /// If the amount in is not 0, it means that we haven't swapped the entire amount yet. - /// We need to find the next bin to swap for. - if (_amountIn != 0) { - _pair.activeId = _tree.findFirstBin(_pair.activeId, _swapForY); - } else { + if (amountsLeft == 0) { break; - } - } - - // Update the oracle and return the updated oracle id. It uses the oracle size to start filling the new slots. - uint256 _updatedOracleId = _oracle.update( - _pair.oracleSize, - _pair.oracleSampleLifetime, - _pair.oracleLastTimestamp, - _pair.oracleId, - _pair.activeId, - _fp.volatilityAccumulated, - _startId.absSub(_pair.activeId) - ); + } else { + uint24 nextId = _getNextNonEmptyBin(swapForY, activeId); - // Update the oracleId and lastTimestamp if the sample write on another slot - if (_updatedOracleId != _pair.oracleId || _pair.oracleLastTimestamp == 0) { - // Can't overflow as the updatedOracleId < oracleSize - _pair.oracleId = uint16(_updatedOracleId); - _pair.oracleLastTimestamp = block.timestamp.safe40(); + if (nextId == 0 || nextId == type(uint24).max) revert LBPair__OutOfLiquidity(); - // Increase the activeSize if the updated sample is written in a new slot - // Can't overflow as _updatedOracleId < maxSize = 2**16-1 - unchecked { - if (_updatedOracleId == _pair.oracleActiveSize) ++_pair.oracleActiveSize; + activeId = nextId; } } - /// Update the fee parameters and the pair information - _feeParameters = _fp; - _pairInformation = _pair; + if (amountsOut == 0) revert LBPair__InsufficientAmountOut(); + + _oracle.update(parameters, activeId); - if (_swapForY) { - amountYOut = _amountOut; - tokenY.safeTransfer(_to, _amountOut); + _reserves = reserves.sub(amountsOut); + _parameters = parameters.setActiveId(activeId); + + if (swapForY) { + amountsOut.transferY(_tokenY(), to); } else { - amountXOut = _amountOut; - tokenX.safeTransfer(_to, _amountOut); + amountsOut.transferX(_tokenX(), to); } } - /// @notice Perform a flashloan on one of the tokens of the pair. The flashloan will call the `_receiver` contract - /// to perform the desired operations. The `_receiver` contract is expected to transfer the `amount + fee` of the - /// token to this contract. - /// @param _receiver The contract that will receive the flashloan and execute the callback - /// @param _token The address of the token to flashloan - /// @param _amount The amount of token to flashloan - /// @param _data The call data that will be forwarded to the `_receiver` contract during the callback - function flashLoan(ILBFlashLoanCallback _receiver, IERC20 _token, uint256 _amount, bytes calldata _data) + /** + * @notice Flash loan tokens from the pool to a receiver contract and execute a callback function. + * The receiver contract is expected to return the tokens plus a fee to this contract. + * The fee is calculated as a percentage of the amount borrowed, and is the same for both tokens. + * @param receiver The contract that will receive the tokens and execute the callback function + * @param amounts The encoded amounts of token X and token Y to flash loan + * @param data Any data that will be passed to the callback function + */ + function flashLoan(ILBFlashLoanCallback receiver, bytes32 amounts, bytes calldata data) external override nonReentrant { - IERC20 _tokenX = tokenX; - if ((_token != _tokenX && _token != tokenY)) revert LBPair__FlashLoanInvalidToken(); - - uint256 _totalFee = _getFlashLoanFee(_amount); + bytes32 reservesBefore = _reserves; + bytes32 parameters = _parameters; - FeeHelper.FeesDistribution memory _fees = FeeHelper.FeesDistribution({ - total: _totalFee.safe128(), - protocol: uint128((_totalFee * _feeParameters.protocolShare) / Constants.BASIS_POINT_MAX) - }); + bytes32 totalFees = _getFlashLoanFees(amounts); - uint256 _balanceBefore = _token.balanceOf(address(this)); - - _token.safeTransfer(address(_receiver), _amount); + amounts.transfer(_tokenX(), _tokenY(), address(receiver)); if ( - _receiver.LBFlashLoanCallback(msg.sender, _token, _amount, _fees.total, _data) != Constants.CALLBACK_SUCCESS - ) revert LBPair__FlashLoanCallbackFailed(); - - uint256 _balanceAfter = _token.balanceOf(address(this)); - - if (_balanceAfter != _balanceBefore + _fees.total) revert LBPair__FlashLoanInvalidBalance(); - - uint256 _activeId = _pairInformation.activeId; - uint256 _totalSupply = totalSupply(_activeId); - - if (_totalFee > 0) { - if (_token == _tokenX) { - (uint128 _feesXTotal,, uint128 _feesXProtocol,) = _getGlobalFees(); - - _setFees(_pairInformation.feesX, _feesXTotal + _fees.total, _feesXProtocol + _fees.protocol); - _bins[_activeId].accTokenXPerShare += _fees.getTokenPerShare(_totalSupply); - } else { - (, uint128 _feesYTotal,, uint128 _feesYProtocol) = _getGlobalFees(); - - _setFees(_pairInformation.feesY, _feesYTotal + _fees.total, _feesYProtocol + _fees.protocol); - _bins[_activeId].accTokenYPerShare += _fees.getTokenPerShare(_totalSupply); - } - } - - emit FlashLoan(msg.sender, _receiver, _token, _amount, _fees.total); - } - - /// @notice Mint new LB tokens for each bins where the user adds liquidity. - /// This function will not transfer the tokens from the caller, it is expected that the tokens have already been - /// transferred to this contract through another contract. - /// That is why this function shouldn't be called directly, but through one of the add liquidity functions of the - /// router that will also perform safety checks. - /// @dev Any excess amount of token will be sent to the `to` address. The lengths of the arrays must be the same. - /// @param _ids The ids of the bins where the liquidity will be added. It will mint LB tokens for each of these bins. - /// @param _distributionX The percentage of token X to add to each bin. The sum of all the values must not exceed 100%, - /// that is 1e18. - /// @param _distributionY The percentage of token Y to add to each bin. The sum of all the values must not exceed 100%, - /// that is 1e18. - /// @param _to The address that will receive the LB tokens and the excess amount of tokens. - /// @return The amount of token X added to the pair - /// @return The amount of token Y added to the pair - /// @return liquidityMinted The amounts of LB tokens minted for each bin - function mint( - uint256[] calldata _ids, - uint256[] calldata _distributionX, - uint256[] calldata _distributionY, - address _to - ) external override nonReentrant returns (uint256, uint256, uint256[] memory liquidityMinted) { - if (_ids.length == 0 || _ids.length != _distributionX.length || _ids.length != _distributionY.length) { - revert LBPair__WrongLengths(); + receiver.LBFlashLoanCallback(msg.sender, _tokenX(), _tokenY(), amounts, totalFees, data) + != Constants.CALLBACK_SUCCESS + ) { + revert LBPair__FlashLoanCallbackFailed(); } - PairInformation memory _pair = _pairInformation; - - FeeHelper.FeeParameters memory _fp = _feeParameters; - - MintInfo memory _mintInfo; + bytes32 balancesAfter = bytes32(0).received(_tokenX(), _tokenY()); - _mintInfo.amountXIn = tokenX.received(_pair.reserveX, _pair.feesX.total).safe112(); - _mintInfo.amountYIn = tokenY.received(_pair.reserveY, _pair.feesY.total).safe112(); + if (balancesAfter.lt(reservesBefore.add(totalFees))) revert LBPair__FlashLoanInsufficientAmount(); - liquidityMinted = new uint256[](_ids.length); + totalFees = reservesBefore.sub(balancesAfter); - // Iterate over the ids to calculate the amount of LB tokens to mint for each bin - for (uint256 i; i < _ids.length;) { - _mintInfo.id = _ids[i].safe24(); - Bin memory _bin = _bins[_mintInfo.id]; + bytes32 protocolFees = totalFees.scalarMulDivBasisPointRoundDown(parameters.getProtocolShare()); + uint24 activeId = parameters.getActiveId(); - if (_bin.reserveX == 0 && _bin.reserveY == 0) _tree.addToTree(_mintInfo.id); + _reserves = balancesAfter; - _mintInfo.totalDistributionX += _distributionX[i]; - _mintInfo.totalDistributionY += _distributionY[i]; + _protocolFees = _protocolFees.add(protocolFees); + _bins[activeId] = _bins[activeId].add(totalFees.sub(protocolFees)); - // Can't overflow as amounts are uint112 and total distributions will be checked to be smaller or equal than 1e18 - unchecked { - _mintInfo.amountX = (_mintInfo.amountXIn * _distributionX[i]) / Constants.PRECISION; - _mintInfo.amountY = (_mintInfo.amountYIn * _distributionY[i]) / Constants.PRECISION; - } - - uint256 _price = BinHelper.getPriceFromId(_mintInfo.id, _fp.binStep); - if (_mintInfo.id >= _pair.activeId) { - // The active bin is the only bin that can have a non-zero reserve of the two tokens. When adding liquidity - // with a different ratio than the active bin, the user would actually perform a swap without paying any - // fees. This is why we calculate the fees for the active bin here. - if (_mintInfo.id == _pair.activeId) { - if (_bin.reserveX != 0 || _bin.reserveY != 0) { - uint256 _totalSupply = totalSupply(_mintInfo.id); - - uint256 _receivedX; - uint256 _receivedY; - - { - uint256 _userL = - _price.mulShiftRoundDown(_mintInfo.amountX, Constants.SCALE_OFFSET) + _mintInfo.amountY; - - uint256 _supply = _totalSupply + _userL; - - // Calculate the amounts received by the user if he were to burn its liquidity directly after adding - // it. These amounts will be used to calculate the fees. - _receivedX = _userL.mulDivRoundDown(uint256(_bin.reserveX) + _mintInfo.amountX, _supply); - _receivedY = _userL.mulDivRoundDown(uint256(_bin.reserveY) + _mintInfo.amountY, _supply); - } - - _fp.updateVariableFeeParameters(_mintInfo.id); - - FeeHelper.FeesDistribution memory _fees; - - // Checks if the amount of tokens received after burning its liquidity is greater than the amount of - // tokens sent by the user. If it is, we add a composition fee of the difference between the two amounts. - if (_mintInfo.amountX > _receivedX) { - unchecked { - _fees = - _fp.getFeeAmountDistribution(_fp.getFeeAmountForC(_mintInfo.amountX - _receivedX)); - } - - _mintInfo.amountX -= _fees.total; - _mintInfo.activeFeeX += _fees.total; - - _bin.updateFees(_pair.feesX, _fees, true, _totalSupply); - } - if (_mintInfo.amountY > _receivedY) { - unchecked { - _fees = - _fp.getFeeAmountDistribution(_fp.getFeeAmountForC(_mintInfo.amountY - _receivedY)); - } - - _mintInfo.amountY -= _fees.total; - _mintInfo.activeFeeY += _fees.total; - - _bin.updateFees(_pair.feesY, _fees, false, _totalSupply); - } - - if (_mintInfo.activeFeeX > 0 || _mintInfo.activeFeeY > 0) { - emit CompositionFee( - msg.sender, _to, _mintInfo.id, _mintInfo.activeFeeX, _mintInfo.activeFeeY - ); - } - } - } else if (_mintInfo.amountY != 0) { - revert LBPair__CompositionFactorFlawed(_mintInfo.id); - } - } else if (_mintInfo.amountX != 0) { - revert LBPair__CompositionFactorFlawed(_mintInfo.id); - } + emit FlashLoan(msg.sender, receiver, activeId, amounts, totalFees, protocolFees); + } - // Calculate the amount of LB tokens to mint for this bin - uint256 _liquidity = _price.mulShiftRoundDown(_mintInfo.amountX, Constants.SCALE_OFFSET) + _mintInfo.amountY; + /** + * @notice Mint liquidity tokens by depositing tokens into the pool. + * It will mint Liquidity Book (LB) tokens for each bin where the user adds liquidity. + * This function will not transfer the tokens from the caller, it is expected that the tokens have already been + * transferred to this contract through another contract, most likely the router. + * That is why this function shouldn't be called directly, but through one of the add liquidity functions of a + * router that will also perform safety checks. + * @dev Any excess amount of token will be sent to the `to` address. + * @param to The address that will receive the LB tokens + * @param liquidityConfigs The encoded liquidity configurations, each one containing the id of the bin and the + * @param refundTo The address that will receive the excess amount of tokens + * percentage of token X and token Y to add to the bin. + * @return amountsReceived The amounts of token X and token Y received by the pool + * @return amountsLeft The amounts of token X and token Y that were not added to the pool and were sent to `to` + * @return liquidityMinted The amounts of LB tokens minted for each bin + */ + function mint(address to, bytes32[] calldata liquidityConfigs, address refundTo) + external + override + nonReentrant + returns (bytes32 amountsReceived, bytes32 amountsLeft, uint256[] memory liquidityMinted) + { + if (liquidityConfigs.length == 0) revert LBPair__EmptyMarketConfigs(); - if (_liquidity == 0) revert LBPair__InsufficientLiquidityMinted(_mintInfo.id); + liquidityMinted = new uint256[](liquidityConfigs.length); - liquidityMinted[i] = _liquidity; + uint256[] memory ids = new uint256[](liquidityConfigs.length); + bytes32[] memory amounts = new bytes32[](liquidityConfigs.length); - // Cast can't overflow as amounts are smaller than amountsIn as totalDistribution will be checked to be smaller than 1e18 - _bin.reserveX += uint112(_mintInfo.amountX); - _bin.reserveY += uint112(_mintInfo.amountY); + bytes32 reserves = _reserves; - // The addition or the cast can't overflow as it would have reverted during the previous 2 lines if - // amounts were greater than uint112 - unchecked { - _pair.reserveX += uint112(_mintInfo.amountX); - _pair.reserveY += uint112(_mintInfo.amountY); + bytes32 parameters = _parameters; + uint24 activeId = parameters.getActiveId(); - _mintInfo.amountXAddedToPair += _mintInfo.amountX; - _mintInfo.amountYAddedToPair += _mintInfo.amountY; - } + amountsReceived = reserves.received(_tokenX(), _tokenY()); + amountsLeft = amountsReceived; - _bins[_mintInfo.id] = _bin; + for (uint256 i; i < liquidityConfigs.length;) { + (bytes32 maxAmountsInToBin, uint24 id) = liquidityConfigs[i].getAmountsAndId(amountsReceived); + (uint256 shares, bytes32 amountsIn, bytes32 amountsInToBin) = + _mintBin(activeId, id, maxAmountsInToBin, parameters); - _mint(_to, _mintInfo.id, _liquidity); + amountsLeft = amountsLeft.sub(amountsIn); - emit DepositedToBin(msg.sender, _to, _mintInfo.id, _mintInfo.amountX, _mintInfo.amountY); + ids[i] = id; + amounts[i] = amountsInToBin; + liquidityMinted[i] = shares; unchecked { ++i; } } - // Assert that the distributions don't exceed 100% - if (_mintInfo.totalDistributionX > Constants.PRECISION || _mintInfo.totalDistributionY > Constants.PRECISION) { - revert LBPair__DistributionsOverflow(); - } + _reserves = reserves.add(amountsReceived.sub(amountsLeft)); - _pairInformation = _pair; + _mintBatch(to, ids, liquidityMinted); - // Send back the excess of tokens to `_to` - unchecked { - uint256 _amountXAddedPlusFee = _mintInfo.amountXAddedToPair + _mintInfo.activeFeeX; - if (_mintInfo.amountXIn > _amountXAddedPlusFee) { - tokenX.safeTransfer(_to, _mintInfo.amountXIn - _amountXAddedPlusFee); - } + if (amountsLeft > 0) amountsLeft.transfer(_tokenX(), _tokenY(), refundTo); - uint256 _amountYAddedPlusFee = _mintInfo.amountYAddedToPair + _mintInfo.activeFeeY; - if (_mintInfo.amountYIn > _amountYAddedPlusFee) { - tokenY.safeTransfer(_to, _mintInfo.amountYIn - _amountYAddedPlusFee); - } - } - - return (_mintInfo.amountXAddedToPair, _mintInfo.amountYAddedToPair, liquidityMinted); + emit DepositedToBins(msg.sender, to, ids, amounts); } - /// @notice Burns LB tokens and sends the corresponding amounts of tokens to `_to`. The amount of tokens sent is - /// determined by the ratio of the amount of LB tokens burned to the total supply of LB tokens in the bin. - /// This function will not transfer the LB Tokens from the caller, it is expected that the tokens have already been - /// transferred to this contract through another contract. - /// That is why this function shouldn't be called directly, but through one of the remove liquidity functions of the router - /// that will also perform safety checks. - /// @param _ids The ids of the bins from which to remove liquidity - /// @param _amounts The amounts of LB tokens to burn - /// @param _to The address that will receive the tokens - /// @return amountX The amount of token X sent to `_to` - /// @return amountY The amount of token Y sent to `_to` - function burn(uint256[] calldata _ids, uint256[] calldata _amounts, address _to) + /** + * @notice Burn Liquidity Book (LB) tokens and withdraw tokens from the pool. + * This function will burn the tokens directly from the caller + * @param from The address that will burn the LB tokens + * @param to The address that will receive the tokens + * @param ids The ids of the bins from which to withdraw + * @param amountsToBurn The amounts of LB tokens to burn for each bin + * @return amounts The amounts of token X and token Y received by the user + */ + function burn(address from, address to, uint256[] calldata ids, uint256[] calldata amountsToBurn) external override nonReentrant - returns (uint256 amountX, uint256 amountY) + checkApproval(from, msg.sender) + returns (bytes32[] memory amounts) { - if (_ids.length == 0 || _ids.length != _amounts.length) revert LBPair__WrongLengths(); + if (ids.length == 0 || ids.length != amountsToBurn.length) revert LBPair__InvalidInput(); - (uint256 _pairReserveX, uint256 _pairReserveY, uint256 _activeId) = _getReservesAndId(); + amounts = new bytes32[](ids.length); - // Iterate over the ids to burn the LB tokens - unchecked { - for (uint256 i; i < _ids.length; ++i) { - uint24 _id = _ids[i].safe24(); - uint256 _amountToBurn = _amounts[i]; - - if (_amountToBurn == 0) revert LBPair__InsufficientLiquidityBurned(_id); - - (uint256 _reserveX, uint256 _reserveY) = _getBin(_id); - - uint256 _totalSupply = totalSupply(_id); + bytes32 amountsOut; - uint256 _amountX; - uint256 _amountY; + for (uint256 i; i < ids.length;) { + uint24 id = ids[i].safe24(); + uint256 amountToBurn = amountsToBurn[i]; - if (_id <= _activeId) { - _amountY = _amountToBurn.mulDivRoundDown(_reserveY, _totalSupply); + if (amountToBurn == 0) revert LBPair__ZeroAmount(id); - amountY += _amountY; - _reserveY -= _amountY; - _pairReserveY -= _amountY; - } - if (_id >= _activeId) { - _amountX = _amountToBurn.mulDivRoundDown(_reserveX, _totalSupply); + bytes32 binReserves = _bins[id]; - amountX += _amountX; - _reserveX -= _amountX; - _pairReserveX -= _amountX; - } + bytes32 amountsOutFromBin = binReserves.getAmountOutOfBin(amountToBurn, totalSupply(id)); - if (_reserveX == 0 && _reserveY == 0) _tree.removeFromTree(_id); + if (amountsOutFromBin == 0) revert LBPair__ZeroAmountsOut(id); - // Optimized `_bins[_id] = _bin` to do only 1 sstore - assembly { - mstore(0, _id) - mstore(32, _bins.slot) - let slot := keccak256(0, 64) + binReserves = binReserves.sub(amountsOutFromBin); - let reserves := add(shl(_OFFSET_BIN_RESERVE_Y, _reserveY), _reserveX) - sstore(slot, reserves) - } + if (binReserves == 0) _tree.remove(id); - _burn(address(this), _id, _amountToBurn); + _bins[id] = binReserves; + amountsOut = amountsOut.add(amountsOutFromBin); - emit WithdrawnFromBin(msg.sender, _to, _id, _amountX, _amountY); + unchecked { + ++i; } } - // Optimization to do only 2 sstore - _pairInformation.reserveX = uint136(_pairReserveX); - _pairInformation.reserveY = uint136(_pairReserveY); + _reserves = _reserves.sub(amountsOut); - tokenX.safeTransfer(_to, amountX); - tokenY.safeTransfer(_to, amountY); - } + _burnBatch(from, ids, amountsToBurn); - /// @notice Increases the length of the oracle to the given `_newLength` by adding empty samples to the end of the oracle. - /// The samples are however initialized to reduce the gas cost of the updates during a swap. - /// @param _newLength The new length of the oracle - function increaseOracleLength(uint16 _newLength) external override { - _increaseOracle(_newLength); + amountsOut.transfer(_tokenX(), _tokenY(), to); + + emit WithdrawnFromBins(msg.sender, to, ids, amounts); } - /// @notice Collect the fees accumulated by a user. - /// @param _account The address of the user - /// @param _ids The ids of the bins for which to collect the fees - /// @return amountX The amount of token X collected and sent to `_account` - /// @return amountY The amount of token Y collected and sent to `_account` - function collectFees(address _account, uint256[] calldata _ids) + /** + * @notice Collect the protocol fees from the pool. + * @return collectedProtocolFees The amount of protocol fees collected + */ + function collectProtocolFees() external override nonReentrant - returns (uint256 amountX, uint256 amountY) + onlyProtocolFeeReceiver + returns (bytes32 collectedProtocolFees) { - if (_account == address(0) || _account == address(this)) revert LBPair__AddressZeroOrThis(); - - bytes32 _unclaimedData = _unclaimedFees[_account]; - delete _unclaimedFees[_account]; - - amountX = _unclaimedData.decode(type(uint128).max, 0); - amountY = _unclaimedData.decode(type(uint128).max, 128); + bytes32 protocolFees = _protocolFees; - // Iterate over the ids to collect the fees - for (uint256 i; i < _ids.length;) { - uint256 _id = _ids[i]; - uint256 _balance = balanceOf(_account, _id); + (uint128 x, uint128 y) = protocolFees.decode(); + bytes32 ones = uint128(x > 1 ? 1 : 0).encode(uint128(y > 1 ? 1 : 0)); - if (_balance != 0) { - Bin memory _bin = _bins[_id]; + collectedProtocolFees = protocolFees.sub(ones); - (uint256 _amountX, uint256 _amountY) = _getPendingFees(_bin, _account, _id, _balance); - _updateUserDebts(_bin, _account, _id, _balance); + if (collectedProtocolFees != 0) { + _protocolFees = ones; + _reserves.sub(collectedProtocolFees); - amountX += _amountX; - amountY += _amountY; - } - - unchecked { - ++i; - } - } - - if (amountX != 0) { - _pairInformation.feesX.total -= uint128(amountX); - } - if (amountY != 0) { - _pairInformation.feesY.total -= uint128(amountY); - } - - tokenX.safeTransfer(_account, amountX); - tokenY.safeTransfer(_account, amountY); - - emit FeesCollected(msg.sender, _account, amountX, amountY); - } - - /// @notice Collect the protocol fees and send them to the fee recipient. - /// @dev The protocol fees are not set to zero to save gas by not resetting the storage slot. - /// @return amountX The amount of token X collected and sent to the fee recipient - /// @return amountY The amount of token Y collected and sent to the fee recipient - function collectProtocolFees() external override nonReentrant returns (uint128 amountX, uint128 amountY) { - address _feeRecipient = factory.feeRecipient(); - - if (msg.sender != _feeRecipient) revert LBPair__OnlyFeeRecipient(_feeRecipient, msg.sender); - - (uint128 _feesXTotal, uint128 _feesYTotal, uint128 _feesXProtocol, uint128 _feesYProtocol) = _getGlobalFees(); + collectedProtocolFees.transfer(_tokenX(), _tokenY(), msg.sender); - // The protocol fees are not set to 0 to reduce the gas cost during a swap - if (_feesXProtocol > 1) { - amountX = _feesXProtocol - 1; - _feesXTotal -= amountX; - - _setFees(_pairInformation.feesX, _feesXTotal, 1); - - tokenX.safeTransfer(_feeRecipient, amountX); - } - - if (_feesYProtocol > 1) { - amountY = _feesYProtocol - 1; - _feesYTotal -= amountY; - - _setFees(_pairInformation.feesY, _feesYTotal, 1); - - tokenY.safeTransfer(_feeRecipient, amountY); + emit CollectedProtocolFees(msg.sender, collectedProtocolFees); } - - emit ProtocolFeesCollected(msg.sender, _feeRecipient, amountX, amountY); } - /// @notice Set the fees parameters - /// @dev Needs to be called by the factory that will validate the values - /// The bin step will not change - /// Only callable by the factory - /// @param _packedFeeParameters The packed fee parameters - function setFeesParameters(bytes32 _packedFeeParameters) external override onlyFactory { - _setFeesParameters(_packedFeeParameters); + /** + * @notice Increase the length of the oracle used by the pool + * @param newLength The new length of the oracle + */ + function increaseOracleLength(uint16 newLength) external override { + uint16 oracleId = _parameters.getOracleId(); + _oracle.inreaseLength(oracleId, newLength); } - /// @notice Force the decaying of the references for volatility and index - /// @dev Only callable by the factory - function forceDecay() external override onlyFactory { - _feeParameters.volatilityReference = uint24( - (uint256(_feeParameters.reductionFactor) * _feeParameters.volatilityReference) / Constants.BASIS_POINT_MAX + /** + * @notice Sets the static fee parameters of the pool + * @dev Can only be called by the factory + * @param baseFactor The base factor of the static fee + * @param filterPeriod The filter period of the static fee + * @param decayPeriod The decay period of the static fee + * @param reductionFactor The reduction factor of the static fee + * @param variableFeeControl The variable fee control of the static fee + * @param protocolShare The protocol share of the static fee + * @param maxVolatilityAccumulated The max volatility accumulated of the static fee + */ + function setStaticFeeParameters( + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated + ) external override onlyFactory { + _setStaticFeeParameters( + _parameters, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated ); - _feeParameters.indexRef = _pairInformation.activeId; } /** - * Internal Functions * + * @notice Forces the decay of the volatility reference variables + * @dev Can only be called by the factory */ + function forceDecay() external override onlyFactory { + bytes32 parameters = _parameters; - /// @notice Cache the accrued fees for a user before any transfer, mint or burn of LB tokens. - /// The tokens are not transferred to reduce the gas cost and to avoid reentrancy. - /// @param _from The address of the sender of the tokens - /// @param _to The address of the receiver of the tokens - /// @param _id The id of the bin - /// @param _amount The amount of LB tokens transferred - function _beforeTokenTransfer(address _from, address _to, uint256 _id, uint256 _amount) - internal - override(LBToken) - { - super._beforeTokenTransfer(_from, _to, _id, _amount); - - if (_from != _to) { - Bin memory _bin = _bins[_id]; - if (_from != address(0) && _from != address(this)) { - uint256 _balanceFrom = balanceOf(_from, _id); - - _cacheFees(_bin, _from, _id, _balanceFrom, _balanceFrom - _amount); - } - - if (_to != address(0) && _to != address(this)) { - uint256 _balanceTo = balanceOf(_to, _id); + _parameters = parameters.updateIdReference().updateVolatilityReference(); - _cacheFees(_bin, _to, _id, _balanceTo, _balanceTo + _amount); - } - } + emit ForcedDecay(msg.sender, parameters.getIdReference(), parameters.getVolatilityReference()); } /** - * Private Functions * + * @dev Returns the address of the token X + * @return The address of the token X */ - - /// @notice View function to get the pending fees of an account on a given bin - /// @param _bin The bin data where the user is collecting fees - /// @param _account The address of the user - /// @param _id The id where the user is collecting fees - /// @param _balance The previous balance of the user - /// @return amountX The amount of token X not collected yet by `_account` - /// @return amountY The amount of token Y not collected yet by `_account` - function _getPendingFees(Bin memory _bin, address _account, uint256 _id, uint256 _balance) - private - view - returns (uint128 amountX, uint128 amountY) - { - Debts memory _debts = _accruedDebts[_account][_id]; - - amountX = (_bin.accTokenXPerShare.mulShiftRoundDown(_balance, Constants.SCALE_OFFSET) - _debts.debtX).safe128(); - amountY = (_bin.accTokenYPerShare.mulShiftRoundDown(_balance, Constants.SCALE_OFFSET) - _debts.debtY).safe128(); + function _tokenX() internal pure returns (IERC20) { + return IERC20(_getArgAddress(0)); } - /// @notice Update the user debts of a user on a given bin - /// @param _bin The bin data where the user has collected fees - /// @param _account The address of the user - /// @param _id The id where the user has collected fees - /// @param _balance The new balance of the user - function _updateUserDebts(Bin memory _bin, address _account, uint256 _id, uint256 _balance) private { - uint256 _debtX = _bin.accTokenXPerShare.mulShiftRoundDown(_balance, Constants.SCALE_OFFSET); - uint256 _debtY = _bin.accTokenYPerShare.mulShiftRoundDown(_balance, Constants.SCALE_OFFSET); - - _accruedDebts[_account][_id].debtX = _debtX; - _accruedDebts[_account][_id].debtY = _debtY; + /** + * @dev Returns the address of the token Y + * @return The address of the token Y + */ + function _tokenY() internal pure returns (IERC20) { + return IERC20(_getArgAddress(20)); } - /// @notice Cache the accrued fees for a user. - /// @param _bin The bin data where the user is receiving LB tokens - /// @param _user The address of the user - /// @param _id The id where the user is receiving LB tokens - /// @param _previousBalance The previous balance of the user - /// @param _newBalance The new balance of the user - function _cacheFees(Bin memory _bin, address _user, uint256 _id, uint256 _previousBalance, uint256 _newBalance) - private - { - bytes32 _unclaimedData = _unclaimedFees[_user]; + /** + * @dev Returns the bin step of the pool, in 20_000ths. + * @return The bin step of the pool + */ + function _binStep() internal pure returns (uint8) { + return _getArgUint8(40); + } - uint128 amountX = uint128(_unclaimedData.decode(type(uint128).max, 0)); - uint128 amountY = uint128(_unclaimedData.decode(type(uint128).max, 128)); + /** + * @dev Returns next non-empty bin + * @param swapForY Whether the swap is for Y + * @param id The id of the bin + * @return The id of the next non-empty bin + */ + function _getNextNonEmptyBin(bool swapForY, uint24 id) internal view returns (uint24) { + return swapForY ? _tree.findFirstRight(id) : _tree.findFirstLeft(id); + } - (uint128 _amountX, uint128 _amountY) = _getPendingFees(_bin, _user, _id, _previousBalance); - _updateUserDebts(_bin, _user, _id, _newBalance); + /** + * @dev Returns the encoded fees amounts for a flash loan + * @param amounts The amounts of the flash loan + * @return The encoded fees amounts + */ + function _getFlashLoanFees(bytes32 amounts) private view returns (bytes32) { + uint128 fee = _factory.getFlashLoanFee(); + (uint128 x, uint128 y) = amounts.decode(); - amountX += _amountX; - amountY += _amountY; + unchecked { + uint256 precisionSubOne = Constants.PRECISION - 1; + x = ((uint256(x) * fee + precisionSubOne) / Constants.PRECISION).safe128(); + y = ((uint256(y) * fee + precisionSubOne) / Constants.PRECISION).safe128(); + } - _unclaimedFees[_user] = bytes32(uint256((uint256(amountY) << 128) | amountX)); + return x.encode(y); } - /// @notice Set the fee parameters of the pair. - /// @dev Only the first 112 bits can be set, as the last 144 bits are reserved for the variables parameters - /// @param _packedFeeParameters The packed fee parameters - function _setFeesParameters(bytes32 _packedFeeParameters) private { - bytes32 _feeStorageSlot; - assembly { - _feeStorageSlot := sload(_feeParameters.slot) + /** + * @dev Sets the static fee parameters of the pair + * @param parameters The current parameters of the pair + * @param baseFactor The base factor of the static fee + * @param filterPeriod The filter period of the static fee + * @param decayPeriod The decay period of the static fee + * @param reductionFactor The reduction factor of the static fee + * @param variableFeeControl The variable fee control of the static fee + * @param protocolShare The protocol share of the static fee + * @param maxVolatilityAccumulated The max volatility accumulated of the static fee + */ + function _setStaticFeeParameters( + bytes32 parameters, + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated + ) internal { + if ( + baseFactor == 0 && filterPeriod == 0 && decayPeriod == 0 && reductionFactor == 0 && variableFeeControl == 0 + && protocolShare == 0 && maxVolatilityAccumulated == 0 + ) { + revert LBPair__InvalidStaticFeeParameters(); } - uint256 _varParameters = _feeStorageSlot.decode(type(uint112).max, _OFFSET_VARIABLE_FEE_PARAMETERS); - uint256 _newFeeParameters = _packedFeeParameters.decode(type(uint144).max, 0); + _parameters = parameters.setStaticFeeParameters( + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated + ); - assembly { - sstore(_feeParameters.slot, or(_newFeeParameters, shl(_OFFSET_VARIABLE_FEE_PARAMETERS, _varParameters))) - } + emit StaticFeeParametersSet( + msg.sender, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated + ); } - /// @notice Increases the length of the oracle to the given `_newSize` by adding empty samples to the end of the oracle. - /// The samples are however initialized to reduce the gas cost of the updates during a swap. - /// @param _newSize The new size of the oracle. Needs to be bigger than current one - function _increaseOracle(uint16 _newSize) private { - uint256 _oracleSize = _pairInformation.oracleSize; - - if (_oracleSize >= _newSize) revert LBPair__OracleNewSizeTooSmall(_newSize, _oracleSize); + /** + * @dev Helper function to mint liquidity in a bin + * @param activeId The id of the active bin + * @param id The id of the bin + * @param maxAmountsInToBin The maximum amounts in to the bin + * @param parameters The parameters of the pair + * @return shares The amount of shares minted + * @return amountsIn The amounts in + * @return amountsInToBin The amounts in to the bin + */ + function _mintBin(uint24 activeId, uint24 id, bytes32 maxAmountsInToBin, bytes32 parameters) + internal + returns (uint256 shares, bytes32 amountsIn, bytes32 amountsInToBin) + { + bytes32 binReserves = _bins[id]; + uint8 binStep = _binStep(); - _pairInformation.oracleSize = _newSize; + uint256 price = id.getPriceFromId(binStep); + uint256 supply = totalSupply(id); - // Iterate over the uninitialized oracle samples and initialize them - for (uint256 _id = _oracleSize; _id < _newSize;) { - _oracle.initialize(_id); + (shares, amountsIn) = binReserves.getShareAndEffectiveAmountsIn(maxAmountsInToBin, price, supply); + amountsInToBin = amountsIn; - unchecked { - ++_id; - } - } + if (id == activeId) { + parameters = parameters.updateVolatilityParameters(id); - emit OracleSizeIncreased(_oracleSize, _newSize); - } + bytes32 fees = binReserves.getCompositionFees(parameters, binStep, amountsIn, supply, shares); - /// @notice Return the oracle's parameters - /// @return oracleSampleLifetime The lifetime of a sample, it accumulates information for up to this timestamp - /// @return oracleSize The size of the oracle (last ids can be empty) - /// @return oracleActiveSize The active size of the oracle (no empty data) - /// @return oracleLastTimestamp The timestamp of the creation of the oracle's latest sample - /// @return oracleId The index of the oracle's latest sample - function _getOracleParameters() - private - view - returns ( - uint256 oracleSampleLifetime, - uint256 oracleSize, - uint256 oracleActiveSize, - uint256 oracleLastTimestamp, - uint256 oracleId - ) - { - bytes32 _slot; - assembly { - _slot := sload(add(_pairInformation.slot, 1)) - } - oracleSampleLifetime = _slot.decode(type(uint16).max, _OFFSET_ORACLE_SAMPLE_LIFETIME); - oracleSize = _slot.decode(type(uint16).max, _OFFSET_ORACLE_SIZE); - oracleActiveSize = _slot.decode(type(uint16).max, _OFFSET_ORACLE_ACTIVE_SIZE); - oracleLastTimestamp = _slot.decode(type(uint40).max, _OFFSET_ORACLE_LAST_TIMESTAMP); - oracleId = _slot.decode(type(uint16).max, _OFFSET_ORACLE_ID); - } + if (fees != 0) { + uint256 userLiquidity = amountsIn.sub(fees).getLiquidity(price); + uint256 binLiquidity = binReserves.getLiquidity(price); - /// @notice Return the reserves and the active id of the pair - /// @return reserveX The reserve of token X - /// @return reserveY The reserve of token Y - /// @return activeId The active id of the pair - function _getReservesAndId() private view returns (uint256 reserveX, uint256 reserveY, uint256 activeId) { - uint256 _mask24 = type(uint24).max; - uint256 _mask136 = type(uint136).max; - assembly { - let slot := sload(add(_pairInformation.slot, 1)) - reserveY := and(slot, _mask136) - - slot := sload(_pairInformation.slot) - activeId := and(slot, _mask24) - reserveX := and(shr(_OFFSET_PAIR_RESERVE_X, slot), _mask136) - } - } + shares = userLiquidity.mulDivRoundDown(supply, binLiquidity); + bytes32 protocolCFees = fees.scalarMulDivBasisPointRoundDown(parameters.getProtocolShare()); - /// @notice Return the reserves of the bin at index `_id` - /// @param _id The id of the bin - /// @return reserveX The reserve of token X in the bin - /// @return reserveY The reserve of token Y in the bin - function _getBin(uint24 _id) private view returns (uint256 reserveX, uint256 reserveY) { - bytes32 _data; - uint256 _mask112 = type(uint112).max; - // low level read of mapping to only load 1 storage slot - assembly { - mstore(0, _id) - mstore(32, _bins.slot) - _data := sload(keccak256(0, 64)) - - reserveX := and(_data, _mask112) - reserveY := shr(_OFFSET_BIN_RESERVE_Y, _data) - } + if (protocolCFees != 0) { + amountsInToBin = amountsInToBin.sub(protocolCFees); + _protocolFees = _protocolFees.add(protocolCFees); + } - return (reserveX.safe112(), reserveY.safe112()); - } + _parameters = parameters; + _oracle.update(parameters, id); - /// @notice Return the total fees and the protocol fees of the pair - /// @dev The fees for users can be computed by subtracting the protocol fees from the total fees - /// @return feesXTotal The total fees of token X - /// @return feesYTotal The total fees of token Y - /// @return feesXProtocol The protocol fees of token X - /// @return feesYProtocol The protocol fees of token Y - function _getGlobalFees() - private - view - returns (uint128 feesXTotal, uint128 feesYTotal, uint128 feesXProtocol, uint128 feesYProtocol) - { - bytes32 _slotX; - bytes32 _slotY; - assembly { - _slotX := sload(add(_pairInformation.slot, 2)) - _slotY := sload(add(_pairInformation.slot, 3)) + emit CompositionFees(msg.sender, id, fees, protocolCFees); + } + } else { + amountsIn.verifyAmounts(activeId, id); } - feesXTotal = uint128(_slotX.decode(type(uint128).max, 0)); - feesYTotal = uint128(_slotY.decode(type(uint128).max, 0)); + if (shares == 0 || amountsInToBin == 0) revert LBPair__ZeroShares(id); - feesXProtocol = uint128(_slotX.decode(type(uint128).max, _OFFSET_PROTOCOL_FEE)); - feesYProtocol = uint128(_slotY.decode(type(uint128).max, _OFFSET_PROTOCOL_FEE)); - } - - /// @notice Return the fee added to a flashloan - /// @dev Rounds up the amount of fees - /// @param _amount The amount of the flashloan - /// @return The fee added to the flashloan - function _getFlashLoanFee(uint256 _amount) private view returns (uint256) { - uint256 _fee = factory.flashLoanFee(); - return (_amount * _fee + Constants.PRECISION - 1) / Constants.PRECISION; - } - - /// @notice Set the total and protocol fees - /// @dev The assembly block does: - /// _pairFees = FeeHelper.FeesDistribution({total: _totalFees, protocol: _protocolFees}); - /// @param _pairFees The storage slot of the fees - /// @param _totalFees The new total fees - /// @param _protocolFees The new protocol fees - function _setFees(FeeHelper.FeesDistribution storage _pairFees, uint128 _totalFees, uint128 _protocolFees) - private - { - assembly { - sstore(_pairFees.slot, or(shl(_OFFSET_PROTOCOL_FEE, _protocolFees), _totalFees)) - } - } + if (binReserves == 0) _tree.add(id); - /// @notice Emit the Swap event and avoid stack too deep error - /// if `swapForY` is: - /// - true: tokenIn is tokenX, and tokenOut is tokenY - /// - false: tokenIn is tokenY, and tokenOut is tokenX - /// @param _to The address of the recipient of the swap - /// @param _swapForY Whether the `amountInToBin` is tokenX (true) or tokenY (false), - /// and if `amountOutOfBin` is tokenY (true) or tokenX (false) - /// @param _amountInToBin The amount of tokenIn sent by the user - /// @param _amountOutOfBin The amount of tokenOut received by the user - /// @param _volatilityAccumulated The volatility accumulated number - /// @param _fees The amount of fees, always denominated in tokenIn - function _emitSwap( - address _to, - uint24 _activeId, - bool _swapForY, - uint256 _amountInToBin, - uint256 _amountOutOfBin, - uint256 _volatilityAccumulated, - uint256 _fees - ) private { - emit Swap(msg.sender, _to, _activeId, _swapForY, _amountInToBin, _amountOutOfBin, _volatilityAccumulated, _fees); + _bins[id] = binReserves.add(amountsInToBin); } } diff --git a/src/LBToken.sol b/src/LBToken.sol index 86aa1140..e99f9bdb 100644 --- a/src/LBToken.sol +++ b/src/LBToken.sol @@ -2,249 +2,269 @@ pragma solidity 0.8.10; -import "openzeppelin/utils/structs/EnumerableSet.sol"; - -import "./LBErrors.sol"; import "./interfaces/ILBToken.sol"; -/// @title Liquidity Book Token -/// @author Trader Joe -/// @notice The LBToken is an implementation of a multi-token. -/// It allows to create multi-ERC20 represented by their ids +/** + * @title Liquidity Book Token + * @author Trader Joe + * @notice The LBToken is an implementation of a multi-token. + * It allows to create multi-ERC20 represented by their ids. + * Its implementation is really similar to the ERC1155 standardn the main difference + * is that it doesn't do any call to the receiver contract to prevent reentrancy. + * As it's only for ERC20s, the uri function is not implemented. + * The contract is made for batch operations. + */ contract LBToken is ILBToken { - using EnumerableSet for EnumerableSet.UintSet; - - /// @dev Mapping from token id to account balances - mapping(uint256 => mapping(address => uint256)) private _balances; - - /// @dev Mapping from account to spender approvals - mapping(address => mapping(address => bool)) private _spenderApprovals; - - /// @dev Mapping from token id to total supplies + /** + * @dev The mapping from account to token id to account balance. + */ + mapping(address => mapping(uint256 => uint256)) private _balances; + + /** + * @dev The mapping from token id to total supply. + */ mapping(uint256 => uint256) private _totalSupplies; - string private constant _NAME = "Liquidity Book Token"; - string private constant _SYMBOL = "LBT"; + /** + * @dev Mapping from account to spender approvals. + */ + mapping(address => mapping(address => bool)) private _spenderApprovals; - modifier checkApproval(address _from, address _spender) { - if (!_isApprovedForAll(_from, _spender)) revert LBToken__SpenderNotApproved(_from, _spender); + /** + * @dev Modifier to check if the spender is approved for all. + */ + modifier checkApproval(address from, address spender) { + if (!_isApprovedForAll(from, spender)) revert LBToken__SpenderNotApproved(from, spender); _; } - modifier checkAddresses(address _from, address _to) { - if (_from == address(0) || _to == address(0)) revert LBToken__TransferFromOrToAddress0(); - if (_from == _to) revert LBToken__TransferToSelf(); + /** + * @dev Modifier to check if the address is not zero or the contract itself. + */ + modifier notAddressZeroOrThis(address account) { + if (account == address(0) || account == address(this)) revert LBToken__AddressThisOrZero(); _; } - modifier checkLength(uint256 _lengthA, uint256 _lengthB) { - if (_lengthA != _lengthB) revert LBToken__LengthMismatch(_lengthA, _lengthB); + /** + * @dev Modifier to check if the length of the arrays are equal. + */ + modifier checkLength(uint256 lengthA, uint256 lengthB) { + if (lengthA != lengthB) revert LBToken__InvalidLength(); _; } - /// @notice Returns the name of the token - /// @return The name of the token - function name() public pure virtual override returns (string memory) { - return _NAME; + /** + * @notice Returns the name of the token. + * @return The name of the token. + */ + function name() public view virtual override returns (string memory) { + return "Liquidity Book Token"; } - /// @notice Returns the symbol of the token, usually a shorter version of the name - /// @return The symbol of the token - function symbol() public pure virtual override returns (string memory) { - return _SYMBOL; + /** + * @notice Returns the symbol of the token, usually a shorter version of the name. + * @return The symbol of the token. + */ + function symbol() public view virtual override returns (string memory) { + return "LBT"; } - /// @notice Returns the total supply of token of type `id` - /// @dev This is the amount of token of type `id` minted minus the amount burned - /// @param _id The token id - /// @return The total supply of that token id - function totalSupply(uint256 _id) public view virtual override returns (uint256) { - return _totalSupplies[_id]; + /** + * @notice Returns the total supply of token of type `id`. + * /** + * @dev This is the amount of token of type `id` minted minus the amount burned. + * @param id The token id. + * @return The total supply of that token id. + */ + function totalSupply(uint256 id) public view virtual override returns (uint256) { + return _totalSupplies[id]; } - /// @notice Returns the amount of tokens of type `id` owned by `_account` - /// @param _account The address of the owner - /// @param _id The token id - /// @return The amount of tokens of type `id` owned by `_account` - function balanceOf(address _account, uint256 _id) public view virtual override returns (uint256) { - return _balances[_id][_account]; + /** + * @notice Returns the amount of tokens of type `id` owned by `account`. + * @param account The address of the owner. + * @param id The token id. + * @return The amount of tokens of type `id` owned by `account`. + */ + function balanceOf(address account, uint256 id) public view virtual override returns (uint256) { + return _balances[account][id]; } - /// @notice Return the balance of multiple (account/id) pairs - /// @param _accounts The addresses of the owners - /// @param _ids The token ids - /// @return batchBalances The balance for each (account, id) pair - function balanceOfBatch(address[] calldata _accounts, uint256[] calldata _ids) + /** + * @notice Return the balance of multiple (account/id) pairs. + * @param accounts The addresses of the owners. + * @param ids The token ids. + * @return batchBalances The balance for each (account, id) pair. + */ + function balanceOfBatch(address[] memory accounts, uint256[] memory ids) public view virtual override - checkLength(_accounts.length, _ids.length) + checkLength(accounts.length, ids.length) returns (uint256[] memory batchBalances) { - batchBalances = new uint256[](_accounts.length); + batchBalances = new uint256[](accounts.length); unchecked { - for (uint256 i; i < _accounts.length; ++i) { - batchBalances[i] = balanceOf(_accounts[i], _ids[i]); + for (uint256 i; i < accounts.length; ++i) { + batchBalances[i] = balanceOf(accounts[i], ids[i]); } } } - /// @notice Returns true if `spender` is approved to transfer `_account`'s tokens - /// @param _owner The address of the owner - /// @param _spender The address of the spender - /// @return True if `spender` is approved to transfer `_account`'s tokens - function isApprovedForAll(address _owner, address _spender) public view virtual override returns (bool) { - return _isApprovedForAll(_owner, _spender); + /** + * @notice Returns true if `spender` is approved to transfer `account`'s tokens. + * @param owner The address of the owner. + * @param spender The address of the spender. + * @return True if `spender` is approved to transfer `account`'s tokens. + */ + function isApprovedForAll(address owner, address spender) public view virtual override returns (bool) { + return _isApprovedForAll(owner, spender); } - /// @notice Grants or revokes permission to `spender` to transfer the caller's tokens, according to `approved` - /// @param _spender The address of the spender - /// @param _approved The boolean value to grant or revoke permission - function setApprovalForAll(address _spender, bool _approved) public virtual override { - _setApprovalForAll(msg.sender, _spender, _approved); + /** + * @notice Grants or revokes permission to `spender` to transfer the caller's tokens, according to `approved`. + * @param spender The address of the spender. + * @param approved The boolean value to grant or revoke permission. + */ + function setApprovalForAll(address spender, bool approved) public virtual override { + _setApprovalForAll(msg.sender, spender, approved); } - /// @notice Transfers `_amount` token of type `_id` from `_from` to `_to` - /// @param _from The address of the owner of the token - /// @param _to The address of the recipient - /// @param _id The token id - /// @param _amount The amount to send - function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount) + /** + * @notice Batch transfers `amounts` of `ids` from `from` to `to`. + * @param from The address of the owner. + * @param to The address of the recipient. + * @param ids The list of token ids. + * @param amounts The list of amounts to transfer for each token id in `ids`. + */ + function batchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory amounts) public virtual override - checkAddresses(_from, _to) - checkApproval(_from, msg.sender) + checkApproval(from, msg.sender) { - address _spender = msg.sender; - - _transfer(_from, _to, _id, _amount); + _batchTransferFrom(from, to, ids, amounts); + } - emit TransferSingle(_spender, _from, _to, _id, _amount); + /** + * @notice Returns true if `spender` is approved to transfer `owner`'s tokens or if `sender` is the `owner`. + * @param owner The address of the owner. + * @param spender The address of the spender. + * @return True if `spender` is approved to transfer `owner`'s tokens. + */ + function _isApprovedForAll(address owner, address spender) internal view returns (bool) { + return owner == spender || _spenderApprovals[owner][spender]; } - /// @notice Batch transfers `_amount` tokens of type `_id` from `_from` to `_to` - /// @param _from The address of the owner of the tokens - /// @param _to The address of the recipient - /// @param _ids The list of token ids - /// @param _amounts The list of amounts to send - function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) - public - virtual - override - checkLength(_ids.length, _amounts.length) - checkAddresses(_from, _to) - checkApproval(_from, msg.sender) + /** + * @dev Batch mints `amounts` of `ids` to `account`. + * The `account` must not be the zero address and the `ids` and `amounts` must have the same length. + * @param account The address of the owner. + * @param ids The list of token ids. + * @param amounts The list of amounts to mint for each token id in `ids`. + */ + function _mintBatch(address account, uint256[] memory ids, uint256[] memory amounts) + internal + notAddressZeroOrThis(account) + checkLength(ids.length, amounts.length) { - unchecked { - for (uint256 i; i < _ids.length; ++i) { - _transfer(_from, _to, _ids[i], _amounts[i]); - } - } + mapping(uint256 => uint256) storage accountBalances = _balances[account]; - emit TransferBatch(msg.sender, _from, _to, _ids, _amounts); - } + for (uint256 i; i < ids.length;) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; - /// @notice Returns whether this contract implements the interface defined by - /// `interfaceId` (true) or not (false) - /// @param _interfaceId The interface identifier - /// @return Whether the interface is supported (true) or not (false) - function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { - return _interfaceId == type(ILBToken).interfaceId || _interfaceId == type(IERC165).interfaceId; - } + _totalSupplies[id] += amount; - /// @notice Internal function to transfer `_amount` tokens of type `_id` from `_from` to `_to` - /// @param _from The address of the owner of the token - /// @param _to The address of the recipient - /// @param _id The token id - /// @param _amount The amount to send - function _transfer(address _from, address _to, uint256 _id, uint256 _amount) internal virtual { - uint256 _fromBalance = _balances[_id][_from]; - if (_fromBalance < _amount) revert LBToken__TransferExceedsBalance(_from, _id, _amount); + unchecked { + accountBalances[id] += amount; - _beforeTokenTransfer(_from, _to, _id, _amount); - - unchecked { - _balances[_id][_from] = _fromBalance - _amount; - _balances[_id][_to] += _amount; + ++i; + } } + + emit TransferBatch(msg.sender, address(0), account, ids, amounts); } - /// @dev Creates `_amount` tokens of type `_id`, and assigns them to `_account` - /// @param _account The address of the recipient - /// @param _id The token id - /// @param _amount The amount to mint - function _mint(address _account, uint256 _id, uint256 _amount) internal virtual { - if (_account == address(0)) revert LBToken__MintToAddress0(); + /** + * @dev Batch burns `amounts` of `ids` from `account`. + * The `ids` and `amounts` must have the same length. + * @param account The address of the owner. + * @param ids The list of token ids. + * @param amounts The list of amounts to burn for each token id in `ids`. + */ + function _burnBatch(address account, uint256[] memory ids, uint256[] memory amounts) + internal + notAddressZeroOrThis(account) + checkLength(ids.length, amounts.length) + { + mapping(uint256 => uint256) storage accountBalances = _balances[account]; - _beforeTokenTransfer(address(0), _account, _id, _amount); + for (uint256 i; i < ids.length;) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; - _totalSupplies[_id] += _amount; + uint256 balance = accountBalances[id]; + if (balance < amount) revert LBToken__BurnExceedsBalance(account, id, amount); - unchecked { - _balances[_id][_account] += _amount; + unchecked { + _totalSupplies[id] -= amount; + accountBalances[id] -= amount; + + ++i; + } } - emit TransferSingle(msg.sender, address(0), _account, _id, _amount); + emit TransferBatch(msg.sender, account, address(0), ids, amounts); } - /// @dev Destroys `_amount` tokens of type `_id` from `_account` - /// @param _account The address of the owner - /// @param _id The token id - /// @param _amount The amount to destroy - function _burn(address _account, uint256 _id, uint256 _amount) internal virtual { - if (_account == address(0)) revert LBToken__BurnFromAddress0(); + /** + * @dev Batch transfers `amounts` of `ids` from `from` to `to`. + * The `to` must not be the zero address and the `ids` and `amounts` must have the same length. + * @param from The address of the owner. + * @param to The address of the recipient. + * @param ids The list of token ids. + * @param amounts The list of amounts to transfer for each token id in `ids`. + */ + function _batchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory amounts) + internal + checkLength(ids.length, amounts.length) + notAddressZeroOrThis(to) + { + mapping(uint256 => uint256) storage fromBalances = _balances[from]; + mapping(uint256 => uint256) storage toBalances = _balances[to]; - uint256 _accountBalance = _balances[_id][_account]; - if (_accountBalance < _amount) revert LBToken__BurnExceedsBalance(_account, _id, _amount); + for (uint256 i; i < ids.length;) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; - _beforeTokenTransfer(_account, address(0), _id, _amount); + uint256 fromBalance = fromBalances[id]; + if (fromBalance < amount) revert LBToken__TransferExceedsBalance(from, id, amount); - unchecked { - _balances[_id][_account] = _accountBalance - _amount; - _totalSupplies[_id] -= _amount; + unchecked { + fromBalances[id] = fromBalance - amount; + toBalances[id] += amount; + + ++i; + } } - emit TransferSingle(msg.sender, _account, address(0), _id, _amount); - } - - /// @notice Grants or revokes permission to `spender` to transfer the caller's tokens, according to `approved` - /// @param _owner The address of the owner - /// @param _spender The address of the spender - /// @param _approved The boolean value to grant or revoke permission - function _setApprovalForAll(address _owner, address _spender, bool _approved) internal virtual { - if (_owner == _spender) revert LBToken__SelfApproval(_owner); - - _spenderApprovals[_owner][_spender] = _approved; - emit ApprovalForAll(_owner, _spender, _approved); - } - - /// @notice Returns true if `spender` is approved to transfer `owner`'s tokens - /// or if `sender` is the `owner` - /// @param _owner The address of the owner - /// @param _spender The address of the spender - /// @return True if `spender` is approved to transfer `owner`'s tokens - function _isApprovedForAll(address _owner, address _spender) internal view virtual returns (bool) { - return _owner == _spender || _spenderApprovals[_owner][_spender]; - } - - /// @notice Hook that is called before any token transfer. This includes minting - /// and burning. - /// - /// Calling conditions (for each `id` and `amount` pair): - /// - /// - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens - /// of token type `id` will be transferred to `to`. - /// - When `from` is zero, `amount` tokens of token type `id` will be minted - /// for `to`. - /// - when `to` is zero, `amount` of ``from``'s tokens of token type `id` - /// will be burned. - /// - `from` and `to` are never both zero. - /// @param from The address of the owner of the token - /// @param to The address of the recipient of the token - /// @param id The id of the token - /// @param amount The amount of token of type `id` - function _beforeTokenTransfer(address from, address to, uint256 id, uint256 amount) internal virtual {} + emit TransferBatch(msg.sender, from, to, ids, amounts); + } + + /** + * @notice Grants or revokes permission to `spender` to transfer the caller's tokens, according to `approved` + * @param owner The address of the owner + * @param spender The address of the spender + * @param approved The boolean value to grant or revoke permission + */ + function _setApprovalForAll(address owner, address spender, bool approved) internal { + if (owner == spender) revert LBToken__SelfApproval(owner); + + _spenderApprovals[owner][spender] = approved; + emit ApprovalForAll(owner, spender, approved); + } } diff --git a/src/interfaces/ILBFlashLoanCallback.sol b/src/interfaces/ILBFlashLoanCallback.sol index cff9fa0e..de6d0dd3 100644 --- a/src/interfaces/ILBFlashLoanCallback.sol +++ b/src/interfaces/ILBFlashLoanCallback.sol @@ -8,7 +8,12 @@ import "openzeppelin/token/ERC20/IERC20.sol"; /// @author Trader Joe /// @notice Required interface to interact with LB flash loans interface ILBFlashLoanCallback { - function LBFlashLoanCallback(address sender, IERC20 token, uint256 amount, uint256 fee, bytes calldata data) - external - returns (bytes32); + function LBFlashLoanCallback( + address sender, + IERC20 tokenX, + IERC20 tokenY, + bytes32 amounts, + bytes32 totalFees, + bytes calldata data + ) external returns (bytes32); } diff --git a/src/interfaces/ILBPair.sol b/src/interfaces/ILBPair.sol index 35b5164a..f755695b 100644 --- a/src/interfaces/ILBPair.sol +++ b/src/interfaces/ILBPair.sol @@ -2,205 +2,148 @@ pragma solidity 0.8.10; -import "openzeppelin/token/ERC20/IERC20.sol"; - -import "../libraries/FeeHelper.sol"; -import "./ILBFactory.sol"; -import "./ILBFlashLoanCallback.sol"; - -/// @title Liquidity Book Pair Interface -/// @author Trader Joe -/// @notice Required interface of LBPair contract -interface ILBPair { - /// @dev Structure to store the reserves of bins: - /// - reserveX: The current reserve of tokenX of the bin - /// - reserveY: The current reserve of tokenY of the bin - struct Bin { - uint112 reserveX; - uint112 reserveY; - uint256 accTokenXPerShare; - uint256 accTokenYPerShare; - } - - /// @dev Structure to store the information of the pair such as: - /// slot0: - /// - activeId: The current id used for swaps, this is also linked with the price - /// - reserveX: The sum of amounts of tokenX across all bins - /// slot1: - /// - reserveY: The sum of amounts of tokenY across all bins - /// - oracleSampleLifetime: The lifetime of an oracle sample - /// - oracleSize: The current size of the oracle, can be increase by users - /// - oracleActiveSize: The current active size of the oracle, composed only from non empty data sample - /// - oracleLastTimestamp: The current last timestamp at which a sample was added to the circular buffer - /// - oracleId: The current id of the oracle - /// slot2: - /// - feesX: The current amount of fees to distribute in tokenX (total, protocol) - /// slot3: - /// - feesY: The current amount of fees to distribute in tokenY (total, protocol) - struct PairInformation { - uint24 activeId; - uint136 reserveX; - uint136 reserveY; - uint16 oracleSampleLifetime; - uint16 oracleSize; - uint16 oracleActiveSize; - uint40 oracleLastTimestamp; - uint16 oracleId; - FeeHelper.FeesDistribution feesX; - FeeHelper.FeesDistribution feesY; - } - - /// @dev Structure to store the debts of users - /// - debtX: The tokenX's debt - /// - debtY: The tokenY's debt - struct Debts { - uint256 debtX; - uint256 debtY; - } - - /// @dev Structure to store fees: - /// - tokenX: The amount of fees of token X - /// - tokenY: The amount of fees of token Y - struct Fees { - uint128 tokenX; - uint128 tokenY; - } - - /// @dev Structure to minting informations: - /// - amountXIn: The amount of token X sent - /// - amountYIn: The amount of token Y sent - /// - amountXAddedToPair: The amount of token X that have been actually added to the pair - /// - amountYAddedToPair: The amount of token Y that have been actually added to the pair - /// - activeFeeX: Fees X currently generated - /// - activeFeeY: Fees Y currently generated - /// - totalDistributionX: Total distribution of token X. Should be 1e18 (100%) or 0 (0%) - /// - totalDistributionY: Total distribution of token Y. Should be 1e18 (100%) or 0 (0%) - /// - id: Id of the current working bin when looping on the distribution array - /// - amountX: The amount of token X deposited in the current bin - /// - amountY: The amount of token Y deposited in the current bin - /// - distributionX: Distribution of token X for the current working bin - /// - distributionY: Distribution of token Y for the current working bin - struct MintInfo { - uint256 amountXIn; - uint256 amountYIn; - uint256 amountXAddedToPair; - uint256 amountYAddedToPair; - uint256 activeFeeX; - uint256 activeFeeY; - uint256 totalDistributionX; - uint256 totalDistributionY; - uint256 id; - uint256 amountX; - uint256 amountY; - uint256 distributionX; - uint256 distributionY; - } +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; + +import {ILBFactory} from "./ILBFactory.sol"; +import {ILBFlashLoanCallback} from "./ILBFlashLoanCallback.sol"; +import {ILBToken} from "./ILBToken.sol"; + +interface ILBPair is ILBToken { + error LBPair__AddressZero(); + error LBPair__AlreadyInitialized(); + error LBPair__EmptyMarketConfigs(); + error LBPair__FlashLoanCallbackFailed(); + error LBPair__FlashLoanInsufficientAmount(); + error LBPair__InsufficientAmountIn(); + error LBPair__InsufficientAmountOut(); + error LBPair__InvalidInput(); + error LBPair__InvalidStaticFeeParameters(); + error LBPair__OnlyFactory(); + error LBPair__OnlyProtocolFeeReceiver(); + error LBPair__OutOfLiquidity(); + error LBPair__TokenNotSupported(); + error LBPair__ZeroAmount(uint24 id); + error LBPair__ZeroAmountsOut(uint24 id); + error LBPair__ZeroShares(uint24 id); + + event DepositedToBins(address indexed sender, address indexed to, uint256[] ids, bytes32[] amounts); + + event WithdrawnFromBins(address indexed sender, address indexed to, uint256[] ids, bytes32[] amounts); + + event CompositionFees(address indexed sender, uint24 id, bytes32 totalFees, bytes32 protocolFees); + + event CollectedProtocolFees(address indexed feeRecipient, bytes32 protocolFees); event Swap( address indexed sender, - address indexed recipient, - uint256 indexed id, - bool swapForY, - uint256 amountIn, - uint256 amountOut, - uint256 volatilityAccumulated, - uint256 fees + address indexed to, + uint24 id, + bytes32 amountsIn, + bytes32 amountsOut, + uint24 volatilityAccumulated, + bytes32 totalFees, + bytes32 protocolFees ); - event FlashLoan( - address indexed sender, ILBFlashLoanCallback indexed receiver, IERC20 token, uint256 amount, uint256 fee + event StaticFeeParametersSet( + address indexed sender, + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated ); - event CompositionFee( - address indexed sender, address indexed recipient, uint256 indexed id, uint256 feesX, uint256 feesY + event FlashLoan( + address indexed sender, + ILBFlashLoanCallback indexed receiver, + uint24 activeId, + bytes32 amounts, + bytes32 totalFees, + bytes32 protocolFees ); - event DepositedToBin( - address indexed sender, address indexed recipient, uint256 indexed id, uint256 amountX, uint256 amountY - ); + event ForcedDecay(address indexed sender, uint24 idReference, uint24 volatilityReference); - event WithdrawnFromBin( - address indexed sender, address indexed recipient, uint256 indexed id, uint256 amountX, uint256 amountY - ); + function initialize( + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated, + uint24 activeId + ) external; - event FeesCollected(address indexed sender, address indexed recipient, uint256 amountX, uint256 amountY); + function getFactory() external view returns (ILBFactory factory); - event ProtocolFeesCollected(address indexed sender, address indexed recipient, uint256 amountX, uint256 amountY); + function getTokenX() external view returns (IERC20 tokenX); - event OracleSizeIncreased(uint256 previousSize, uint256 newSize); + function getTokenY() external view returns (IERC20 tokenY); - function tokenX() external view returns (IERC20); + function getBinStep() external view returns (uint8 binStep); - function tokenY() external view returns (IERC20); + function getReserves() external view returns (uint128 reserveX, uint128 reserveY); - function factory() external view returns (ILBFactory); + function getActiveId() external view returns (uint24 activeId); - function getReservesAndId() external view returns (uint256 reserveX, uint256 reserveY, uint256 activeId); + function getBin(uint24 id) external view returns (uint128 binReserveX, uint128 binReserveY); - function getGlobalFees() - external - view - returns (uint128 feesXTotal, uint128 feesYTotal, uint128 feesXProtocol, uint128 feesYProtocol); + function getNextNonEmptyBin(bool swapForY, uint24 id) external view returns (uint24 nextId); - function getOracleParameters() + function getProtocolFees() external view returns (uint128 protocolFeeX, uint128 protocolFeeY); + + function getStaticFeeParameters() external view returns ( - uint256 oracleSampleLifetime, - uint256 oracleSize, - uint256 oracleActiveSize, - uint256 oracleLastTimestamp, - uint256 oracleId, - uint256 min, - uint256 max + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated ); - function getOracleSampleFrom(uint256 timeDelta) + function getVariableFeeParameters() external view - returns (uint256 cumulativeId, uint256 cumulativeAccumulator, uint256 cumulativeBinCrossed); - - function feeParameters() external view returns (FeeHelper.FeeParameters memory); - - function findFirstNonEmptyBinId(uint24 id_, bool sentTokenY) external view returns (uint24 id); + returns (uint24 volatilityAccumulated, uint24 volatilityReference, uint24 idReference, uint40 timeOfLastUpdate); - function getBin(uint24 id) external view returns (uint256 reserveX, uint256 reserveY); - - function pendingFees(address account, uint256[] memory ids) + function getOracleSampleAt(uint40 lookupTimestamp) external view - returns (uint256 amountX, uint256 amountY); - - function swap(bool sentTokenY, address to) external returns (uint256 amountXOut, uint256 amountYOut); + returns (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed); - function flashLoan(ILBFlashLoanCallback receiver, IERC20 token, uint256 amount, bytes calldata data) external; + function swap(bool swapForY, address to) external returns (bytes32 amountsOut); - function mint( - uint256[] calldata ids, - uint256[] calldata distributionX, - uint256[] calldata distributionY, - address to - ) external returns (uint256 amountXAddedToPair, uint256 amountYAddedToPair, uint256[] memory liquidityMinted); + function flashLoan(ILBFlashLoanCallback receiver, bytes32 amounts, bytes calldata data) external; - function burn(uint256[] calldata ids, uint256[] calldata amounts, address to) + function mint(address to, bytes32[] calldata liquidityConfigs, address refundTo) external - returns (uint256 amountX, uint256 amountY); + returns (bytes32 amountsReceived, bytes32 amountsLeft, uint256[] memory liquidityMinted); - function increaseOracleLength(uint16 newSize) external; + function burn(address from, address to, uint256[] calldata ids, uint256[] calldata amountsToBurn) + external + returns (bytes32[] memory amounts); - function collectFees(address account, uint256[] calldata ids) external returns (uint256 amountX, uint256 amountY); + function collectProtocolFees() external returns (bytes32 collectedProtocolFees); - function collectProtocolFees() external returns (uint128 amountX, uint128 amountY); + function increaseOracleLength(uint16 newLength) external; - function setFeesParameters(bytes32 packedFeeParameters) external; + function setStaticFeeParameters( + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated + ) external; function forceDecay() external; - - function initialize( - IERC20 tokenX, - IERC20 tokenY, - uint24 activeId, - uint16 sampleLifetime, - bytes32 packedFeeParameters - ) external; } diff --git a/src/interfaces/ILBToken.sol b/src/interfaces/ILBToken.sol index a94f2b65..e20c67c8 100644 --- a/src/interfaces/ILBToken.sol +++ b/src/interfaces/ILBToken.sol @@ -2,13 +2,18 @@ pragma solidity 0.8.10; -import "openzeppelin/utils/introspection/IERC165.sol"; - -/// @title Liquidity Book Token Interface -/// @author Trader Joe -/// @notice Required interface of LBToken contract -interface ILBToken is IERC165 { - event TransferSingle(address indexed sender, address indexed from, address indexed to, uint256 id, uint256 amount); +/** + * @title Liquidity Book Token Interface + * @author Trader Joe + * @notice Interface to interact with the LBToken. + */ +interface ILBToken { + error LBToken__AddressThisOrZero(); + error LBToken__InvalidLength(); + error LBToken__SelfApproval(address owner); + error LBToken__SpenderNotApproved(address from, address spender); + error LBToken__TransferExceedsBalance(address from, uint256 id, uint256 amount); + error LBToken__BurnExceedsBalance(address from, uint256 id, uint256 amount); event TransferBatch( address indexed sender, address indexed from, address indexed to, uint256[] ids, uint256[] amounts @@ -20,21 +25,18 @@ interface ILBToken is IERC165 { function symbol() external view returns (string memory); + function totalSupply(uint256 id) external view returns (uint256); + function balanceOf(address account, uint256 id) external view returns (uint256); function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view - returns (uint256[] memory batchBalances); - - function totalSupply(uint256 id) external view returns (uint256); + returns (uint256[] memory); function isApprovedForAll(address owner, address spender) external view returns (bool); - function setApprovalForAll(address sender, bool approved) external; - - function safeTransferFrom(address from, address to, uint256 id, uint256 amount) external; + function setApprovalForAll(address spender, bool approved) external; - function safeBatchTransferFrom(address from, address to, uint256[] calldata id, uint256[] calldata amount) - external; + function batchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts) external; } diff --git a/src/interfaces/IPendingOwnable.sol b/src/interfaces/IPendingOwnable.sol index e57628ff..6fa423de 100644 --- a/src/interfaces/IPendingOwnable.sol +++ b/src/interfaces/IPendingOwnable.sol @@ -2,11 +2,20 @@ pragma solidity 0.8.10; -/// @title Liquidity Book Pending Ownable Interface -/// @author Trader Joe -/// @notice Required interface of Pending Ownable contract used for LBFactory +/** + * @title Liquidity Book Pending Ownable Interface + * @author Trader Joe + * @notice Required interface of Pending Ownable contract used for LBFactory + */ interface IPendingOwnable { + error PendingOwnable__AddressZero(); + error PendingOwnable__NoPendingOwner(); + error PendingOwnable__NotOwner(); + error PendingOwnable__NotPendingOwner(); + error PendingOwnable__PendingOwnerAlreadySet(); + event PendingOwnerSet(address indexed pendingOwner); + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); function owner() external view returns (address); diff --git a/src/interfaces/IWAVAX.sol b/src/interfaces/IWAVAX.sol index 372d4ecd..74f77bdb 100644 --- a/src/interfaces/IWAVAX.sol +++ b/src/interfaces/IWAVAX.sol @@ -4,8 +4,10 @@ pragma solidity 0.8.10; import "openzeppelin/token/ERC20/IERC20.sol"; -/// @title WAVAX Interface -/// @notice Required interface of Wrapped AVAX contract +/** + * @title WAVAX Interface + * @notice Required interface of Wrapped AVAX contract + */ interface IWAVAX is IERC20 { function deposit() external payable; diff --git a/src/libraries/AddressesHelper.sol b/src/libraries/AddressesHelper.sol new file mode 100644 index 00000000..fa8847c4 --- /dev/null +++ b/src/libraries/AddressesHelper.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +/** + * @title Liquidity Book Addresses Helper Library + * @author Trader Joe + * @notice This library contains functions to check if an address is a contract and + * catch low level calls errors + */ +library AddressesHelper { + error AddressesHelper__NonContract(); + error AddressesHelper__CallFailed(); + + /** + * @notice Private view function to perform a low level call on `target` + * @dev Revert if the call doesn't succeed + * @param target The address of the account + * @param data The data to execute on `target` + * @return returnData The data returned by the call + */ + function callAndCatch(address target, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returnData) = target.call(data); + + if (success) { + if (returnData.length == 0 && !isContract(target)) revert AddressesHelper__NonContract(); + } else { + if (returnData.length == 0) { + revert AddressesHelper__CallFailed(); + } else { + // Look for revert reason and bubble it up if present + assembly { + revert(add(32, returnData), mload(returnData)) + } + } + } + + return returnData; + } + + /** + * @notice Private view function to return if an address is a contract + * @dev It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * @param account The address of the account + * @return Whether the account is a contract (true) or not (false) + */ + function isContract(address account) internal view returns (bool) { + return account.code.length > 0; + } +} diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index d25f5158..d6bbe2a5 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -2,58 +2,193 @@ pragma solidity 0.8.10; -import "../LBErrors.sol"; -import "./Math128x128.sol"; +import "openzeppelin/token/ERC20/IERC20.sol"; + +import "./math/PackedUint128Math.sol"; +import "./math/Uint256x256Math.sol"; +import "./math/SafeCast.sol"; +import "./Constants.sol"; +import "./PairParameterHelper.sol"; +import "./FeeHelper.sol"; +import "./PriceHelper.sol"; +import "./TokenHelper.sol"; -/// @title Liquidity Book Bin Helper Library -/// @author Trader Joe -/// @notice Contract used to convert bin ID to price and back library BinHelper { - using Math128x128 for uint256; + using PackedUint128Math for bytes32; + using PackedUint128Math for uint128; + using Uint256x256Math for uint256; + using PriceHelper for uint24; + using SafeCast for uint256; + using PairParameterHelper for bytes32; + using FeeHelper for uint128; + using TokenHelper for IERC20; + + error BinMath__CompositionFactorFlawed(uint24 id); + + function received(bytes32 reserves, IERC20 tokenX, IERC20 tokenY) internal view returns (bytes32 amounts) { + amounts = _balanceOf(tokenX).encode(_balanceOf(tokenY)).sub(reserves); + } + + function receivedX(bytes32 reserves, IERC20 tokenX) internal view returns (bytes32) { + uint128 reserveX = reserves.decodeFirst(); + return (_balanceOf(tokenX) - reserveX).encodeFirst(); + } + + function receivedY(bytes32 reserves, IERC20 tokenY) internal view returns (bytes32) { + uint128 reserveY = reserves.decodeSecond(); + return (_balanceOf(tokenY) - reserveY).encodeSecond(); + } + + function transfer(bytes32 amounts, IERC20 tokenX, IERC20 tokenY, address recipient) internal { + (uint128 amountX, uint128 amountY) = amounts.decode(); + + if (amountX > 0) tokenX.safeTransfer(recipient, amountX); + if (amountY > 0) tokenY.safeTransfer(recipient, amountY); + } + + function transferX(bytes32 amounts, IERC20 tokenX, address recipient) internal { + uint128 amountX = amounts.decodeFirst(); + + if (amountX > 0) tokenX.safeTransfer(recipient, amountX); + } + + function transferY(bytes32 amounts, IERC20 tokenY, address recipient) internal { + uint128 amountY = amounts.decodeSecond(); + + if (amountY > 0) tokenY.safeTransfer(recipient, amountY); + } + + function getAmountOutOfBin(bytes32 binReserves, uint256 amountToBurn, uint256 totalSupply) + internal + pure + returns (bytes32 amountsOut) + { + (uint128 binReserveX, uint128 binReserveY) = binReserves.decode(); + + uint128 amountXOutFromBin; + uint128 amountYOutFromBin; + + if (binReserveX > 0) { + amountXOutFromBin = (amountToBurn.mulDivRoundDown(binReserveX, totalSupply)).safe128(); + } + + if (binReserveY > 0) { + amountYOutFromBin = (amountToBurn.mulDivRoundDown(binReserveY, totalSupply)).safe128(); + } - int256 private constant REAL_ID_SHIFT = 1 << 23; + amountsOut = amountXOutFromBin.encode(amountYOutFromBin); + } + + function getShareAndEffectiveAmountsIn(bytes32 binReserves, bytes32 amountsIn, uint256 price, uint256 totalSupply) + internal + pure + returns (uint256 shares, bytes32 effectiveAmountsIn) + { + uint256 userLiquidity = getLiquidity(amountsIn, price); + if (totalSupply == 0) return (userLiquidity, amountsIn); + + uint256 binLiquidity = getLiquidity(binReserves, price); - /// @notice Returns the id corresponding to the given price - /// @dev The id may be inaccurate due to rounding issues, always trust getPriceFromId rather than - /// getIdFromPrice - /// @param _price The price of y per x as a 128.128-binary fixed-point number - /// @param _binStep The bin step - /// @return The id corresponding to this price - function getIdFromPrice(uint256 _price, uint256 _binStep) internal pure returns (uint24) { - unchecked { - uint256 _binStepValue = _getBPValue(_binStep); + shares = userLiquidity.mulDivRoundDown(totalSupply, binLiquidity); + uint256 effectiveLiquidity = shares.mulDivRoundDown(binLiquidity, totalSupply); - // can't overflow as `2**23 + log2(price) < 2**23 + 2**128 < max(uint256)` - int256 _id = REAL_ID_SHIFT + _price.log2() / _binStepValue.log2(); + uint256 ratioLiquidity = effectiveLiquidity.shiftDivRoundUp(Constants.SCALE_OFFSET, userLiquidity); + effectiveAmountsIn = amountsIn.scalarMulShift128RoundUp(ratioLiquidity.safe128()); + } - if (_id < 0 || uint256(_id) > type(uint24).max) revert BinHelper__IdOverflows(); - return uint24(uint256(_id)); + function getLiquidity(bytes32 amounts, uint256 price) internal pure returns (uint256 liquidity) { + (uint128 x, uint128 y) = amounts.decode(); + if (x > 0) { + liquidity = price.mulShiftRoundDown(x, Constants.SCALE_OFFSET); + } + if (y > 0) { + liquidity += y; } } - /// @notice Returns the price corresponding to the given ID, as a 128.128-binary fixed-point number - /// @dev This is the trusted function to link id to price, the other way may be inaccurate - /// @param _id The id - /// @param _binStep The bin step - /// @return The price corresponding to this id, as a 128.128-binary fixed-point number - function getPriceFromId(uint256 _id, uint256 _binStep) internal pure returns (uint256) { - if (_id > uint256(type(uint24).max)) revert BinHelper__IdOverflows(); - unchecked { - int256 _realId = int256(_id) - REAL_ID_SHIFT; + function verifyAmounts(bytes32 amounts, uint24 activeId, uint24 id) internal pure { + if ( + uint256(amounts) <= type(uint128).max && id < activeId + || uint256(amounts) > type(uint128).max && id > activeId + ) revert BinMath__CompositionFactorFlawed(id); + } + + function getCompositionFees( + bytes32 binReserves, + bytes32 parameters, + uint8 binStep, + bytes32 amountsIn, + uint256 totalSupply, + uint256 shares + ) internal pure returns (bytes32 fees) { + (uint128 amountX, uint128 amountY) = amountsIn.decode(); + (uint128 receivedAmountX, uint128 receivedAmountY) = + getAmountOutOfBin(binReserves.add(amountsIn), shares, totalSupply + shares).decode(); + + if (receivedAmountX > amountX) { + uint128 feeY = (amountY - receivedAmountY).getCompositionFee(parameters.getTotalFee(binStep)); + + fees = feeY.encodeSecond(); + } else if (receivedAmountY > amountY) { + uint128 feeX = (amountX - receivedAmountX).getCompositionFee(parameters.getTotalFee(binStep)); - return _getBPValue(_binStep).power(_realId); + fees = feeX.encodeFirst(); } } - /// @notice Returns the (1 + bp) value as a 128.128-decimal fixed-point number - /// @param _binStep The bp value in [1; 100] (referring to 0.01% to 1%) - /// @return The (1+bp) value as a 128.128-decimal fixed-point number - function _getBPValue(uint256 _binStep) internal pure returns (uint256) { - if (_binStep == 0 || _binStep > Constants.BASIS_POINT_MAX) revert BinHelper__BinStepOverflows(_binStep); + function isEmpty(bytes32 binReserves, bool isX) internal pure returns (bool) { + return isX ? binReserves.decodeFirst() == 0 : binReserves.decodeSecond() == 0; + } + + function getAmounts( + bytes32 binReserves, + bytes32 parameters, + uint8 binStep, + bool swapForY, // swap `swapForY` and `activeId` to avoid stack too deep + uint24 activeId, + bytes32 amountsLeft + ) internal pure returns (bytes32 amountsInToBin, bytes32 amountsOutOfBin, bytes32 totalFees) { + uint256 price = activeId.getPriceFromId(binStep); + + uint128 binReserveOut = binReserves.decode(!swapForY); - unchecked { - // can't overflow as `max(result) = 2**128 + 10_000 << 128 / 10_000 < max(uint256)` - return Constants.SCALE + (_binStep << Constants.SCALE_OFFSET) / Constants.BASIS_POINT_MAX; + uint128 maxAmountIn = swapForY + ? uint256(binReserveOut).shiftDivRoundUp(Constants.SCALE_OFFSET, price).safe128() + : uint256(binReserveOut).mulShiftRoundUp(price, Constants.SCALE_OFFSET).safe128(); + + uint256 totalFee = parameters.getTotalFee(binStep); + uint128 maxFee = maxAmountIn.getFeeAmount(totalFee); + + uint128 fee128; + uint128 amountIn128; + uint128 amountOut128; + + uint128 amountIn = amountsLeft.decode(swapForY); + + if (amountIn >= maxAmountIn + maxFee) { + fee128 = maxFee; + + amountIn128 = maxAmountIn + maxFee; + amountOut128 = binReserveOut; + } else { + fee128 = amountIn.getFeeAmountFrom(totalFee); + + amountIn128 = amountIn; + + amountIn -= fee128; + amountOut128 = swapForY + ? uint256(amountIn).mulShiftRoundDown(price, Constants.SCALE_OFFSET).safe128() + : uint256(amountIn).shiftDivRoundDown(Constants.SCALE_OFFSET, price).safe128(); + + if (amountOut128 > binReserveOut) amountOut128 = binReserveOut; } + + (amountsInToBin, amountsOutOfBin, totalFees) = swapForY + ? (amountIn128.encodeFirst(), amountOut128.encodeSecond(), fee128.encodeFirst()) + : (amountIn128.encodeSecond(), amountOut128.encodeFirst(), fee128.encodeSecond()); + } + + function _balanceOf(IERC20 token) private view returns (uint128) { + return token.balanceOf(address(this)).safe128(); } } diff --git a/src/libraries/BitMath.sol b/src/libraries/BitMath.sol deleted file mode 100644 index 71202592..00000000 --- a/src/libraries/BitMath.sol +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -/// @title Liquidity Book Bit Math Library -/// @author Trader Joe -/// @notice Helper contract used for bit calculations -library BitMath { - /// @notice Returns the closest non-zero bit of `integer` to the right (of left) of the `bit` bits that is not `bit` - /// @param _integer The integer as a uint256 - /// @param _bit The bit index - /// @param _rightSide Whether we're searching in the right side of the tree (true) or the left side (false) - /// @return The index of the closest non-zero bit. If there is no closest bit, it returns max(uint256) - function closestBit(uint256 _integer, uint8 _bit, bool _rightSide) internal pure returns (uint256) { - return _rightSide ? closestBitRight(_integer, _bit - 1) : closestBitLeft(_integer, _bit + 1); - } - - /// @notice Returns the most (or least) significant bit of `_integer` - /// @param _integer The integer - /// @param _isMostSignificant Whether we want the most (true) or the least (false) significant bit - /// @return The index of the most (or least) significant bit - function significantBit(uint256 _integer, bool _isMostSignificant) internal pure returns (uint8) { - return _isMostSignificant ? mostSignificantBit(_integer) : leastSignificantBit(_integer); - } - - /// @notice Returns the index of the closest bit on the right of x that is non null - /// @param x The value as a uint256 - /// @param bit The index of the bit to start searching at - /// @return id The index of the closest non null bit on the right of x. - /// If there is no closest bit, it returns max(uint256) - function closestBitRight(uint256 x, uint8 bit) internal pure returns (uint256 id) { - unchecked { - uint256 _shift = 255 - bit; - x <<= _shift; - - // can't overflow as it's non-zero and we shifted it by `_shift` - return (x == 0) ? type(uint256).max : mostSignificantBit(x) - _shift; - } - } - - /// @notice Returns the index of the closest bit on the left of x that is non null - /// @param x The value as a uint256 - /// @param bit The index of the bit to start searching at - /// @return id The index of the closest non null bit on the left of x. - /// If there is no closest bit, it returns max(uint256) - function closestBitLeft(uint256 x, uint8 bit) internal pure returns (uint256 id) { - unchecked { - x >>= bit; - - return (x == 0) ? type(uint256).max : leastSignificantBit(x) + bit; - } - } - - /// @notice Returns the index of the most significant bit of x - /// @param x The value as a uint256 - /// @return msb The index of the most significant bit of x - function mostSignificantBit(uint256 x) internal pure returns (uint8 msb) { - unchecked { - if (x >= 1 << 128) { - x >>= 128; - msb = 128; - } - if (x >= 1 << 64) { - x >>= 64; - msb += 64; - } - if (x >= 1 << 32) { - x >>= 32; - msb += 32; - } - if (x >= 1 << 16) { - x >>= 16; - msb += 16; - } - if (x >= 1 << 8) { - x >>= 8; - msb += 8; - } - if (x >= 1 << 4) { - x >>= 4; - msb += 4; - } - if (x >= 1 << 2) { - x >>= 2; - msb += 2; - } - if (x >= 1 << 1) { - msb += 1; - } - } - } - - /// @notice Returns the index of the least significant bit of x - /// @param x The value as a uint256 - /// @return lsb The index of the least significant bit of x - function leastSignificantBit(uint256 x) internal pure returns (uint8 lsb) { - unchecked { - if (x << 128 != 0) { - x <<= 128; - lsb = 128; - } - if (x << 64 != 0) { - x <<= 64; - lsb += 64; - } - if (x << 32 != 0) { - x <<= 32; - lsb += 32; - } - if (x << 16 != 0) { - x <<= 16; - lsb += 16; - } - if (x << 8 != 0) { - x <<= 8; - lsb += 8; - } - if (x << 4 != 0) { - x <<= 4; - lsb += 4; - } - if (x << 2 != 0) { - x <<= 2; - lsb += 2; - } - if (x << 1 != 0) { - lsb += 1; - } - - return 255 - lsb; - } - } -} diff --git a/src/libraries/Clone.sol b/src/libraries/Clone.sol new file mode 100644 index 00000000..1ddef455 --- /dev/null +++ b/src/libraries/Clone.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +/// @notice Class with helper read functions for clone with immutable args. +/// @author Trader Joe +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Clone.sol) +/// @author Adapted from clones with immutable args by zefram.eth, Saw-mon & Natalie +/// (https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args) + +/** + * @title Clone + * @notice Class with helper read functions for clone with immutable args. + * @author Trader Joe + * @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Clone.sol) + * @author Adapted from clones with immutable args by zefram.eth, Saw-mon & Natalie + * (https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args) + */ +abstract contract Clone { + /** + * @dev Reads an immutable arg with type bytes + * @param argOffset The offset of the arg in the immutable args + * @param length The length of the arg + * @return arg The immutable bytes arg + */ + function _getArgBytes(uint256 argOffset, uint256 length) internal pure returns (bytes memory arg) { + uint256 offset = _getImmutableArgsOffset(); + /// @solidity memory-safe-assembly + assembly { + // Grab the free memory pointer. + arg := mload(0x40) + // Store the array length. + mstore(arg, length) + // Copy the array. + calldatacopy(add(arg, 0x20), add(offset, argOffset), length) + // Allocate the memory, rounded up to the next 32 byte boudnary. + mstore(0x40, and(add(add(arg, 0x3f), length), not(0x1f))) + } + } + + /** + * @dev Reads an immutable arg with type address + * @param argOffset The offset of the arg in the immutable args + * @return arg The immutable address arg + */ + function _getArgAddress(uint256 argOffset) internal pure returns (address arg) { + uint256 offset = _getImmutableArgsOffset(); + /// @solidity memory-safe-assembly + assembly { + arg := shr(0x60, calldataload(add(offset, argOffset))) + } + } + + /** + * @dev Reads an immutable arg with type uint256 + * @param argOffset The offset of the arg in the immutable args + * @return arg The immutable uint256 arg + */ + function _getArgUint256(uint256 argOffset) internal pure returns (uint256 arg) { + uint256 offset = _getImmutableArgsOffset(); + /// @solidity memory-safe-assembly + assembly { + arg := calldataload(add(offset, argOffset)) + } + } + + /** + * @dev Reads a uint256 array stored in the immutable args. + * @param argOffset The offset of the arg in the immutable args + * @param length The length of the arg + * @return arg The immutable uint256 array arg + */ + function _getArgUint256Array(uint256 argOffset, uint256 length) internal pure returns (uint256[] memory arg) { + uint256 offset = _getImmutableArgsOffset(); + /// @solidity memory-safe-assembly + assembly { + // Grab the free memory pointer. + arg := mload(0x40) + // Store the array length. + mstore(arg, length) + // Copy the array. + calldatacopy(add(arg, 0x20), add(offset, argOffset), shl(5, length)) + // Allocate the memory. + mstore(0x40, add(add(arg, 0x20), shl(5, length))) + } + } + + /** + * @dev Reads an immutable arg with type uint64 + * @param argOffset The offset of the arg in the immutable args + * @return arg The immutable uint64 arg + */ + function _getArgUint64(uint256 argOffset) internal pure returns (uint64 arg) { + uint256 offset = _getImmutableArgsOffset(); + /// @solidity memory-safe-assembly + assembly { + arg := shr(0xc0, calldataload(add(offset, argOffset))) + } + } + + /** + * @dev Reads an immutable arg with type uint8 + * @param argOffset The offset of the arg in the immutable args + * @return arg The immutable uint8 arg + */ + function _getArgUint8(uint256 argOffset) internal pure returns (uint8 arg) { + uint256 offset = _getImmutableArgsOffset(); + /// @solidity memory-safe-assembly + assembly { + arg := shr(0xf8, calldataload(add(offset, argOffset))) + } + } + + /** + * @dev Reads the offset of the packed immutable args in calldata. + * @return offset The offset of the packed immutable args in calldata. + */ + function _getImmutableArgsOffset() internal pure returns (uint256 offset) { + /// @solidity memory-safe-assembly + assembly { + offset := sub(calldatasize(), shr(0xf0, calldataload(sub(calldatasize(), 2)))) + } + } +} diff --git a/src/libraries/Constants.sol b/src/libraries/Constants.sol index 0ee61c51..3b3a5e86 100644 --- a/src/libraries/Constants.sol +++ b/src/libraries/Constants.sol @@ -2,16 +2,18 @@ pragma solidity 0.8.10; -/// @title Liquidity Book Constants Library -/// @author Trader Joe -/// @notice Set of constants for Liquidity Book contracts +/** + * @title Liquidity Book Constants Library + * @author Trader Joe + * @notice Set of constants for Liquidity Book contracts + */ library Constants { - uint256 internal constant SCALE_OFFSET = 128; + uint8 internal constant SCALE_OFFSET = 128; uint256 internal constant SCALE = 1 << SCALE_OFFSET; uint256 internal constant PRECISION = 1e18; uint256 internal constant BASIS_POINT_MAX = 10_000; /// @dev The expected return after a successful flash loan - bytes32 internal constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan"); + bytes32 internal constant CALLBACK_SUCCESS = keccak256("LBPair.onFlashLoan"); } diff --git a/src/libraries/FeeHelper.sol b/src/libraries/FeeHelper.sol index 6aa57677..73081a22 100644 --- a/src/libraries/FeeHelper.sol +++ b/src/libraries/FeeHelper.sol @@ -3,166 +3,63 @@ pragma solidity 0.8.10; import "./Constants.sol"; -import "./SafeCast.sol"; -import "./SafeMath.sol"; +import "./math/SafeCast.sol"; -/// @title Liquidity Book Fee Helper Library -/// @author Trader Joe -/// @notice Helper contract used for fees calculation +/** + * @title Liquidity Book Fee Helper Library + * @author Trader Joe + * @notice This library contains functions to calculate fees + */ library FeeHelper { using SafeCast for uint256; - using SafeMath for uint256; - /// @dev Structure to store the protocol fees: - /// - binStep: The bin step - /// - baseFactor: The base factor - /// - filterPeriod: The filter period, where the fees stays constant - /// - decayPeriod: The decay period, where the fees are halved - /// - reductionFactor: The reduction factor, used to calculate the reduction of the accumulator - /// - variableFeeControl: The variable fee control, used to control the variable fee, can be 0 to disable them - /// - protocolShare: The share of fees sent to protocol - /// - maxVolatilityAccumulated: The max value of volatility accumulated - /// - volatilityAccumulated: The value of volatility accumulated - /// - volatilityReference: The value of volatility reference - /// - indexRef: The index reference - /// - time: The last time the accumulator was called - struct FeeParameters { - // 144 lowest bits in slot - uint16 binStep; - uint16 baseFactor; - uint16 filterPeriod; - uint16 decayPeriod; - uint16 reductionFactor; - uint24 variableFeeControl; - uint16 protocolShare; - uint24 maxVolatilityAccumulated; - // 112 highest bits in slot - uint24 volatilityAccumulated; - uint24 volatilityReference; - uint24 indexRef; - uint40 time; - } - - /// @dev Structure used during swaps to distributes the fees: - /// - total: The total amount of fees - /// - protocol: The amount of fees reserved for protocol - struct FeesDistribution { - uint128 total; - uint128 protocol; - } - - /// @notice Update the value of the volatility accumulated - /// @param _fp The current fee parameters - /// @param _activeId The current active id - function updateVariableFeeParameters(FeeParameters memory _fp, uint256 _activeId) internal view { - uint256 _deltaT = block.timestamp - _fp.time; - - if (_deltaT >= _fp.filterPeriod || _fp.time == 0) { - _fp.indexRef = uint24(_activeId); - if (_deltaT < _fp.decayPeriod) { - unchecked { - // This can't overflow as `reductionFactor <= BASIS_POINT_MAX` - _fp.volatilityReference = - uint24((uint256(_fp.reductionFactor) * _fp.volatilityAccumulated) / Constants.BASIS_POINT_MAX); - } - } else { - _fp.volatilityReference = 0; - } - } - - _fp.time = (block.timestamp).safe40(); - - updateVolatilityAccumulated(_fp, _activeId); - } - - /// @notice Update the volatility accumulated - /// @param _fp The fee parameter - /// @param _activeId The current active id - function updateVolatilityAccumulated(FeeParameters memory _fp, uint256 _activeId) internal pure { - uint256 volatilityAccumulated = - (_activeId.absSub(_fp.indexRef) * Constants.BASIS_POINT_MAX) + _fp.volatilityReference; - _fp.volatilityAccumulated = volatilityAccumulated > _fp.maxVolatilityAccumulated - ? _fp.maxVolatilityAccumulated - : uint24(volatilityAccumulated); - } - - /// @notice Returns the base fee added to a swap, with 18 decimals - /// @param _fp The current fee parameters - /// @return The fee with 18 decimals precision - function getBaseFee(FeeParameters memory _fp) internal pure returns (uint256) { + /** + * @dev Calculates the fee amount from the amount with fees + * @param amounWithFees The amount with fees + * @param totalFee The total fee + * @return feeAmount The fee amount + */ + function getFeeAmountFrom(uint128 amounWithFees, uint256 totalFee) internal pure returns (uint128) { unchecked { - return uint256(_fp.baseFactor) * _fp.binStep * 1e10; + return ((uint256(amounWithFees) * totalFee + Constants.PRECISION - 1) / Constants.PRECISION).safe128(); } } - /// @notice Returns the variable fee added to a swap, with 18 decimals - /// @param _fp The current fee parameters - /// @return variableFee The variable fee with 18 decimals precision - function getVariableFee(FeeParameters memory _fp) internal pure returns (uint256 variableFee) { - if (_fp.variableFeeControl != 0) { - // Can't overflow as the max value is `max(uint24) * (max(uint24) * max(uint16)) ** 2 < max(uint104)` - // It returns 18 decimals as: - // decimals(variableFeeControl * (volatilityAccumulated * binStep)**2 / 100) = 4 + (4 + 4) * 2 - 2 = 18 - unchecked { - uint256 _prod = uint256(_fp.volatilityAccumulated) * _fp.binStep; - variableFee = (_prod * _prod * _fp.variableFeeControl + 99) / 100; - } + /** + * @dev Calculates the fee amount that will be charged + * @param amount The amount + * @param totalFee The total fee + * @return feeAmount The fee amount + */ + function getFeeAmount(uint128 amount, uint256 totalFee) internal pure returns (uint128) { + unchecked { + uint256 denominator = Constants.PRECISION - totalFee; + return ((uint256(amount) * totalFee + denominator - 1) / denominator).safe128(); } } - /// @notice Return the amount of fees from an amount - /// @dev Rounds amount up, follows `amount = amountWithFees - getFeeAmountFrom(fp, amountWithFees)` - /// @param _fp The current fee parameter - /// @param _amountWithFees The amount of token sent - /// @return The fee amount from the amount sent - function getFeeAmountFrom(FeeParameters memory _fp, uint256 _amountWithFees) internal pure returns (uint256) { - return (_amountWithFees * getTotalFee(_fp) + Constants.PRECISION - 1) / (Constants.PRECISION); - } - - /// @notice Return the fees to add to an amount - /// @dev Rounds amount up, follows `amountWithFees = amount + getFeeAmount(fp, amount)` - /// @param _fp The current fee parameter - /// @param _amount The amount of token sent - /// @return The fee amount to add to the amount - function getFeeAmount(FeeParameters memory _fp, uint256 _amount) internal pure returns (uint256) { - uint256 _fee = getTotalFee(_fp); - uint256 _denominator = Constants.PRECISION - _fee; - return (_amount * _fee + _denominator - 1) / _denominator; - } - - /// @notice Return the fees added when an user adds liquidity and change the ratio in the active bin - /// @dev Rounds amount up - /// @param _fp The current fee parameter - /// @param _amountWithFees The amount of token sent - /// @return The fee amount - function getFeeAmountForC(FeeParameters memory _fp, uint256 _amountWithFees) internal pure returns (uint256) { - uint256 _fee = getTotalFee(_fp); - uint256 _denominator = Constants.PRECISION * Constants.PRECISION; - return (_amountWithFees * _fee * (_fee + Constants.PRECISION) + _denominator - 1) / _denominator; - } - - /// @notice Return the fees distribution added to an amount - /// @param _fp The current fee parameter - /// @param _fees The fee amount - /// @return fees The fee distribution - function getFeeAmountDistribution(FeeParameters memory _fp, uint256 _fees) - internal - pure - returns (FeesDistribution memory fees) - { - fees.total = _fees.safe128(); - // unsafe math is fine because total >= protocol + /** + * @dev Calculates the composition fee amount from the amount with fees + * @param amountWithFees The amount with fees + * @param totalFee The total fee + * @return The amount with fees + */ + function getCompositionFee(uint128 amountWithFees, uint256 totalFee) internal pure returns (uint128) { unchecked { - fees.protocol = uint128((_fees * _fp.protocolShare) / Constants.BASIS_POINT_MAX); + uint256 denominator = Constants.PRECISION * Constants.PRECISION; + return (uint256(amountWithFees) * totalFee * (totalFee + Constants.PRECISION) / denominator).safe128(); } } - /// @notice Return the total fee, i.e. baseFee + variableFee - /// @param _fp The current fee parameter - /// @return The total fee, with 18 decimals - function getTotalFee(FeeParameters memory _fp) private pure returns (uint256) { + /** + * @dev Calculates the protocol fee amount from the fee amount and the protocol share + * @param feeAmount The fee amount + * @param protocolShare The protocol share + * @return protocolFeeAmount The protocol fee amount + */ + function getProtocolFeeAmount(uint128 feeAmount, uint128 protocolShare) internal pure returns (uint128) { unchecked { - return getBaseFee(_fp) + getVariableFee(_fp); + return (uint256(feeAmount) * protocolShare / Constants.BASIS_POINT_MAX).safe128(); } } } diff --git a/src/libraries/ImmutableClone.sol b/src/libraries/ImmutableClone.sol new file mode 100644 index 00000000..ef7b905b --- /dev/null +++ b/src/libraries/ImmutableClone.sol @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +/** + * @title Liquidity Book Immutable Clone Library + * @notice Minimal immutable proxy library. + * @author Trader Joe + * @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibClone.sol) + * @author Minimal proxy by 0age (https://github.com/0age) + * @author Clones with immutable args by wighawag, zefram.eth, Saw-mon & Natalie + * (https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args) + * @dev Minimal proxy: + * Although the sw0nt pattern saves 5 gas over the erc-1167 pattern during runtime, + * it is not supported out-of-the-box on Etherscan. Hence, we choose to use the 0age pattern, + * which saves 4 gas over the erc-1167 pattern during runtime, and has the smallest bytecode. + * @dev Clones with immutable args (CWIA): + * The implementation of CWIA here doesn't implements a `receive()` as it is not needed for LB. + */ +library ImmutableClone { + error DeploymentFailed(); + error PackedDataTooBig(); + + /** + * @dev Deploys a deterministic clone of `implementation` using immutable arguments encoded in `data`, with `salt` + * @param implementation The address of the implementation + * @param data The encoded immutable arguments + * @param salt The salt + */ + function cloneDeterministic(address implementation, bytes memory data, bytes32 salt) + internal + returns (address instance) + { + assembly { + // Compute the boundaries of the data and cache the memory slots around it. + let mBefore2 := mload(sub(data, 0x40)) + let mBefore1 := mload(sub(data, 0x20)) + let dataLength := mload(data) + let dataEnd := add(add(data, 0x20), dataLength) + let mAfter1 := mload(dataEnd) + + // +2 bytes for telling how much data there is appended to the call. + let extraLength := add(dataLength, 2) + // The `creationSize` is `extraLength + 63` + // The `runSize` is `creationSize - 10`. + + // if `extraLength` is greater than `0xffca` revert as the `creationSize` would be greater than `0xffff`. + if gt(extraLength, 0xffca) { + // Store the function selector of `PackedDataTooBig()`. + mstore(0x00, 0xc8c78139) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + + /** + * ---------------------------------------------------------------------------------------------------+ + * CREATION (10 bytes) | + * ---------------------------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------------------------| + * 61 runSize | PUSH2 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * ---------------------------------------------------------------------------------------------------| + * RUNTIME (98 bytes + extraLength) | + * ---------------------------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------------------------| + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 3d | RETURNDATASIZE | 0 cds | | + * 3d | RETURNDATASIZE | 0 0 cds | | + * 37 | CALLDATACOPY | | [0..cds): calldata | + * | + * ::: keep some values in stack :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 0 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 0 0 0 | [0..cds): calldata | + * 61 extra | PUSH2 extra | e 0 0 0 0 | [0..cds): calldata | + * | + * ::: copy extra data to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 80 | DUP1 | e e 0 0 0 0 | [0..cds): calldata | + * 60 0x35 | PUSH1 0x35 | 0x35 e e 0 0 0 0 | [0..cds): calldata | + * 36 | CALLDATASIZE | cds 0x35 e e 0 0 0 0 | [0..cds): calldata | + * 39 | CODECOPY | e 0 0 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * | + * ::: delegate call to the implementation contract ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds e 0 0 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * 01 | ADD | cds+e 0 0 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * 3d | RETURNDATASIZE | 0 cds+e 0 0 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * 73 addr | PUSH20 addr | addr 0 cds+e 0 0 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * 5a | GAS | gas addr 0 cds+e 0 0 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * f4 | DELEGATECALL | success 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * | + * ::: copy return data to memory ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds success 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * 3d | RETURNDATASIZE | rds rds success 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * 93 | SWAP4 | 0 rds success 0 rds | [0..cds): calldata, [cds..cds+e): extraData | + * 80 | DUP1 | 0 0 rds success 0 rds | [0..cds): calldata, [cds..cds+e): extraData | + * 3e | RETURNDATACOPY | success 0 rds | [0..rds): returndata | + * | + * 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0..rds): returndata | + * 57 | JUMPI | 0 rds | [0..rds): returndata | + * | + * ::: revert ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * fd | REVERT | | [0..rds): returndata | + * | + * ::: return ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | 0 rds | [0..rds): returndata | + * f3 | RETURN | | [0..rds): returndata | + * ---------------------------------------------------------------------------------------------------+ + */ + // Write the bytecode before the data. + mstore(data, 0x5af43d3d93803e603357fd5bf3) + // Write the address of the implementation. + mstore(sub(data, 0x0d), implementation) + mstore( + sub(data, 0x21), + or( + shl(0xd8, add(extraLength, 0x35)), + or(shl(0x48, extraLength), 0x6100003d81600a3d39f3363d3d373d3d3d3d610000806035363936013d73) + ) + ) + mstore(dataEnd, shl(0xf0, extraLength)) + + // Create the instance. + instance := create2(0, sub(data, 0x1f), add(extraLength, 0x3f), salt) + + // If `instance` is zero, revert. + if iszero(instance) { + // Store the function selector of `DeploymentFailed()`. + mstore(0x00, 0x30116425) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + + // Restore the overwritten memory surrounding `data`. + mstore(dataEnd, mAfter1) + mstore(data, dataLength) + mstore(sub(data, 0x20), mBefore1) + mstore(sub(data, 0x40), mBefore2) + } + } + + /** + * @dev Returns the initialization code hash of the clone of `implementation` + * using immutable arguments encoded in `data`. + * Used for mining vanity addresses with create2crunch. + * @param implementation The address of the implementation contract. + * @param data The encoded immutable arguments. + * @return hash The initialization code hash. + */ + function initCodeHash(address implementation, bytes memory data) internal pure returns (bytes32 hash) { + assembly { + // Compute the boundaries of the data and cache the memory slots around it. + let mBefore2 := mload(sub(data, 0x40)) + let mBefore1 := mload(sub(data, 0x20)) + let dataLength := mload(data) + let dataEnd := add(add(data, 0x20), dataLength) + let mAfter1 := mload(dataEnd) + + // +2 bytes for telling how much data there is appended to the call. + let extraLength := add(dataLength, 2) + // The `creationSize` is `extraLength + 63` + // The `runSize` is `creationSize - 10`. + + // if `extraLength` is greater than `0xffca` revert as the `creationSize` would be greater than `0xffff`. + if gt(extraLength, 0xffca) { + // Store the function selector of `PackedDataTooBig()`. + mstore(0x00, 0xc8c78139) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + + // Write the bytecode before the data. + mstore(data, 0x5af43d3d93803e603357fd5bf3) + // Write the address of the implementation. + mstore(sub(data, 0x0d), implementation) + mstore( + sub(data, 0x21), + or( + shl(0xd8, add(extraLength, 0x35)), + or(shl(0x48, extraLength), 0x6100003d81600a3d39f3363d3d373d3d3d3d610000806035363936013d73) + ) + ) + mstore(dataEnd, shl(0xf0, extraLength)) + + // Create the instance. + hash := keccak256(sub(data, 0x1f), add(extraLength, 0x3f)) + + // Restore the overwritten memory surrounding `data`. + mstore(dataEnd, mAfter1) + mstore(data, dataLength) + mstore(sub(data, 0x20), mBefore1) + mstore(sub(data, 0x40), mBefore2) + } + } + + /** + * @dev Returns the address of the deterministic clone of + * `implementation` using immutable arguments encoded in `data`, with `salt`, by `deployer`. + * @param implementation The address of the implementation. + * @param data The immutable arguments of the implementation. + * @param salt The salt used to compute the address. + * @param deployer The address of the deployer. + * @return predicted The predicted address. + */ + function predictDeterministicAddress(address implementation, bytes memory data, bytes32 salt, address deployer) + internal + pure + returns (address predicted) + { + bytes32 hash = initCodeHash(implementation, data); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /** + * @dev Returns the address when a contract with initialization code hash, + * `hash`, is deployed with `salt`, by `deployer`. + * @param hash The initialization code hash. + * @param salt The salt used to compute the address. + * @param deployer The address of the deployer. + * @return predicted The predicted address. + */ + function predictDeterministicAddress(bytes32 hash, bytes32 salt, address deployer) + internal + pure + returns (address predicted) + { + /// @solidity memory-safe-assembly + assembly { + // Compute the boundaries of the data and cache the memory slots around it. + let mBefore := mload(0x35) + + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, hash) + mstore(0x01, shl(96, deployer)) + mstore(0x15, salt) + predicted := keccak256(0x00, 0x55) + + // Restore the part of the free memory pointer that has been overwritten. + mstore(0x35, mBefore) + } + } +} diff --git a/src/libraries/JoeLibrary.sol b/src/libraries/JoeLibrary.sol index d09e67a9..ba4ec6d7 100644 --- a/src/libraries/JoeLibrary.sol +++ b/src/libraries/JoeLibrary.sol @@ -2,12 +2,17 @@ pragma solidity 0.8.10; -import "../LBErrors.sol"; - -/// @title Liquidity Book Joe Library Helper Library -/// @author Trader Joe -/// @notice Helper contract used for Joe V1 related calculations +/** + * @title Liquidity Book Joe Library Helper Library + * @author Trader Joe + * @notice Helper contract used for Joe V1 related calculations + */ library JoeLibrary { + error JoeLibrary__AddressZero(); + error JoeLibrary__IdenticalAddresses(); + error JoeLibrary__InsufficientAmount(); + error JoeLibrary__InsufficientLiquidity(); + // returns sorted token addresses, used to handle return values from pairs sorted in this order function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { if (tokenA == tokenB) revert JoeLibrary__IdenticalAddresses(); diff --git a/src/libraries/Math512Bits.sol b/src/libraries/Math512Bits.sol deleted file mode 100644 index 941a0b80..00000000 --- a/src/libraries/Math512Bits.sol +++ /dev/null @@ -1,232 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import "../LBErrors.sol"; -import "./BitMath.sol"; - -/// @title Liquidity Book Math Helper Library -/// @author Trader Joe -/// @notice Helper contract used for full precision calculations -library Math512Bits { - using BitMath for uint256; - - /// @notice Calculates floor(x*y÷denominator) with full precision - /// The result will be rounded down - /// - /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv - /// - /// Requirements: - /// - The denominator cannot be zero - /// - The result must fit within uint256 - /// - /// Caveats: - /// - This function does not work with fixed-point numbers - /// - /// @param x The multiplicand as an uint256 - /// @param y The multiplier as an uint256 - /// @param denominator The divisor as an uint256 - /// @return result The result as an uint256 - function mulDivRoundDown(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { - (uint256 prod0, uint256 prod1) = _getMulProds(x, y); - - return _getEndOfDivRoundDown(x, y, denominator, prod0, prod1); - } - - /// @notice Calculates x * y >> offset with full precision - /// The result will be rounded down - /// - /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv - /// - /// Requirements: - /// - The offset needs to be strictly lower than 256 - /// - The result must fit within uint256 - /// - /// Caveats: - /// - This function does not work with fixed-point numbers - /// - /// @param x The multiplicand as an uint256 - /// @param y The multiplier as an uint256 - /// @param offset The offset as an uint256, can't be greater than 256 - /// @return result The result as an uint256 - function mulShiftRoundDown(uint256 x, uint256 y, uint256 offset) internal pure returns (uint256 result) { - if (offset > 255) revert Math512Bits__OffsetOverflows(offset); - - (uint256 prod0, uint256 prod1) = _getMulProds(x, y); - - if (prod0 != 0) result = prod0 >> offset; - if (prod1 != 0) { - // Make sure the result is less than 2^256. - if (prod1 >= 1 << offset) revert Math512Bits__MulShiftOverflow(prod1, offset); - - unchecked { - result += prod1 << (256 - offset); - } - } - } - - /// @notice Calculates x * y >> offset with full precision - /// The result will be rounded up - /// - /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv - /// - /// Requirements: - /// - The offset needs to be strictly lower than 256 - /// - The result must fit within uint256 - /// - /// Caveats: - /// - This function does not work with fixed-point numbers - /// - /// @param x The multiplicand as an uint256 - /// @param y The multiplier as an uint256 - /// @param offset The offset as an uint256, can't be greater than 256 - /// @return result The result as an uint256 - function mulShiftRoundUp(uint256 x, uint256 y, uint256 offset) internal pure returns (uint256 result) { - unchecked { - result = mulShiftRoundDown(x, y, offset); - if (mulmod(x, y, 1 << offset) != 0) result += 1; - } - } - - /// @notice Calculates x << offset / y with full precision - /// The result will be rounded down - /// - /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv - /// - /// Requirements: - /// - The offset needs to be strictly lower than 256 - /// - The result must fit within uint256 - /// - /// Caveats: - /// - This function does not work with fixed-point numbers - /// - /// @param x The multiplicand as an uint256 - /// @param offset The number of bit to shift x as an uint256 - /// @param denominator The divisor as an uint256 - /// @return result The result as an uint256 - function shiftDivRoundDown(uint256 x, uint256 offset, uint256 denominator) internal pure returns (uint256 result) { - if (offset > 255) revert Math512Bits__OffsetOverflows(offset); - uint256 prod0; - uint256 prod1; - - prod0 = x << offset; // Least significant 256 bits of the product - unchecked { - prod1 = x >> (256 - offset); // Most significant 256 bits of the product - } - - return _getEndOfDivRoundDown(x, 1 << offset, denominator, prod0, prod1); - } - - /// @notice Calculates x << offset / y with full precision - /// The result will be rounded up - /// - /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv - /// - /// Requirements: - /// - The offset needs to be strictly lower than 256 - /// - The result must fit within uint256 - /// - /// Caveats: - /// - This function does not work with fixed-point numbers - /// - /// @param x The multiplicand as an uint256 - /// @param offset The number of bit to shift x as an uint256 - /// @param denominator The divisor as an uint256 - /// @return result The result as an uint256 - function shiftDivRoundUp(uint256 x, uint256 offset, uint256 denominator) internal pure returns (uint256 result) { - result = shiftDivRoundDown(x, offset, denominator); - unchecked { - if (mulmod(x, 1 << offset, denominator) != 0) result += 1; - } - } - - /// @notice Helper function to return the result of `x * y` as 2 uint256 - /// @param x The multiplicand as an uint256 - /// @param y The multiplier as an uint256 - /// @return prod0 The least significant 256 bits of the product - /// @return prod1 The most significant 256 bits of the product - function _getMulProds(uint256 x, uint256 y) private pure returns (uint256 prod0, uint256 prod1) { - // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use - // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 - // variables such that product = prod1 * 2^256 + prod0. - assembly { - let mm := mulmod(x, y, not(0)) - prod0 := mul(x, y) - prod1 := sub(sub(mm, prod0), lt(mm, prod0)) - } - } - - /// @notice Helper function to return the result of `x * y / denominator` with full precision - /// @param x The multiplicand as an uint256 - /// @param y The multiplier as an uint256 - /// @param denominator The divisor as an uint256 - /// @param prod0 The least significant 256 bits of the product - /// @param prod1 The most significant 256 bits of the product - /// @return result The result as an uint256 - function _getEndOfDivRoundDown(uint256 x, uint256 y, uint256 denominator, uint256 prod0, uint256 prod1) - private - pure - returns (uint256 result) - { - // Handle non-overflow cases, 256 by 256 division - if (prod1 == 0) { - unchecked { - result = prod0 / denominator; - } - } else { - // Make sure the result is less than 2^256. Also prevents denominator == 0 - if (prod1 >= denominator) revert Math512Bits__MulDivOverflow(prod1, denominator); - - // Make division exact by subtracting the remainder from [prod1 prod0]. - uint256 remainder; - assembly { - // Compute remainder using mulmod. - remainder := mulmod(x, y, denominator) - - // Subtract 256 bit number from 512 bit number. - prod1 := sub(prod1, gt(remainder, prod0)) - prod0 := sub(prod0, remainder) - } - - // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1 - // See https://cs.stackexchange.com/q/138556/92363 - unchecked { - // Does not overflow because the denominator cannot be zero at this stage in the function - uint256 lpotdod = denominator & (~denominator + 1); - assembly { - // Divide denominator by lpotdod. - denominator := div(denominator, lpotdod) - - // Divide [prod1 prod0] by lpotdod. - prod0 := div(prod0, lpotdod) - - // Flip lpotdod such that it is 2^256 / lpotdod. If lpotdod is zero, then it becomes one - lpotdod := add(div(sub(0, lpotdod), lpotdod), 1) - } - - // Shift in bits from prod1 into prod0 - prod0 |= prod1 * lpotdod; - - // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such - // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for - // four bits. That is, denominator * inv = 1 mod 2^4 - uint256 inverse = (3 * denominator) ^ 2; - - // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works - // in modular arithmetic, doubling the correct bits in each step - inverse *= 2 - denominator * inverse; // inverse mod 2^8 - inverse *= 2 - denominator * inverse; // inverse mod 2^16 - inverse *= 2 - denominator * inverse; // inverse mod 2^32 - inverse *= 2 - denominator * inverse; // inverse mod 2^64 - inverse *= 2 - denominator * inverse; // inverse mod 2^128 - inverse *= 2 - denominator * inverse; // inverse mod 2^256 - - // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. - // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is - // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 - // is no longer required. - result = prod0 * inverse; - } - } - } -} diff --git a/src/libraries/OracleHelper.sol b/src/libraries/OracleHelper.sol new file mode 100644 index 00000000..f1d6cb56 --- /dev/null +++ b/src/libraries/OracleHelper.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "./math/SampleMath.sol"; +import "./math/SafeCast.sol"; +import "./PairParameterHelper.sol"; + +/** + * @title Liquidity Book Oracle Helper Library + * @author Trader Joe + * @notice This library contains functions to manage the oracle + * The oracle samples are stored in a single bytes32 array. + * Each sample is encoded as follows: + * 0 - 16: oracle length (16 bits) + * 16 - 80: cumulative id (64 bits) + * 80 - 144: cumulative volatility accumulated (64 bits) + * 144 - 208: cumulative bin crossed (64 bits) + * 208 - 216: sample lifetime (8 bits) + * 216 - 256: sample creation timestamp (40 bits) + */ +library OracleHelper { + using SampleMath for bytes32; + using SafeCast for uint256; + using PairParameterHelper for bytes32; + + error OracleHelper__InvalidOracleId(); + error OracleHelper__NewLengthTooSmall(); + error OracleHelper__LookUpTimestampTooOld(); + + struct Oracle { + bytes32[65535] samples; + } + + uint256 internal constant _MAX_SAMPLE_LIFETIME = 120 seconds; + + /** + * @dev Returns the sample at the given oracleId + * @param oracle The oracle + * @param oracleId The oracle id + * @return sample The sample + */ + function getSample(Oracle storage oracle, uint16 oracleId) internal view returns (bytes32 sample) { + if (oracleId == 0) revert OracleHelper__InvalidOracleId(); + + unchecked { + sample = oracle.samples[oracleId - 1]; + } + } + + /** + * @dev Returns the sample at the given timestamp. If the timestamp is not in the oracle, it returns the closest sample + * @param oracle The oracle + * @param oracleId The oracle id + * @param lookUpTimestamp The timestamp to look up + * @return lastUpdate The last update timestamp + * @return cumulativeId The cumulative id + * @return cumulativeVolatility The cumulative volatility + * @return cumulativeBinCrossed The cumulative bin crossed + */ + function getSampleAt(Oracle storage oracle, uint16 oracleId, uint40 lookUpTimestamp) + internal + view + returns (uint40 lastUpdate, uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) + { + bytes32 sample = getSample(oracle, oracleId); + uint16 length = sample.getOracleLength(); + + assembly { + oracleId := mod(oracleId, length) + } + bytes32 oldestSample = oracle.samples[oracleId]; + + // Oreacle is not fully initialized yet + if (oldestSample >> SampleMath._SHIFT_CUMULATIVE_ID == 0) { + length = oracleId; + oldestSample = oracle.samples[0]; + } + + if (oldestSample.getSampleLastUpdate() > lookUpTimestamp) revert OracleHelper__LookUpTimestampTooOld(); + + lastUpdate = sample.getSampleLastUpdate(); + if (lastUpdate <= lookUpTimestamp) { + return ( + lastUpdate, sample.getCumulativeId(), sample.getCumulativeVolatility(), sample.getCumulativeBinCrossed() + ); + } else { + lastUpdate = lookUpTimestamp; + } + + (bytes32 prevSample, bytes32 nextSample) = binarySearch(oracle, oracleId, lookUpTimestamp, length); + + uint40 weightPrev = nextSample.getSampleLastUpdate() - lookUpTimestamp; + uint40 weightNext = lookUpTimestamp - sample.getSampleLastUpdate(); + + (cumulativeId, cumulativeVolatility, cumulativeBinCrossed) = + sample.getWeightedAverage(prevSample, weightPrev, weightNext); + } + + /** + * @dev Binary search to find the 2 samples surrounding the given timestamp + * @param oracle The oracle + * @param oracleId The oracle id + * @param lookUpTimestamp The timestamp to look up + * @param length The oracle length + * @return prevSample The previous sample + * @return nextSample The next sample + */ + function binarySearch(Oracle storage oracle, uint16 oracleId, uint40 lookUpTimestamp, uint16 length) + internal + view + returns (bytes32, bytes32) + { + uint16 low = 0; + uint16 high = length - 1; + + bytes32 sample; + uint40 sampleLastUpdate; + + while (low < high) { + uint16 mid = (low + high) / 2; + + assembly { + oracleId := addmod(oracleId, mid, length) + } + + sample = oracle.samples[oracleId]; + sampleLastUpdate = sample.getSampleCreation(); + + if (sampleLastUpdate > lookUpTimestamp) { + high = mid - 1; + } else if (sampleLastUpdate < lookUpTimestamp) { + low = mid + 1; + } else { + return (sample, sample); + } + } + + if (lookUpTimestamp < sampleLastUpdate) { + unchecked { + if (oracleId == 0) { + oracleId = length; + } + + return (oracle.samples[oracleId - 1], sample); + } + } else { + assembly { + oracleId := addmod(oracleId, 1, length) + } + + return (sample, oracle.samples[oracleId]); + } + } + + /** + * @dev Sets the sample at the given oracleId + * @param oracle The oracle + * @param oracleId The oracle id + * @param sample The sample + */ + function setSample(Oracle storage oracle, uint16 oracleId, bytes32 sample) internal { + if (oracleId == 0) revert OracleHelper__InvalidOracleId(); + + unchecked { + oracle.samples[oracleId - 1] = sample; + } + } + + /** + * @dev Updates the oracle + * @param oracle The oracle + * @param parameters The parameters + * @param activeId The active id + */ + function update(Oracle storage oracle, bytes32 parameters, uint24 activeId) internal { + uint16 oracleId = parameters.getOracleId(); + if (oracleId == 0) return; + + bytes32 sample = getSample(oracle, oracleId); + + uint40 createdAt = sample.getSampleCreation(); + uint40 deltaTime = block.timestamp.safe40() - createdAt; + + if (deltaTime > 0) { + (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = sample.update( + deltaTime, activeId, parameters.getVolatilityAccumulated(), parameters.getDeltaId(activeId) + ); + + uint16 length = sample.getOracleLength(); + + if (deltaTime > _MAX_SAMPLE_LIFETIME) { + deltaTime = 0; + createdAt = uint40(block.timestamp); + assembly { + oracleId := add(mod(oracleId, length), 1) + } + } + + sample = SampleMath.encode( + length, cumulativeId, cumulativeVolatility, cumulativeBinCrossed, uint8(deltaTime), createdAt + ); + + setSample(oracle, oracleId, sample); + } + } + + /** + * @dev Increases the oracle length + * @param oracle The oracle + * @param oracleId The oracle id + * @param newLength The new length + */ + function inreaseLength(Oracle storage oracle, uint16 oracleId, uint16 newLength) internal { + bytes32 sample = getSample(oracle, oracleId); + uint16 length = sample.getOracleLength(); + + if (length >= newLength) revert OracleHelper__NewLengthTooSmall(); + + for (uint256 i = length; i < newLength;) { + oracle.samples[i] = bytes32(uint256(newLength)); + + unchecked { + ++i; + } + } + + if (oracleId != length) { + setSample(oracle, oracleId, (sample ^ bytes32(uint256(length))) | bytes32(uint256(newLength))); + } + } +} diff --git a/src/libraries/PairParameterHelper.sol b/src/libraries/PairParameterHelper.sol new file mode 100644 index 00000000..69f38fe5 --- /dev/null +++ b/src/libraries/PairParameterHelper.sol @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "./Constants.sol"; +import "./math/SafeCast.sol"; + +/** + * @title Liquidity Book Pair Parameter Helper Library + * @author Trader Joe + * @dev This library contains functions to get and set parameters of a pair + * The parameters are stored in a single bytes32 variable in the following format: + * [0 - 16[: base factor (16 bits) + * [16 - 28[: filter period (12 bits) + * [28 - 40[: decay period (12 bits) + * [40 - 54[: reduction factor (14 bits) + * [54 - 78[: variable fee control (24 bits) + * [78 - 92[: protocol share (14 bits) + * [92 - 112[: max volatility accumulated (20 bits) + * [112 - 132[: volatility accumulated (20 bits) + * [132 - 152[: volatility reference (20 bits) + * [152 - 176[: index reference (24 bits) + * [176 - 216[: time of last update (40 bits) + * [216 - 232[: oracle index (16 bits) + * [232 - 256[: active index (24 bits) + */ +library PairParameterHelper { + using SafeCast for uint256; + + error PairParametersHelper__InvalidParameter(); + + uint256 internal constant _FILTER_PERIOD_OFFSET = 16; + uint256 internal constant _DECAY_PERIOD_OFFSET = 28; + uint256 internal constant _REDUCTION_FACTOR_OFFSET = 40; + uint256 internal constant _VARIABLE_FEE_CONTROL_OFFSET = 54; + uint256 internal constant _PROTOCOL_SHARE_OFFSET = 78; + uint256 internal constant _MAX_VOL_ACC_OFFSET = 92; + uint256 internal constant _VOL_ACC_OFFSET = 112; + uint256 internal constant _VOLATILITY_REFERENCE_OFFSET = 132; + uint256 internal constant _INDEX_REF_OFFSET = 152; + uint256 internal constant _TIME_OFFSET = 176; + uint256 internal constant _ORACLE_ID_OFFSET = 216; + uint256 internal constant _ACTIVE_ID_OFFSET = 232; + + uint256 internal constant _STATIC_PARAMETER_MASK = 0xffffffffffffffffffffffffffff; + uint256 internal constant _DYNAMIC_PARAMETER_MASK = 0xffffffffffffffffffffffffffffffffffff; + + uint256 internal constant _BASE_FACTOR_MASK = 0xffff; + uint256 internal constant _FILTER_PERIOD_MASK = 0xfff; + uint256 internal constant _DECAY_PERIOD_MASK = 0xfff; + uint256 internal constant _REDUCTION_FACTOR_MASK = 0x3fff; + uint256 internal constant _VARIABLE_FEE_CONTROL_MASK = 0xffffff; + uint256 internal constant _PROTOCOL_SHARE_MASK = 0x3fff; + uint256 internal constant _VOLATILITY_MASK = 0xfffff; + uint256 internal constant _INDEX_REF_MASK = 0xffffff; + uint256 internal constant _TIME_MASK = 0xffffffffff; + uint256 internal constant _ORACLE_ID_MASK = 0xffff; + uint256 internal constant _ACTIVE_ID_MASK = 0xffffff; + + uint256 internal constant _MAX_BASIS_POINTS = 10_000; + uint256 internal constant _MAX_PROTOCOL_SHARE = 2_500; + uint256 internal constant _PRECISION = 1e18; + uint256 internal constant _PRECISION_SUB_ONE = _PRECISION - 1; + + /** + * @dev Get the base factor from the encoded pair parameters + * @param params The encoded pair parameters + * @return baseFactor The base factor + */ + function getBaseFactor(bytes32 params) internal pure returns (uint16 baseFactor) { + assembly { + baseFactor := and(params, _BASE_FACTOR_MASK) + } + } + + /** + * @dev Get the filter period from the encoded pair parameters + * @param params The encoded pair parameters + * @return filterPeriod The filter period + */ + function getFilterPeriod(bytes32 params) internal pure returns (uint16 filterPeriod) { + assembly { + filterPeriod := shr(_FILTER_PERIOD_OFFSET, and(params, _FILTER_PERIOD_MASK)) + } + } + + /** + * @dev Get the decay period from the encoded pair parameters + * @param params The encoded pair parameters + * @return decayPeriod The decay period + */ + function getDecayPeriod(bytes32 params) internal pure returns (uint16 decayPeriod) { + assembly { + decayPeriod := shr(_DECAY_PERIOD_OFFSET, and(params, _DECAY_PERIOD_MASK)) + } + } + + /** + * @dev Get the reduction factor from the encoded pair parameters + * @param params The encoded pair parameters + * @return reductionFactor The reduction factor + */ + function getReductionFactor(bytes32 params) internal pure returns (uint16 reductionFactor) { + assembly { + reductionFactor := shr(_REDUCTION_FACTOR_OFFSET, and(params, _REDUCTION_FACTOR_MASK)) + } + } + + /** + * @dev Get the variable fee control from the encoded pair parameters + * @param params The encoded pair parameters + * @return variableFeeControl The variable fee control + */ + function getVariableFeeControl(bytes32 params) internal pure returns (uint24 variableFeeControl) { + assembly { + variableFeeControl := shr(_VARIABLE_FEE_CONTROL_OFFSET, and(params, _VARIABLE_FEE_CONTROL_MASK)) + } + } + + /** + * @dev Get the protocol share from the encoded pair parameters + * @param params The encoded pair parameters + * @return protocolShare The protocol share + */ + function getProtocolShare(bytes32 params) internal pure returns (uint16 protocolShare) { + assembly { + protocolShare := shr(_PROTOCOL_SHARE_OFFSET, and(params, _PROTOCOL_SHARE_MASK)) + } + } + + /** + * @dev Get the max volatility accumulated from the encoded pair parameters + * @param params The encoded pair parameters + * @return maxVolatilityAccumulated The max volatility accumulated + */ + function getMaxVolatilityAccumulated(bytes32 params) internal pure returns (uint24 maxVolatilityAccumulated) { + assembly { + maxVolatilityAccumulated := shr(_MAX_VOL_ACC_OFFSET, and(params, _VOLATILITY_MASK)) + } + } + + /** + * @dev Get the volatility accumulated from the encoded pair parameters + * @param params The encoded pair parameters + * @return volatilityAccumulated The volatility accumulated + */ + function getVolatilityAccumulated(bytes32 params) internal pure returns (uint24 volatilityAccumulated) { + assembly { + volatilityAccumulated := shr(_VOL_ACC_OFFSET, and(params, _VOLATILITY_MASK)) + } + } + + /** + * @dev Get the volatility reference from the encoded pair parameters + * @param params The encoded pair parameters + * @return volatilityReference The volatility reference + */ + function getVolatilityReference(bytes32 params) internal pure returns (uint24 volatilityReference) { + assembly { + volatilityReference := shr(_VOLATILITY_REFERENCE_OFFSET, and(params, _VOLATILITY_MASK)) + } + } + + /** + * @dev Get the index reference from the encoded pair parameters + * @param params The encoded pair parameters + * @return idReference The index reference + */ + function getIdReference(bytes32 params) internal pure returns (uint24 idReference) { + assembly { + idReference := shr(_INDEX_REF_OFFSET, and(params, _INDEX_REF_MASK)) + } + } + + /** + * @dev Get the time of last update from the encoded pair parameters + * @param params The encoded pair parameters + * @return timeOflastUpdate The time of last update + */ + function getTimeOfLastUpdate(bytes32 params) internal pure returns (uint40 timeOflastUpdate) { + assembly { + timeOflastUpdate := shr(_TIME_OFFSET, and(params, _TIME_MASK)) + } + } + + /** + * @dev Get the oracle id from the encoded pair parameters + * @param params The encoded pair parameters + * @return oracleId The oracle id + */ + function getOracleId(bytes32 params) internal pure returns (uint16 oracleId) { + assembly { + oracleId := shr(_ORACLE_ID_OFFSET, params) + } + } + + /** + * @dev Get the delta between the active index and the reference index + * @param params The encoded pair parameters + * @param activeId The active index + * @return The delta + */ + function getDeltaId(bytes32 params, uint24 activeId) internal pure returns (uint24) { + uint24 id = getActiveId(params); + unchecked { + return activeId > id ? activeId - id : id - activeId; + } + } + + /** + * @dev Get the active index from the encoded pair parameters + * @param params The encoded pair parameters + * @return activeId The active index + */ + function getActiveId(bytes32 params) internal pure returns (uint24 activeId) { + assembly { + activeId := shr(_ACTIVE_ID_OFFSET, params) + } + } + + /** + * @dev Set the oracle id in the encoded pair parameters + * @param params The encoded pair parameters + * @param oracleId The oracle id + * @return The updated encoded pair parameters + */ + function setOracleId(bytes32 params, uint16 oracleId) internal pure returns (bytes32) { + assembly { + params := and(params, shl(_ORACLE_ID_OFFSET, not(_ORACLE_ID_MASK))) + params := or(params, shl(_ORACLE_ID_OFFSET, oracleId)) + } + return params; + } + + /** + * @dev Set the volatility reference in the encoded pair parameters + * @param params The encoded pair parameters + * @param volRef The volatility reference + * @return The updated encoded pair parameters + */ + function setVolatilityReference(bytes32 params, uint24 volRef) internal pure returns (bytes32) { + if (volRef > _VOLATILITY_MASK) revert PairParametersHelper__InvalidParameter(); + + assembly { + params := and(params, shl(_VOLATILITY_REFERENCE_OFFSET, not(_VOLATILITY_MASK))) + params := or(params, shl(_VOLATILITY_REFERENCE_OFFSET, volRef)) + } + return params; + } + + /** + * @dev Set the active id in the encoded pair parameters + * @param params The encoded pair parameters + * @param activeId The active id + * @return newParams The updated encoded pair parameters + */ + function setActiveId(bytes32 params, uint24 activeId) internal pure returns (bytes32 newParams) { + assembly { + params := and(params, shl(_ACTIVE_ID_OFFSET, not(_ACTIVE_ID_MASK))) + newParams := or(params, shl(_ACTIVE_ID_OFFSET, activeId)) + } + } + + /** + * @dev Sets the static fee parameters in the encoded pair parameters + * @param params The encoded pair parameters + * @param baseFactor The base factor + * @param filterPeriod The filter period + * @param decayPeriod The decay period + * @param reductionFactor The reduction factor + * @param variableFeeControl The variable fee control + * @param protocolShare The protocol share + * @param maxVolatilityAccumulated The max volatility accumulated + * @return The updated encoded pair parameters + */ + function setStaticFeeParameters( + bytes32 params, + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated + ) internal pure returns (bytes32) { + if ( + filterPeriod > decayPeriod || decayPeriod > _DECAY_PERIOD_MASK || reductionFactor > _MAX_BASIS_POINTS + || protocolShare > _MAX_PROTOCOL_SHARE || maxVolatilityAccumulated > _VOLATILITY_MASK + ) revert PairParametersHelper__InvalidParameter(); + + assembly { + params := and(params, not(_STATIC_PARAMETER_MASK)) + + params := or(params, and(baseFactor, _BASE_FACTOR_MASK)) + params := or(params, shl(_FILTER_PERIOD_OFFSET, and(filterPeriod, _FILTER_PERIOD_MASK))) + params := or(params, shl(_DECAY_PERIOD_OFFSET, and(decayPeriod, _DECAY_PERIOD_MASK))) + params := or(params, shl(_REDUCTION_FACTOR_OFFSET, and(reductionFactor, _REDUCTION_FACTOR_MASK))) + params := or(params, shl(_VARIABLE_FEE_CONTROL_OFFSET, and(variableFeeControl, _VARIABLE_FEE_CONTROL_MASK))) + params := or(params, shl(_PROTOCOL_SHARE_OFFSET, and(protocolShare, _PROTOCOL_SHARE_MASK))) + params := or(params, shl(_MAX_VOL_ACC_OFFSET, and(maxVolatilityAccumulated, _VOLATILITY_MASK))) + } + + return params; + } + + /** + * @dev Updates the index reference in the encoded pair parameters + * @param params The encoded pair parameters + * @return newParams The updated encoded pair parameters + */ + function updateIdReference(bytes32 params) internal pure returns (bytes32 newParams) { + uint24 activeId = getActiveId(params); + assembly { + params := and(params, shl(_INDEX_REF_OFFSET, not(_INDEX_REF_MASK))) + newParams := or(params, shl(_INDEX_REF_OFFSET, activeId)) + } + } + + /** + * @dev Updates the time of last update in the encoded pair parameters + * @param params The encoded pair parameters + * @return newParams The updated encoded pair parameters + */ + function updateTimeOfLastUpdate(bytes32 params) internal view returns (bytes32 newParams) { + uint40 currentTime = block.timestamp.safe40(); + assembly { + params := and(params, shl(_TIME_OFFSET, not(_TIME_MASK))) + newParams := or(params, shl(_TIME_OFFSET, currentTime)) + } + } + + /** + * @dev Updates the volatility reference in the encoded pair parameters + * @param params The encoded pair parameters + * @return The updated encoded pair parameters + */ + function updateVolatilityReference(bytes32 params) internal pure returns (bytes32) { + uint256 volAcc = getVolatilityAccumulated(params); + uint256 reductionFactor = getReductionFactor(params); + + uint24 volRef; + unchecked { + volRef = uint24(volAcc * reductionFactor / _MAX_BASIS_POINTS); + } + + return setVolatilityReference(params, volRef); + } + + /** + * @dev Calculates the base fee + * @param params The encoded pair parameters + * @param binStep The bin step + * @return baseFee The base fee + */ + function getBaseFee(bytes32 params, uint8 binStep) internal pure returns (uint256) { + unchecked { + return uint256(getBaseFactor(params)) * binStep * 1e10; + } + } + + /** + * @dev Calculates the variable fee + * @param params The encoded pair parameters + * @param binStep The bin step + * @return variableFee The variable fee + */ + function getVariableFee(bytes32 params, uint8 binStep) internal pure returns (uint256 variableFee) { + uint256 variableFeeControl = getVariableFeeControl(params); + + if (variableFeeControl != 0) { + unchecked { + uint256 prod = uint256(getVolatilityAccumulated(params)) * binStep; + variableFee = (prod * prod * variableFeeControl + 99) / 100; + } + } + } + + /** + * @dev Calculates the total fee, which is the sum of the base fee and the variable fee + * @param params The encoded pair parameters + * @param binStep The bin step + * @return totalFee The total fee + */ + function getTotalFee(bytes32 params, uint8 binStep) internal pure returns (uint256) { + unchecked { + return getBaseFee(params, binStep) + getVariableFee(params, binStep); + } + } + + /** + * @dev Updates the volatility accumulated in the encoded pair parameters + * @param params The encoded pair parameters + * @param activeId The active id + * @return The updated encoded pair parameters + */ + function updateVolatilityAccumulated(bytes32 params, uint24 activeId) internal pure returns (bytes32) { + uint24 deltaId = getDeltaId(params, activeId); + + uint256 volAcc; + unchecked { + volAcc = (uint256(getVolatilityAccumulated(params)) + deltaId * _MAX_BASIS_POINTS); + } + + uint24 maxVolAcc = getMaxVolatilityAccumulated(params); + + if (volAcc > maxVolAcc) volAcc = maxVolAcc; + + assembly { + params := and(params, shl(_VOL_ACC_OFFSET, not(_VOLATILITY_MASK))) + params := or(params, shl(_VOL_ACC_OFFSET, volAcc)) + } + return params; + } + + /** + * @dev Updates the volatility reference and the volatility accumulated in the encoded pair parameters + * @param params The encoded pair parameters + * @return The updated encoded pair parameters + */ + function updateReferences(bytes32 params) internal view returns (bytes32) { + uint256 deltaT = block.timestamp - getTimeOfLastUpdate(params); + + if (deltaT >= getFilterPeriod(params)) { + params = updateIdReference(params); + if (deltaT < getDecayPeriod(params)) params = updateVolatilityReference(params); + else params = setVolatilityReference(params, 0); + } + + return updateTimeOfLastUpdate(params); + } + + /** + * @dev Updates the volatility reference and the volatility accumulated in the encoded pair parameters + * @param params The encoded pair parameters + * @param activeId The active id + * @return The updated encoded pair parameters + */ + function updateVolatilityParameters(bytes32 params, uint24 activeId) internal view returns (bytes32) { + params = updateReferences(params); + return updateVolatilityAccumulated(params, activeId); + } +} diff --git a/src/libraries/PendingOwnable.sol b/src/libraries/PendingOwnable.sol index 344be641..8ac22433 100644 --- a/src/libraries/PendingOwnable.sol +++ b/src/libraries/PendingOwnable.sol @@ -2,92 +2,115 @@ pragma solidity 0.8.10; -import "../LBErrors.sol"; import "../interfaces/IPendingOwnable.sol"; -/// @title Pending Ownable -/// @author Trader Joe -/// @notice Contract module which provides a basic access control mechanism, where -/// there is an account (an owner) that can be granted exclusive access to -/// specific functions. The ownership of this contract is transferred using the -/// push and pull pattern, the current owner set a `pendingOwner` using -/// {setPendingOwner} and that address can then call {becomeOwner} to become the -/// owner of that contract. The main logic and comments comes from OpenZeppelin's -/// Ownable contract. -/// -/// By default, the owner account will be the one that deploys the contract. This -/// can later be changed with {setPendingOwner} and {becomeOwner}. -/// -/// This module is used through inheritance. It will make available the modifier -/// `onlyOwner`, which can be applied to your functions to restrict their use to -/// the owner +/** + * @title Pending Ownable + * @author Trader Joe + * @notice Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. The ownership of this contract is transferred using the + * push and pull pattern, the current owner set a `pendingOwner` using + * {setPendingOwner} and that address can then call {becomeOwner} to become the + * owner of that contract. The main logic and comments comes from OpenZeppelin's + * Ownable contract. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {setPendingOwner} and {becomeOwner}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner + */ contract PendingOwnable is IPendingOwnable { address private _owner; address private _pendingOwner; - /// @notice Throws if called by any account other than the owner. + /** + * @notice Throws if called by any account other than the owner. + */ modifier onlyOwner() { if (msg.sender != _owner) revert PendingOwnable__NotOwner(); _; } - /// @notice Throws if called by any account other than the pending owner. + /** + * /** + * @notice Throws if called by any account other than the pending owner. + */ modifier onlyPendingOwner() { if (msg.sender != _pendingOwner || msg.sender == address(0)) revert PendingOwnable__NotPendingOwner(); _; } - /// @notice Initializes the contract setting the deployer as the initial owner + /** + * /** + * @notice Initializes the contract setting the deployer as the initial owner + */ constructor() { _transferOwnership(msg.sender); } - /// @notice Returns the address of the current owner - /// @return The address of the current owner + /** + * @notice Returns the address of the current owner + * @return The address of the current owner + */ function owner() public view override returns (address) { return _owner; } - /// @notice Returns the address of the current pending owner - /// @return The address of the current pending owner + /** + * @notice Returns the address of the current pending owner + * @return The address of the current pending owner + */ function pendingOwner() public view override returns (address) { return _pendingOwner; } - /// @notice Sets the pending owner address. This address will be able to become - /// the owner of this contract by calling {becomeOwner} + /** + * @notice Sets the pending owner address. This address will be able to become + * the owner of this contract by calling {becomeOwner} + */ function setPendingOwner(address pendingOwner_) public override onlyOwner { if (pendingOwner_ == address(0)) revert PendingOwnable__AddressZero(); if (_pendingOwner != address(0)) revert PendingOwnable__PendingOwnerAlreadySet(); _setPendingOwner(pendingOwner_); } - /// @notice Revoke the pending owner address. This address will not be able to - /// call {becomeOwner} to become the owner anymore. - /// Can only be called by the owner + /** + * @notice Revoke the pending owner address. This address will not be able to + * call {becomeOwner} to become the owner anymore. + * Can only be called by the owner + */ function revokePendingOwner() public override onlyOwner { if (_pendingOwner == address(0)) revert PendingOwnable__NoPendingOwner(); _setPendingOwner(address(0)); } - /// @notice Transfers the ownership to the new owner (`pendingOwner). - /// Can only be called by the pending owner + /** + * @notice Transfers the ownership to the new owner (`pendingOwner). + * Can only be called by the pending owner + */ function becomeOwner() public override onlyPendingOwner { _transferOwnership(msg.sender); } - /// @notice Leaves the contract without owner. It will not be possible to call - /// `onlyOwner` functions anymore. Can only be called by the current owner. - /// - /// NOTE: Renouncing ownership will leave the contract without an owner, - /// thereby removing any functionality that is only available to the owner. + /** + * @notice Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ function renounceOwnership() public override onlyOwner { _transferOwnership(address(0)); } - /// @notice Transfers ownership of the contract to a new account (`newOwner`). - /// Internal function without access restriction. - /// @param _newOwner The address of the new owner + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + * @param _newOwner The address of the new owner + */ function _transferOwnership(address _newOwner) internal virtual { address _oldOwner = _owner; _owner = _newOwner; @@ -95,9 +118,11 @@ contract PendingOwnable is IPendingOwnable { emit OwnershipTransferred(_oldOwner, _newOwner); } - /// @notice Push the new owner, it needs to be pulled to be effective. - /// Internal function without access restriction. - /// @param pendingOwner_ The address of the new pending owner + /** + * @dev Push the new owner, it needs to be pulled to be effective. + * Internal function without access restriction. + * @param pendingOwner_ The address of the new pending owner + */ function _setPendingOwner(address pendingOwner_) internal virtual { _pendingOwner = pendingOwner_; emit PendingOwnerSet(pendingOwner_); diff --git a/src/libraries/PriceHelper.sol b/src/libraries/PriceHelper.sol new file mode 100644 index 00000000..5f15dab4 --- /dev/null +++ b/src/libraries/PriceHelper.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "./math/Uint128x128Math.sol"; +import "./math/SafeCast.sol"; +import "./Constants.sol"; + +/** + * @title Liquidity Book Price Helper Library + * @author Trader Joe + * @notice This library contains functions to calculate prices + */ +library PriceHelper { + using Uint128x128Math for uint256; + using SafeCast for uint256; + + int256 private constant REAL_ID_SHIFT = 1 << 23; + + /** + * @dev Calculates the price from the id and the bin step + * @param id The id + * @param binStep The bin step + * @return price The price + */ + function getPriceFromId(uint24 id, uint8 binStep) internal pure returns (uint256 price) { + uint256 base = getBase(binStep); + int256 exponent = getExponent(id); + + price = base.pow(exponent); + } + + /** + * @dev Calculates the id from the price and the bin step + * @param price The price + * @param binStep The bin step + * @return id The id + */ + function getIdFromPrice(uint256 price, uint8 binStep) internal pure returns (uint24 id) { + uint256 base = getBase(binStep); + int256 realId = price.log2() / base.log2(); + + unchecked { + id = uint256(REAL_ID_SHIFT + realId).safe24(); + } + } + + /** + * @dev Calculates the base from the bin step, which is `1 + binStep / BASIS_POINT_MAX` + * @param binStep The bin step + * @return base The base + */ + function getBase(uint8 binStep) internal pure returns (uint256) { + unchecked { + return Constants.SCALE + (uint256(binStep) << Constants.SCALE_OFFSET) / Constants.BASIS_POINT_MAX; + } + } + + /** + * @dev Calculates the exponent from the id, which is `id - REAL_ID_SHIFT` + * @param id The id + * @return exponent The exponent + */ + function getExponent(uint24 id) internal pure returns (int256) { + unchecked { + return int256(uint256(id)) - REAL_ID_SHIFT; + } + } +} diff --git a/src/libraries/ReentrancyGuardUpgradeable.sol b/src/libraries/ReentrancyGuardUpgradeable.sol index ad922a8f..ba25d918 100644 --- a/src/libraries/ReentrancyGuardUpgradeable.sol +++ b/src/libraries/ReentrancyGuardUpgradeable.sol @@ -2,23 +2,28 @@ pragma solidity 0.8.10; -import "../LBErrors.sol"; - -/// @title Reentrancy Guard -/// @author Trader Joe -/// @notice Contract module that helps prevent reentrant calls to a function +/** + * @title Liquidity Book Reentrancy Guard Upgradeable Library + * @author Trader Joe + * @notice This library contains functions to prevent reentrant calls to a function + */ abstract contract ReentrancyGuardUpgradeable { - // Booleans are more expensive than uint256 or any type that takes up a full - // word because each write operation emits an extra SLOAD to first read the - // slot's contents, replace the bits taken up by the boolean, and then write - // back. This is the compiler's defense against contract upgrades and - // pointer aliasing, and it cannot be disabled. - - // The values being non-zero value makes deployment a bit more expensive, - // but in exchange the refund on every call to nonReentrant will be lower in - // amount. Since refunds are capped to a percentage of the total - // transaction's gas, it is best to keep them low in cases like this one, to - // increase the likelihood of the full refund coming into effect. + error ReentrancyGuardUpgradeable__ReentrantCall(); + + /** + * Booleans are more expensive than uint256 or any type that takes up a full + * word because each write operation emits an extra SLOAD to first read the + * slot's contents, replace the bits taken up by the boolean, and then write + * back. This is the compiler's defense against contract upgrades and + * pointer aliasing, and it cannot be disabled. + * + * The values being non-zero value makes deployment a bit more expensive, + * but in exchange the refund on every call to nonReentrant will be lower in + * amount. Since refunds are capped to a percentage of the total + * transaction's gas, it is best to keep them low in cases like this one, to + * increase the likelihood of the full refund coming into effect. + */ + uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; @@ -29,16 +34,16 @@ abstract contract ReentrancyGuardUpgradeable { } function __ReentrancyGuard_init_unchained() internal { - if (_status != 0) revert ReentrancyGuardUpgradeable__AlreadyInitialized(); - _status = _NOT_ENTERED; } - /// @notice Prevents a contract from calling itself, directly or indirectly. - /// Calling a `nonReentrant` function from another `nonReentrant` - /// function is not supported. It is possible to prevent this from happening - /// by making the `nonReentrant` function external, and making it call a - /// `private` function that does the actual work + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and making it call a + * `private` function that does the actual work + */ modifier nonReentrant() { // On the first call to nonReentrant, _notEntered will be true if (_status != _NOT_ENTERED) revert ReentrancyGuardUpgradeable__ReentrantCall(); diff --git a/src/libraries/SafeCast.sol b/src/libraries/SafeCast.sol deleted file mode 100644 index 409c59fc..00000000 --- a/src/libraries/SafeCast.sol +++ /dev/null @@ -1,227 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import "../LBErrors.sol"; - -/// @title Liquidity Book Safe Cast Library -/// @author Trader Joe -/// @notice Helper contract used for converting uint values safely -library SafeCast { - /// @notice Returns x on uint248 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint248 - function safe248(uint256 x) internal pure returns (uint248 y) { - if ((y = uint248(x)) != x) revert SafeCast__Exceeds248Bits(x); - } - - /// @notice Returns x on uint240 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint240 - function safe240(uint256 x) internal pure returns (uint240 y) { - if ((y = uint240(x)) != x) revert SafeCast__Exceeds240Bits(x); - } - - /// @notice Returns x on uint232 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint232 - function safe232(uint256 x) internal pure returns (uint232 y) { - if ((y = uint232(x)) != x) revert SafeCast__Exceeds232Bits(x); - } - - /// @notice Returns x on uint224 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint224 - function safe224(uint256 x) internal pure returns (uint224 y) { - if ((y = uint224(x)) != x) revert SafeCast__Exceeds224Bits(x); - } - - /// @notice Returns x on uint216 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint216 - function safe216(uint256 x) internal pure returns (uint216 y) { - if ((y = uint216(x)) != x) revert SafeCast__Exceeds216Bits(x); - } - - /// @notice Returns x on uint208 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint208 - function safe208(uint256 x) internal pure returns (uint208 y) { - if ((y = uint208(x)) != x) revert SafeCast__Exceeds208Bits(x); - } - - /// @notice Returns x on uint200 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint200 - function safe200(uint256 x) internal pure returns (uint200 y) { - if ((y = uint200(x)) != x) revert SafeCast__Exceeds200Bits(x); - } - - /// @notice Returns x on uint192 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint192 - function safe192(uint256 x) internal pure returns (uint192 y) { - if ((y = uint192(x)) != x) revert SafeCast__Exceeds192Bits(x); - } - - /// @notice Returns x on uint184 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint184 - function safe184(uint256 x) internal pure returns (uint184 y) { - if ((y = uint184(x)) != x) revert SafeCast__Exceeds184Bits(x); - } - - /// @notice Returns x on uint176 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint176 - function safe176(uint256 x) internal pure returns (uint176 y) { - if ((y = uint176(x)) != x) revert SafeCast__Exceeds176Bits(x); - } - - /// @notice Returns x on uint168 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint168 - function safe168(uint256 x) internal pure returns (uint168 y) { - if ((y = uint168(x)) != x) revert SafeCast__Exceeds168Bits(x); - } - - /// @notice Returns x on uint160 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint160 - function safe160(uint256 x) internal pure returns (uint160 y) { - if ((y = uint160(x)) != x) revert SafeCast__Exceeds160Bits(x); - } - - /// @notice Returns x on uint152 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint152 - function safe152(uint256 x) internal pure returns (uint152 y) { - if ((y = uint152(x)) != x) revert SafeCast__Exceeds152Bits(x); - } - - /// @notice Returns x on uint144 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint144 - function safe144(uint256 x) internal pure returns (uint144 y) { - if ((y = uint144(x)) != x) revert SafeCast__Exceeds144Bits(x); - } - - /// @notice Returns x on uint136 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint136 - function safe136(uint256 x) internal pure returns (uint136 y) { - if ((y = uint136(x)) != x) revert SafeCast__Exceeds136Bits(x); - } - - /// @notice Returns x on uint128 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint128 - function safe128(uint256 x) internal pure returns (uint128 y) { - if ((y = uint128(x)) != x) revert SafeCast__Exceeds128Bits(x); - } - - /// @notice Returns x on uint120 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint120 - function safe120(uint256 x) internal pure returns (uint120 y) { - if ((y = uint120(x)) != x) revert SafeCast__Exceeds120Bits(x); - } - - /// @notice Returns x on uint112 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint112 - function safe112(uint256 x) internal pure returns (uint112 y) { - if ((y = uint112(x)) != x) revert SafeCast__Exceeds112Bits(x); - } - - /// @notice Returns x on uint104 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint104 - function safe104(uint256 x) internal pure returns (uint104 y) { - if ((y = uint104(x)) != x) revert SafeCast__Exceeds104Bits(x); - } - - /// @notice Returns x on uint96 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint96 - function safe96(uint256 x) internal pure returns (uint96 y) { - if ((y = uint96(x)) != x) revert SafeCast__Exceeds96Bits(x); - } - - /// @notice Returns x on uint88 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint88 - function safe88(uint256 x) internal pure returns (uint88 y) { - if ((y = uint88(x)) != x) revert SafeCast__Exceeds88Bits(x); - } - - /// @notice Returns x on uint80 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint80 - function safe80(uint256 x) internal pure returns (uint80 y) { - if ((y = uint80(x)) != x) revert SafeCast__Exceeds80Bits(x); - } - - /// @notice Returns x on uint72 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint72 - function safe72(uint256 x) internal pure returns (uint72 y) { - if ((y = uint72(x)) != x) revert SafeCast__Exceeds72Bits(x); - } - - /// @notice Returns x on uint64 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint64 - function safe64(uint256 x) internal pure returns (uint64 y) { - if ((y = uint64(x)) != x) revert SafeCast__Exceeds64Bits(x); - } - - /// @notice Returns x on uint56 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint56 - function safe56(uint256 x) internal pure returns (uint56 y) { - if ((y = uint56(x)) != x) revert SafeCast__Exceeds56Bits(x); - } - - /// @notice Returns x on uint48 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint48 - function safe48(uint256 x) internal pure returns (uint48 y) { - if ((y = uint48(x)) != x) revert SafeCast__Exceeds48Bits(x); - } - - /// @notice Returns x on uint40 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint40 - function safe40(uint256 x) internal pure returns (uint40 y) { - if ((y = uint40(x)) != x) revert SafeCast__Exceeds40Bits(x); - } - - /// @notice Returns x on uint32 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint32 - function safe32(uint256 x) internal pure returns (uint32 y) { - if ((y = uint32(x)) != x) revert SafeCast__Exceeds32Bits(x); - } - - /// @notice Returns x on uint24 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint24 - function safe24(uint256 x) internal pure returns (uint24 y) { - if ((y = uint24(x)) != x) revert SafeCast__Exceeds24Bits(x); - } - - /// @notice Returns x on uint16 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint16 - function safe16(uint256 x) internal pure returns (uint16 y) { - if ((y = uint16(x)) != x) revert SafeCast__Exceeds16Bits(x); - } - - /// @notice Returns x on uint8 and check that it does not overflow - /// @param x The value as an uint256 - /// @return y The value as an uint8 - function safe8(uint256 x) internal pure returns (uint8 y) { - if ((y = uint8(x)) != x) revert SafeCast__Exceeds8Bits(x); - } -} diff --git a/src/libraries/TokenHelper.sol b/src/libraries/TokenHelper.sol index 54179f4b..f547a4f1 100644 --- a/src/libraries/TokenHelper.sol +++ b/src/libraries/TokenHelper.sol @@ -4,96 +4,49 @@ pragma solidity 0.8.10; import "openzeppelin/token/ERC20/IERC20.sol"; -import "../LBErrors.sol"; - -/// @title Safe Transfer -/// @author Trader Joe -/// @notice Wrappers around ERC20 operations that throw on failure (when the token -/// contract returns false). Tokens that return no value (and instead revert or -/// throw on failure) are also supported, non-reverting calls are assumed to be -/// successful. -/// To use this library you can add a `using TokenHelper for IERC20;` statement to your contract, -/// which allows you to call the safe operation as `token.safeTransfer(...)` +import "./AddressesHelper.sol"; + +/** + * @title Liquidity Book Token Helper Library + * @author Trader Joe + * @notice Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using TokenHelper for IERC20;` statement to your contract, + * which allows you to call the safe operation as `token.safeTransfer(...)` + */ library TokenHelper { - /// @notice Transfers token only if the amount is greater than zero - /// @param token The address of the token - /// @param owner The owner of the tokens - /// @param recipient The address of the recipient - /// @param amount The amount to send - function safeTransferFrom(IERC20 token, address owner, address recipient, uint256 amount) internal { - if (amount != 0) { - bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, owner, recipient, amount); - - bytes memory returnData = _callAndCatchError(address(token), data); + using AddressesHelper for address; - if (returnData.length > 0 && !abi.decode(returnData, (bool))) revert TokenHelper__TransferFailed(); - } - } - - /// @notice Transfers token only if the amount is greater than zero - /// @param token The address of the token - /// @param recipient The address of the recipient - /// @param amount The amount to send - function safeTransfer(IERC20 token, address recipient, uint256 amount) internal { - if (amount != 0) { - bytes memory data = abi.encodeWithSelector(token.transfer.selector, recipient, amount); + error TokenHelper__TransferFailed(); - bytes memory returnData = _callAndCatchError(address(token), data); + /** + * @notice Transfers token and reverts if the transfer fails + * @param token The address of the token + * @param owner The owner of the tokens + * @param recipient The address of the recipient + * @param amount The amount to send + */ + function safeTransferFrom(IERC20 token, address owner, address recipient, uint256 amount) internal { + bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, owner, recipient, amount); - if (returnData.length > 0 && !abi.decode(returnData, (bool))) revert TokenHelper__TransferFailed(); - } - } + bytes memory returnData = address(token).callAndCatch(data); - /// @notice Returns the amount of token received by the pair - /// @param token The address of the token - /// @param reserve The total reserve of token - /// @param fees The total fees of token - /// @return The amount received by the pair - function received(IERC20 token, uint256 reserve, uint256 fees) internal view returns (uint256) { - uint256 _internalBalance; - unchecked { - _internalBalance = reserve + fees; - } - return token.balanceOf(address(this)) - _internalBalance; + if (returnData.length > 0 && !abi.decode(returnData, (bool))) revert TokenHelper__TransferFailed(); } - /// @notice Private view function to perform a low level call on `target` - /// @dev Revert if the call doesn't succeed - /// @param target The address of the account - /// @param data The data to execute on `target` - /// @return returnData The data returned by the call - function _callAndCatchError(address target, bytes memory data) private returns (bytes memory) { - (bool success, bytes memory returnData) = target.call(data); - - if (success) { - if (returnData.length == 0 && !_isContract(target)) revert TokenHelper__NonContract(); - } else { - if (returnData.length == 0) { - revert TokenHelper__CallFailed(); - } else { - // Look for revert reason and bubble it up if present - assembly { - revert(add(32, returnData), mload(returnData)) - } - } - } + /** + * @notice Transfers token and reverts if the transfer fails + * @param token The address of the token + * @param recipient The address of the recipient + * @param amount The amount to send + */ + function safeTransfer(IERC20 token, address recipient, uint256 amount) internal { + bytes memory data = abi.encodeWithSelector(token.transfer.selector, recipient, amount); - return returnData; - } + bytes memory returnData = address(token).callAndCatch(data); - /// @notice Private view function to return if an address is a contract - /// @dev It is unsafe to assume that an address for which this function returns - /// false is an externally-owned account (EOA) and not a contract. - /// - /// Among others, `isContract` will return false for the following - /// types of addresses: - /// - an externally-owned account - /// - a contract in construction - /// - an address where a contract will be created - /// - an address where a contract lived, but was destroyed - /// @param account The address of the account - /// @return Whether the account is a contract (true) or not (false) - function _isContract(address account) private view returns (bool) { - return account.code.length > 0; + if (returnData.length > 0 && !abi.decode(returnData, (bool))) revert TokenHelper__TransferFailed(); } } diff --git a/src/libraries/math/BitMath.sol b/src/libraries/math/BitMath.sol new file mode 100644 index 00000000..52953dd1 --- /dev/null +++ b/src/libraries/math/BitMath.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +/** + * @title Liquidity Book Bit Math Library + * @author Trader Joe + * @notice Helper contract used for bit calculations + */ +library BitMath { + /** + * @dev Returns the index of the closest bit on the right of x that is non null + * @param x The value as a uint256 + * @param bit The index of the bit to start searching at + * @return id The index of the closest non null bit on the right of x. + * If there is no closest bit, it returns max(uint256) + */ + function closestBitRight(uint256 x, uint8 bit) internal pure returns (uint256 id) { + unchecked { + uint256 shift = 255 - bit; + x <<= shift; + + // can't overflow as it's non-zero and we shifted it by `_shift` + return (x == 0) ? type(uint256).max : mostSignificantBit(x) - shift; + } + } + + /** + * @dev Returns the index of the closest bit on the left of x that is non null + * @param x The value as a uint256 + * @param bit The index of the bit to start searching at + * @return id The index of the closest non null bit on the left of x. + * If there is no closest bit, it returns max(uint256) + */ + function closestBitLeft(uint256 x, uint8 bit) internal pure returns (uint256 id) { + unchecked { + x >>= bit; + + return (x == 0) ? type(uint256).max : leastSignificantBit(x) + bit; + } + } + + /** + * @dev Returns the index of the most significant bit of x + * This function returns 0 if x is 0 + * @param x The value as a uint256 + * @return msb The index of the most significant bit of x + */ + function mostSignificantBit(uint256 x) internal pure returns (uint8 msb) { + assembly { + if gt(x, 0xffffffffffffffffffffffffffffffff) { + x := shr(128, x) + msb := 128 + } + if gt(x, 0xffffffffffffffff) { + x := shr(64, x) + msb := add(msb, 64) + } + if gt(x, 0xffffffff) { + x := shr(32, x) + msb := add(msb, 32) + } + if gt(x, 0xffff) { + x := shr(16, x) + msb := add(msb, 16) + } + if gt(x, 0xff) { + x := shr(8, x) + msb := add(msb, 8) + } + if gt(x, 0xf) { + x := shr(4, x) + msb := add(msb, 4) + } + if gt(x, 0x3) { + x := shr(2, x) + msb := add(msb, 2) + } + if gt(x, 0x1) { msb := add(msb, 1) } + } + } + + /** + * @dev Returns the index of the least significant bit of x + * This function returns 255 if x is 0 + * @param x The value as a uint256 + * @return lsb The index of the least significant bit of x + */ + function leastSignificantBit(uint256 x) internal pure returns (uint8 lsb) { + assembly { + let sx := shl(128, x) + if iszero(iszero(sx)) { + lsb := 128 + x := sx + } + sx := shl(64, x) + if iszero(iszero(sx)) { + x := sx + lsb := add(lsb, 64) + } + sx := shl(32, x) + if iszero(iszero(sx)) { + x := sx + lsb := add(lsb, 32) + } + sx := shl(16, x) + if iszero(iszero(sx)) { + x := sx + lsb := add(lsb, 16) + } + sx := shl(8, x) + if iszero(iszero(sx)) { + x := sx + lsb := add(lsb, 8) + } + sx := shl(4, x) + if iszero(iszero(sx)) { + x := sx + lsb := add(lsb, 4) + } + sx := shl(2, x) + if iszero(iszero(sx)) { + x := sx + lsb := add(lsb, 2) + } + if iszero(iszero(shl(1, x))) { lsb := add(lsb, 1) } + + lsb := sub(255, lsb) + } + } +} diff --git a/src/libraries/math/LiquidityConfigurations.sol b/src/libraries/math/LiquidityConfigurations.sol new file mode 100644 index 00000000..d2055221 --- /dev/null +++ b/src/libraries/math/LiquidityConfigurations.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "./PackedUint128Math.sol"; + +/** + * @title Liquidity Book Liquidity Configurations Library + * @author Trader Joe + * @notice This library contains functions to encode and decode the config of a pool and interact with the encoded bytes32. + */ +library LiquidityConfigurations { + using PackedUint128Math for bytes32; + using PackedUint128Math for uint128; + + error LiquidityConfigurations__InvalidConfig(); + + uint256 private constant PRECISION = 1e18; + + /** + * @dev Encode the distributionX, distributionY and id into a single bytes32 + * @param distributionX The distribution of the first token + * @param distributionY The distribution of the second token + * @param id The id of the pool + * @return config The encoded config as follows: + * [0 - 24[: id + * [24 - 88[: distributionY + * [88 - 152[: distributionX + * [152 - 256[: empty + */ + function encodeParams(uint64 distributionX, uint64 distributionY, uint24 id) + internal + pure + returns (bytes32 config) + { + assembly { + config := or(shl(88, distributionX), or(shl(24, distributionY), id)) + } + } + + /** + * @dev Decode the distributionX, distributionY and id from a single bytes32 + * @param config The encoded config as follows: + * [0 - 24[: id + * [24 - 88[: distributionY + * [88 - 152[: distributionX + * [152 - 256[: empty + * @return distributionX The distribution of the first token + * @return distributionY The distribution of the second token + * @return id The id of the bin to add the liquidity to + */ + function decodeParams(bytes32 config) + internal + pure + returns (uint64 distributionX, uint64 distributionY, uint24 id) + { + assembly { + distributionX := shr(88, config) + distributionY := and(shr(24, config), 0xffffffffffffffff) + id := and(config, 0xffffff) + } + + if (uint256(config) > type(uint152).max || distributionX > PRECISION || distributionY > PRECISION) { + revert LiquidityConfigurations__InvalidConfig(); + } + } + + /** + * @dev Get the amounts and id from a config and amountsIn + * @param config The encoded config as follows: + * [0 - 24[: id + * [24 - 88[: distributionY + * [88 - 152[: distributionX + * [152 - 256[: empty + * @param amountsIn The amounts to distribute as follows: + * [0 - 128[: x1 + * [128 - 256[: x2 + * @return amounts The distributed amounts as follows: + * [0 - 128[: x1 + * [128 - 256[: x2 + * @return id The id of the bin to add the liquidity to + */ + function getAmountsAndId(bytes32 config, bytes32 amountsIn) internal pure returns (bytes32, uint24) { + (uint64 distributionX, uint64 distributionY, uint24 id) = decodeParams(config); + + (uint128 x1, uint128 x2) = amountsIn.decode(); + + assembly { + x1 := div(mul(x1, distributionX), PRECISION) + x2 := div(mul(x2, distributionY), PRECISION) + } + + return (x1.encode(x2), id); + } +} diff --git a/src/libraries/math/PackedUint128Math.sol b/src/libraries/math/PackedUint128Math.sol new file mode 100644 index 00000000..a422a96c --- /dev/null +++ b/src/libraries/math/PackedUint128Math.sol @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "../Constants.sol"; +import "./SafeCast.sol"; + +/** + * @title Liquidity Book Packed Uint128 Math Library + * @author Trader Joe + * @notice This library contains functions to encode and decode two uint128 into a single bytes32 + * and interact with the encoded bytes32. + */ +library PackedUint128Math { + using SafeCast for uint256; + + error PackedUint128Math__AddOverflow(); + error PackedUint128Math__SubUnderflow(); + error PackedUint128Math__AddFirstSubSecondOverflow(); + error PackedUint128Math__MultiplierBiggerThanMax(); + + uint256 private constant _OFFSET = 128; + uint256 private constant _MASK_128 = 0xffffffffffffffffffffffffffffffff; + + /** + * @dev Encodes two uint128 into a single bytes32 + * @param x1 The first uint128 + * @param x2 The second uint128 + * @return z The encoded bytes32 as follows: + * [0 - 128[: x1 + * [128 - 256[: x2 + */ + function encode(uint128 x1, uint128 x2) internal pure returns (bytes32 z) { + assembly { + z := or(x1, shl(_OFFSET, x2)) + } + } + + /** + * @dev Encodes a uint128 into a single bytes32 as the first uint128 + * @param x1 The uint128 + * @return z The encoded bytes32 as follows: + * [0 - 128[: x1 + * [128 - 256[: empty + */ + function encodeFirst(uint128 x1) internal pure returns (bytes32 z) { + assembly { + z := x1 + } + } + + /** + * @dev Encodes a uint128 into a single bytes32 as the second uint128 + * @param x2 The uint128 + * @return z The encoded bytes32 as follows: + * [0 - 128[: empty + * [128 - 256[: x2 + */ + function encodeSecond(uint128 x2) internal pure returns (bytes32 z) { + assembly { + z := shl(_OFFSET, x2) + } + } + + /** + * @dev Encodes a uint128 into a single bytes32 as the first or second uint128 + * @param x The uint128 + * @param first Whether to encode as the first or second uint128 + * @return z The encoded bytes32 as follows: + * if first: + * [0 - 128[: x + * [128 - 256[: empty + * else: + * [0 - 128[: empty + * [128 - 256[: x + */ + function encode(uint128 x, bool first) internal pure returns (bytes32 z) { + return first ? encodeFirst(x) : encodeSecond(x); + } + + /** + * @dev Decodes a bytes32 into two uint128 + * @param z The encoded bytes32 as follows: + * [0 - 128[: x1 + * [128 - 256[: x2 + * @return x1 The first uint128 + * @return x2 The second uint128 + */ + function decode(bytes32 z) internal pure returns (uint128 x1, uint128 x2) { + assembly { + x1 := and(z, _MASK_128) + x2 := shr(_OFFSET, z) + } + } + + /** + * @dev Decodes a bytes32 into a uint128 as the first uint128 + * @param z The encoded bytes32 as follows: + * [0 - 128[: x1 + * [128 - 256[: any + * @return x1 The first uint128 + */ + function decodeFirst(bytes32 z) internal pure returns (uint128 x1) { + assembly { + x1 := and(z, _MASK_128) + } + } + + /** + * @dev Decodes a bytes32 into a uint128 as the second uint128 + * @param z The encoded bytes32 as follows: + * [0 - 128[: any + * [128 - 256[: x2 + * @return x2 The second uint128 + */ + function decodeSecond(bytes32 z) internal pure returns (uint128 x2) { + assembly { + x2 := shr(_OFFSET, z) + } + } + + /** + * @dev Decodes a bytes32 into a uint128 as the first or second uint128 + * @param z The encoded bytes32 as follows: + * if first: + * [0 - 128[: x1 + * [128 - 256[: empty + * else: + * [0 - 128[: empty + * [128 - 256[: x2 + * @param first Whether to decode as the first or second uint128 + * @return x The decoded uint128 + */ + function decode(bytes32 z, bool first) internal pure returns (uint128 x) { + return first ? decodeFirst(z) : decodeSecond(z); + } + + /** + * @dev Adds two encoded bytes32, reverting on overflow on any of the uint128 + * @param x The first bytes32 encoded as follows: + * [0 - 128[: x1 + * [128 - 256[: x2 + * @param y The second bytes32 encoded as follows: + * [0 - 128[: y1 + * [128 - 256[: y2 + * @return z The sum of x and y encoded as follows: + * [0 - 128[: x1 + y1 + * [128 - 256[: x2 + y2 + */ + function add(bytes32 x, bytes32 y) internal pure returns (bytes32 z) { + assembly { + z := add(x, y) + } + + if (z < x || uint128(uint256(z)) < uint128(uint256(x))) { + revert PackedUint128Math__AddOverflow(); + } + } + + /** + * @dev Adds an encoded bytes32 and two uint128, reverting on overflow on any of the uint128 + * @param x The bytes32 encoded as follows: + * [0 - 128[: x1 + * [128 - 256[: x2 + * @param y1 The first uint128 + * @param y2 The second uint128 + * @return z The sum of x and y encoded as follows: + * [0 - 128[: x1 + y1 + * [128 - 256[: x2 + y2 + */ + function add(bytes32 x, uint128 y1, uint128 y2) internal pure returns (bytes32) { + return add(x, encode(y1, y2)); + } + + /** + * @dev Subtracts two encoded bytes32, reverting on underflow on any of the uint128 + * @param x The first bytes32 encoded as follows: + * [0 - 128[: x1 + * [128 - 256[: x2 + * @param y The second bytes32 encoded as follows: + * [0 - 128[: y1 + * [128 - 256[: y2 + * @return z The difference of x and y encoded as follows: + * [0 - 128[: x1 - y1 + * [128 - 256[: x2 - y2 + */ + function sub(bytes32 x, bytes32 y) internal pure returns (bytes32 z) { + assembly { + z := sub(x, y) + } + + if (z > x || uint128(uint256(z)) > uint128(uint256(x))) { + revert PackedUint128Math__SubUnderflow(); + } + } + + /** + * @dev Subtracts an encoded bytes32 and two uint128, reverting on underflow on any of the uint128 + * @param x The bytes32 encoded as follows: + * [0 - 128[: x1 + * [128 - 256[: x2 + * @param y1 The first uint128 + * @param y2 The second uint128 + * @return z The difference of x and y encoded as follows: + * [0 - 128[: x1 - y1 + * [128 - 256[: x2 - y2 + */ + function sub(bytes32 x, uint128 y1, uint128 y2) internal pure returns (bytes32) { + return sub(x, encode(y1, y2)); + } + + /** + * @dev Returns whether any of the uint128 of x is greater than the corresponding uint128 of y + * @param x The first bytes32 encoded as follows: + * [0 - 128[: x1 + * [128 - 256[: x2 + * @param y The second bytes32 encoded as follows: + * [0 - 128[: y1 + * [128 - 256[: y2 + * @return x1 < y1 || x2 < y2 + */ + function lt(bytes32 x, bytes32 y) internal pure returns (bool) { + (uint128 x1, uint128 x2) = decode(x); + (uint128 y1, uint128 y2) = decode(y); + + return x1 < y1 || x2 < y2; + } + + /** + * @dev Returns whether any of the uint128 of x is greater than or equal to the corresponding uint128 of y + * @param x The first bytes32 encoded as follows: + * [0 - 128[: x1 + * [128 - 256[: x2 + * @param y The second bytes32 encoded as follows: + * [0 - 128[: y1 + * [128 - 256[: y2 + * @return x1 < y1 || x2 < y2 + */ + function gt(bytes32 x, bytes32 y) internal pure returns (bool) { + (uint128 x1, uint128 x2) = decode(x); + (uint128 y1, uint128 y2) = decode(y); + + return x1 > y1 || x2 > y2; + } + + /** + * @dev Multiplies an encoded bytes32 by a uint128 then shifts the result 128 bits to the right, rounding up + * The result can't overflow + * @param x The bytes32 encoded as follows: + * [0 - 128[: x1 + * [128 - 256[: x2 + * @param multiplier The uint128 to multiply by + * @return z The product of x and multiplier encoded as follows: + * [0 - 128[: ceil((x1 * multiplier) / 2**128) + * [128 - 256[: ceil((x2 * multiplier) / 2**128) + */ + function scalarMulShift128RoundUp(bytes32 x, uint128 multiplier) internal pure returns (bytes32 z) { + if (multiplier == 0) return 0; + + (uint128 x1, uint128 x2) = decode(x); + + // Can't overflow because: + // ``` + // max(x{1,2} * multiplier) = type(uint128).max * type(uint128).max + // = type(uint256).max - (2**129 - 2) + // _MASK_128 = 2**128 - 1 < 2**129 - 2 + // ``` + assembly { + x1 := shr(_OFFSET, add(mul(x1, multiplier), _MASK_128)) + x2 := shr(_OFFSET, add(mul(x2, multiplier), _MASK_128)) + } + + return encode(x1, x2); + } + + /** + * @dev Multiplies an encoded bytes32 by a uint128 then divides the result by 10_000, rounding down + * The result can't overflow as the multiplier needs to be smaller or equal to 10_000 + * @param x The bytes32 encoded as follows: + * [0 - 128[: x1 + * [128 - 256[: x2 + * @param multiplier The uint128 to multiply by (must be smaller or equal to 10_000) + * @return z The product of x and multiplier encoded as follows: + * [0 - 128[: floor((x1 * multiplier) / 10_000) + * [128 - 256[: floor((x2 * multiplier) / 10_000) + */ + function scalarMulDivBasisPointRoundDown(bytes32 x, uint128 multiplier) internal pure returns (bytes32 z) { + if (multiplier == 0) return 0; + + uint256 BASIS_POINT_MAX = Constants.BASIS_POINT_MAX; + if (multiplier > BASIS_POINT_MAX) revert PackedUint128Math__MultiplierBiggerThanMax(); + + (uint128 x1, uint128 x2) = decode(x); + + assembly { + x1 := div(mul(x1, multiplier), BASIS_POINT_MAX) + x2 := div(mul(x2, multiplier), BASIS_POINT_MAX) + } + + return encode(x1, x2); + } +} diff --git a/src/libraries/math/SafeCast.sol b/src/libraries/math/SafeCast.sol new file mode 100644 index 00000000..ca41a4d2 --- /dev/null +++ b/src/libraries/math/SafeCast.sol @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +/** + * @title Liquidity Book Safe Cast Library + * @author Trader Joe + * @notice This library contains functions to safely cast uint256 to different uint types. + */ +library SafeCast { + error SafeCast__Exceeds248Bits(); + error SafeCast__Exceeds240Bits(); + error SafeCast__Exceeds232Bits(); + error SafeCast__Exceeds224Bits(); + error SafeCast__Exceeds216Bits(); + error SafeCast__Exceeds208Bits(); + error SafeCast__Exceeds200Bits(); + error SafeCast__Exceeds192Bits(); + error SafeCast__Exceeds184Bits(); + error SafeCast__Exceeds176Bits(); + error SafeCast__Exceeds168Bits(); + error SafeCast__Exceeds160Bits(); + error SafeCast__Exceeds152Bits(); + error SafeCast__Exceeds144Bits(); + error SafeCast__Exceeds136Bits(); + error SafeCast__Exceeds128Bits(); + error SafeCast__Exceeds120Bits(); + error SafeCast__Exceeds112Bits(); + error SafeCast__Exceeds104Bits(); + error SafeCast__Exceeds96Bits(); + error SafeCast__Exceeds88Bits(); + error SafeCast__Exceeds80Bits(); + error SafeCast__Exceeds72Bits(); + error SafeCast__Exceeds64Bits(); + error SafeCast__Exceeds56Bits(); + error SafeCast__Exceeds48Bits(); + error SafeCast__Exceeds40Bits(); + error SafeCast__Exceeds32Bits(); + error SafeCast__Exceeds24Bits(); + error SafeCast__Exceeds16Bits(); + error SafeCast__Exceeds8Bits(); + + /** + * @dev Returns x on uint248 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint248 + */ + function safe248(uint256 x) internal pure returns (uint248 y) { + if ((y = uint248(x)) != x) revert SafeCast__Exceeds248Bits(); + } + + /** + * @dev Returns x on uint240 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint240 + */ + function safe240(uint256 x) internal pure returns (uint240 y) { + if ((y = uint240(x)) != x) revert SafeCast__Exceeds240Bits(); + } + + /** + * @dev Returns x on uint232 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint232 + */ + function safe232(uint256 x) internal pure returns (uint232 y) { + if ((y = uint232(x)) != x) revert SafeCast__Exceeds232Bits(); + } + + /** + * @dev Returns x on uint224 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint224 + */ + function safe224(uint256 x) internal pure returns (uint224 y) { + if ((y = uint224(x)) != x) revert SafeCast__Exceeds224Bits(); + } + + /** + * @dev Returns x on uint216 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint216 + */ + function safe216(uint256 x) internal pure returns (uint216 y) { + if ((y = uint216(x)) != x) revert SafeCast__Exceeds216Bits(); + } + + /** + * @dev Returns x on uint208 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint208 + */ + function safe208(uint256 x) internal pure returns (uint208 y) { + if ((y = uint208(x)) != x) revert SafeCast__Exceeds208Bits(); + } + + /** + * @dev Returns x on uint200 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint200 + */ + function safe200(uint256 x) internal pure returns (uint200 y) { + if ((y = uint200(x)) != x) revert SafeCast__Exceeds200Bits(); + } + + /** + * @dev Returns x on uint192 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint192 + */ + function safe192(uint256 x) internal pure returns (uint192 y) { + if ((y = uint192(x)) != x) revert SafeCast__Exceeds192Bits(); + } + + /** + * @dev Returns x on uint184 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint184 + */ + function safe184(uint256 x) internal pure returns (uint184 y) { + if ((y = uint184(x)) != x) revert SafeCast__Exceeds184Bits(); + } + + /** + * @dev Returns x on uint176 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint176 + */ + function safe176(uint256 x) internal pure returns (uint176 y) { + if ((y = uint176(x)) != x) revert SafeCast__Exceeds176Bits(); + } + + /** + * @dev Returns x on uint168 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint168 + */ + function safe168(uint256 x) internal pure returns (uint168 y) { + if ((y = uint168(x)) != x) revert SafeCast__Exceeds168Bits(); + } + + /** + * @dev Returns x on uint160 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint160 + */ + function safe160(uint256 x) internal pure returns (uint160 y) { + if ((y = uint160(x)) != x) revert SafeCast__Exceeds160Bits(); + } + + /** + * @dev Returns x on uint152 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint152 + */ + function safe152(uint256 x) internal pure returns (uint152 y) { + if ((y = uint152(x)) != x) revert SafeCast__Exceeds152Bits(); + } + + /** + * @dev Returns x on uint144 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint144 + */ + function safe144(uint256 x) internal pure returns (uint144 y) { + if ((y = uint144(x)) != x) revert SafeCast__Exceeds144Bits(); + } + + /** + * @dev Returns x on uint136 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint136 + */ + function safe136(uint256 x) internal pure returns (uint136 y) { + if ((y = uint136(x)) != x) revert SafeCast__Exceeds136Bits(); + } + + /** + * @dev Returns x on uint128 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint128 + */ + function safe128(uint256 x) internal pure returns (uint128 y) { + if ((y = uint128(x)) != x) revert SafeCast__Exceeds128Bits(); + } + + /** + * @dev Returns x on uint120 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint120 + */ + function safe120(uint256 x) internal pure returns (uint120 y) { + if ((y = uint120(x)) != x) revert SafeCast__Exceeds120Bits(); + } + + /** + * @dev Returns x on uint112 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint112 + */ + function safe112(uint256 x) internal pure returns (uint112 y) { + if ((y = uint112(x)) != x) revert SafeCast__Exceeds112Bits(); + } + + /** + * @dev Returns x on uint104 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint104 + */ + function safe104(uint256 x) internal pure returns (uint104 y) { + if ((y = uint104(x)) != x) revert SafeCast__Exceeds104Bits(); + } + + /** + * @dev Returns x on uint96 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint96 + */ + function safe96(uint256 x) internal pure returns (uint96 y) { + if ((y = uint96(x)) != x) revert SafeCast__Exceeds96Bits(); + } + + /** + * @dev Returns x on uint88 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint88 + */ + function safe88(uint256 x) internal pure returns (uint88 y) { + if ((y = uint88(x)) != x) revert SafeCast__Exceeds88Bits(); + } + + /** + * @dev Returns x on uint80 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint80 + */ + function safe80(uint256 x) internal pure returns (uint80 y) { + if ((y = uint80(x)) != x) revert SafeCast__Exceeds80Bits(); + } + + /** + * @dev Returns x on uint72 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint72 + */ + function safe72(uint256 x) internal pure returns (uint72 y) { + if ((y = uint72(x)) != x) revert SafeCast__Exceeds72Bits(); + } + + /** + * @dev Returns x on uint64 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint64 + */ + function safe64(uint256 x) internal pure returns (uint64 y) { + if ((y = uint64(x)) != x) revert SafeCast__Exceeds64Bits(); + } + + /** + * @dev Returns x on uint56 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint56 + */ + function safe56(uint256 x) internal pure returns (uint56 y) { + if ((y = uint56(x)) != x) revert SafeCast__Exceeds56Bits(); + } + + /** + * @dev Returns x on uint48 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint48 + */ + function safe48(uint256 x) internal pure returns (uint48 y) { + if ((y = uint48(x)) != x) revert SafeCast__Exceeds48Bits(); + } + + /** + * @dev Returns x on uint40 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint40 + */ + function safe40(uint256 x) internal pure returns (uint40 y) { + if ((y = uint40(x)) != x) revert SafeCast__Exceeds40Bits(); + } + + /** + * @dev Returns x on uint32 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint32 + */ + function safe32(uint256 x) internal pure returns (uint32 y) { + if ((y = uint32(x)) != x) revert SafeCast__Exceeds32Bits(); + } + + /** + * @dev Returns x on uint24 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint24 + */ + function safe24(uint256 x) internal pure returns (uint24 y) { + if ((y = uint24(x)) != x) revert SafeCast__Exceeds24Bits(); + } + + /** + * @dev Returns x on uint16 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint16 + */ + function safe16(uint256 x) internal pure returns (uint16 y) { + if ((y = uint16(x)) != x) revert SafeCast__Exceeds16Bits(); + } + + /** + * @dev Returns x on uint8 and check that it does not overflow + * @param x The value as an uint256 + * @return y The value as an uint8 + */ + function safe8(uint256 x) internal pure returns (uint8 y) { + if ((y = uint8(x)) != x) revert SafeCast__Exceeds8Bits(); + } +} diff --git a/src/libraries/math/SampleMath.sol b/src/libraries/math/SampleMath.sol new file mode 100644 index 00000000..2147a25f --- /dev/null +++ b/src/libraries/math/SampleMath.sol @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +/** + * @title Liquidity Book Sample Math Library + * @author Trader Joe + * @notice This library contains functions to encode and decode a sample into a single bytes32 + * and interact with the encoded bytes32 + * The sample is encoded as follows: + * 0 - 16: oracle length (16 bits) + * 16 - 80: cumulative id (64 bits) + * 80 - 144: cumulative volatility accumulated (64 bits) + * 144 - 208: cumulative bin crossed (64 bits) + * 208 - 216: sample lifetime (8 bits) + * 216 - 256: sample creation timestamp (40 bits) + */ +library SampleMath { + uint256 internal constant _MASK_ORACLE_LENGTH = 0xffff; + uint256 internal constant _MASK_CUMULATIVE = 0xffffffffffff; + uint256 internal constant _MASK_SAMPLE_LIFETIME = 0xff; + + uint256 internal constant _SHIFT_CUMULATIVE_ID = 16; + uint256 internal constant _SHIFT_CUMULATIVE_VOLATILITY = 80; + uint256 internal constant _SHIFT_CUMULATIVE_BIN_CROSSED = 144; + uint256 internal constant _SHIFT_SAMPLE_LIFETIME = 208; + uint256 internal constant _SHIFT_SAMPLE_CREATION = 216; + + /** + * @dev Decodes an encoded sample and return all the values + * @param sample The encoded sample + * @return oracleLength The oracle length + * @return cumulativeId The cumulative id + * @return cumulativeVolatility The cumulative volatility + * @return cumulativeBinCrossed The cumulative bin crossed + * @return sampleLifetime The sample lifetime + * @return createdAt The sample creation timestamp + */ + function decode(bytes32 sample) + internal + pure + returns ( + uint16 oracleLength, + uint64 cumulativeId, + uint64 cumulativeVolatility, + uint64 cumulativeBinCrossed, + uint8 sampleLifetime, + uint40 createdAt + ) + { + oracleLength = getOracleLength(sample); + cumulativeId = getCumulativeId(sample); + cumulativeVolatility = getCumulativeVolatility(sample); + cumulativeBinCrossed = getCumulativeBinCrossed(sample); + sampleLifetime = getSampleLifetime(sample); + createdAt = getSampleCreation(sample); + } + + /** + * @dev Encodes a sample + * @param oracleLength The oracle length + * @param cumulativeId The cumulative id + * @param cumulativeVolatility The cumulative volatility + * @param cumulativeBinCrossed The cumulative bin crossed + * @param sampleLifetime The sample lifetime + * @param createdAt The sample creation timestamp + * @return sample The encoded sample + */ + function encode( + uint16 oracleLength, + uint64 cumulativeId, + uint64 cumulativeVolatility, + uint64 cumulativeBinCrossed, + uint8 sampleLifetime, + uint40 createdAt + ) internal pure returns (bytes32 sample) { + assembly { + sample := or(oracleLength, shl(_SHIFT_CUMULATIVE_ID, cumulativeId)) + sample := or(sample, shl(_SHIFT_CUMULATIVE_VOLATILITY, cumulativeVolatility)) + sample := or(sample, shl(_SHIFT_CUMULATIVE_BIN_CROSSED, cumulativeBinCrossed)) + sample := or(sample, shl(_SHIFT_SAMPLE_LIFETIME, sampleLifetime)) + sample := or(sample, shl(_SHIFT_SAMPLE_CREATION, createdAt)) + } + } + + /** + * @dev Gets the oracle length from an encoded sample + * @param sample The encoded sample + * @return length The oracle length + */ + function getOracleLength(bytes32 sample) internal pure returns (uint16 length) { + assembly { + length := sample + } + } + + /** + * @dev Gets the cumulative id from an encoded sample + * @param sample The encoded sample as follows: + * [0 - 16[: oracle length (16 bits) + * [16 - 256[: any (240 bits) + * @return id The cumulative id + */ + function getCumulativeId(bytes32 sample) internal pure returns (uint64 id) { + assembly { + id := shr(_SHIFT_CUMULATIVE_ID, sample) + } + } + + /** + * @dev Gets the cumulative volatility accumulated from an encoded sample + * @param sample The encoded sample as follows: + * [0 - 16[: any (16 bits) + * [16 - 80[: cumulative id (64 bits) + * [80 - 256[: any (176 bits) + * @return volatilityAccumulated The cumulative volatility + */ + function getCumulativeVolatility(bytes32 sample) internal pure returns (uint64 volatilityAccumulated) { + assembly { + volatilityAccumulated := shr(_SHIFT_CUMULATIVE_VOLATILITY, sample) + } + } + + /** + * @dev Gets the cumulative bin crossed from an encoded sample + * @param sample The encoded sample as follows: + * [0 - 80[: any (80 bits) + * [80 - 144[: cumulative volatility accumulated (64 bits) + * [144 - 256[: any (112 bits) + * @return binCrossed The cumulative bin crossed + */ + function getCumulativeBinCrossed(bytes32 sample) internal pure returns (uint64 binCrossed) { + assembly { + binCrossed := shr(_SHIFT_CUMULATIVE_BIN_CROSSED, sample) + } + } + + /** + * @dev Gets the sample lifetime from an encoded sample + * @param sample The encoded sample as follows: + * [0 - 144[: any (144 bits) + * [144 - 208[: cumulative bin crossed (64 bits) + * [208 - 256[: any (48 bits) + * @return lifetime The sample lifetime + */ + function getSampleLifetime(bytes32 sample) internal pure returns (uint8 lifetime) { + assembly { + lifetime := shr(_SHIFT_SAMPLE_LIFETIME, sample) + } + } + + /** + * @dev Gets the sample creation timestamp from an encoded sample + * @param sample The encoded sample as follows: + * [0 - 208[: any (208 bits) + * [208 - 216[: sample lifetime (8 bits) + * [216 - 256[: any (40 bits) + * @return creation The sample creation timestamp + */ + function getSampleCreation(bytes32 sample) internal pure returns (uint40 creation) { + assembly { + creation := shr(_SHIFT_SAMPLE_CREATION, sample) + } + } + + /** + * @dev Gets the sample last update timestamp from an encoded sample + * @param sample The encoded sample as follows: + * [0 - 216[: any (216 bits) + * [216 - 256[: sample creation timestamp (40 bits) + * @return lastUpdate The sample last update timestamp + */ + //TODO lastupdate 48 bits? + function getSampleLastUpdate(bytes32 sample) internal pure returns (uint40 lastUpdate) { + lastUpdate = getSampleCreation(sample) + getSampleLifetime(sample); + } + + /** + * @dev Gets the weighted average of two samples and their respective weights + * @param sample1 The first encoded sample + * @param sample2 The second encoded sample + * @param weight1 The weight of the first sample + * @param weight2 The weight of the second sample + * @return weightedAverageId The weighted average id + * @return weightedAverageVolatility The weighted average volatility + * @return weightedAverageBinCrossed The weighted average bin crossed + */ + function getWeightedAverage(bytes32 sample1, bytes32 sample2, uint40 weight1, uint40 weight2) + internal + pure + returns (uint64 weightedAverageId, uint64 weightedAverageVolatility, uint64 weightedAverageBinCrossed) + { + uint256 cId1 = getCumulativeId(sample1); + uint256 cVolatility1 = getCumulativeVolatility(sample1); + uint256 cBinCrossed1 = getCumulativeBinCrossed(sample1); + + if (weight2 == 0) return (uint64(cId1), uint64(cVolatility1), uint64(cBinCrossed1)); + + uint256 cId2 = getCumulativeId(sample2); + uint256 cVolatility2 = getCumulativeVolatility(sample2); + uint256 cBinCrossed2 = getCumulativeBinCrossed(sample2); + + if (weight1 == 0) return (uint64(cId2), uint64(cVolatility2), uint64(cBinCrossed2)); + + uint256 totalWeight = uint256(weight1) + weight2; + + unchecked { + weightedAverageId = uint64((cId1 * weight1 + cId2 * weight2) / totalWeight); + weightedAverageVolatility = uint64((cVolatility1 * weight1 + cVolatility2 * weight2) / totalWeight); + weightedAverageBinCrossed = uint64((cBinCrossed1 * weight1 + cBinCrossed2 * weight2) / totalWeight); + } + } + + /** + * @dev Updates a sample with the given values + * @param sample The encoded sample + * @param deltaTime The time elapsed since the last update + * @param activeId The active id + * @param volatilityAccumulated The volatility accumulated + * @param binCrossed The bin crossed + * @return cumulativeId The cumulative id + * @return cumulativeVolatility The cumulative volatility + * @return cumulativeBinCrossed The cumulative bin crossed + */ + function update(bytes32 sample, uint40 deltaTime, uint24 activeId, uint24 volatilityAccumulated, uint24 binCrossed) + internal + pure + returns (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) + { + unchecked { + cumulativeId = uint64(activeId) * deltaTime; + cumulativeVolatility = uint64(volatilityAccumulated) * deltaTime; + cumulativeBinCrossed = uint64(binCrossed) * deltaTime; + } + + cumulativeId += getCumulativeId(sample); + cumulativeVolatility += getCumulativeVolatility(sample); + cumulativeBinCrossed += getCumulativeBinCrossed(sample); + } +} diff --git a/src/libraries/math/TreeMath.sol b/src/libraries/math/TreeMath.sol new file mode 100644 index 00000000..52ccba7b --- /dev/null +++ b/src/libraries/math/TreeMath.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "./BitMath.sol"; + +/** + * @title Liquidity Book Tree Math Library + * @author Trader Joe + * @notice This library contains functions to interact with a tree of TreeUint24. + */ +library TreeMath { + using BitMath for uint256; + + struct TreeUint24 { + bytes32 level0; + mapping(bytes32 => bytes32) level1; + mapping(bytes32 => bytes32) level2; + } + + /** + * @dev Returns true if the tree contains the id + * @param tree The tree + * @param id The id + * @return True if the tree contains the id + */ + function contains(TreeUint24 storage tree, uint24 id) internal view returns (bool) { + bytes32 leaf2 = bytes32(uint256(id) >> 8); + + return tree.level2[leaf2] & bytes32(1 << (id & type(uint8).max)) != 0; + } + + /** + * @dev Adds the id to the tree and returns true if the id was not already in the tree + * It will also propagate the change to the parent levels. + * @param tree The tree + * @param id The id + * @return True if the id was not already in the tree + */ + function add(TreeUint24 storage tree, uint24 id) internal returns (bool) { + bytes32 key2 = bytes32(uint256(id) >> 8); + + bytes32 leaves = tree.level2[key2]; + bytes32 newLeaves = leaves | bytes32(1 << (id & type(uint8).max)); + + if (leaves != newLeaves) { + tree.level2[key2] = newLeaves; + + if (leaves == 0) { + bytes32 key1 = key2 >> 8; + leaves = tree.level1[key1]; + + tree.level1[key1] = leaves | bytes32(1 << (uint256(key2) & type(uint8).max)); + + if (leaves == 0) tree.level0 |= bytes32(1 << (uint256(key1) & type(uint8).max)); + } + + return true; + } + + return false; + } + + /** + * @dev Removes the id from the tree and returns true if the id was in the tree. + * It will also propagate the change to the parent levels. + * @param tree The tree + * @param id The id + * @return True if the id was in the tree + */ + function remove(TreeUint24 storage tree, uint24 id) internal returns (bool) { + bytes32 key2 = bytes32(uint256(id) >> 8); + + bytes32 leaves = tree.level2[key2]; + bytes32 newLeaves = leaves & ~bytes32(1 << (id & type(uint8).max)); + + if (leaves != newLeaves) { + tree.level2[key2] = newLeaves; + + if (newLeaves == 0) { + bytes32 key1 = key2 >> 8; + leaves = tree.level1[key1]; + + tree.level1[key1] = leaves & ~bytes32(1 << (uint256(key2) & type(uint8).max)); + + if (leaves == 0) tree.level0 &= ~bytes32(1 << (uint256(key1) & type(uint8).max)); + } + + return true; + } + + return false; + } + + /** + * @dev Returns the first id in the tree that is lower than or equal to the given id. + * It will return type(uint24).max if there is no such id. + * @param tree The tree + * @param id The id + * @return The first id in the tree that is lower than or equal to the given id + */ + function findFirstRight(TreeUint24 storage tree, uint24 id) internal view returns (uint24) { + bytes32 leaves; + + bytes32 key2 = bytes32(uint256(id) >> 8); + uint8 bit = uint8(id & type(uint8).max); + + if (bit != 0) { + leaves = tree.level2[key2]; + uint256 closestBit = _closestBitRight(leaves, bit); + + if (closestBit != type(uint256).max) return uint24(uint256(key2) << 8 | closestBit); + } + + bytes32 key1 = key2 >> 8; + bit = uint8(uint256(key2) & type(uint8).max); + + if (bit != 0) { + leaves = tree.level1[key1]; + uint256 closestBit = _closestBitRight(leaves, bit); + + if (closestBit != type(uint256).max) { + key2 = bytes32(uint256(key1) << 8 | closestBit); + leaves = tree.level2[key2]; + + return uint24(uint256(key2) << 8 | uint256(leaves).mostSignificantBit()); + } + } + + bit = uint8(uint256(key1) & type(uint8).max); + + if (bit != 0) { + leaves = tree.level0; + uint256 closestBit = _closestBitRight(leaves, bit); + + if (closestBit != type(uint256).max) { + key1 = bytes32(closestBit); + leaves = tree.level1[key1]; + + key2 = bytes32(uint256(key1) << 8 | uint256(leaves).mostSignificantBit()); + leaves = tree.level2[key2]; + + return uint24(uint256(key2) << 8 | uint256(leaves).mostSignificantBit()); + } + } + + return type(uint24).max; + } + + /** + * @dev Returns the first id in the tree that is higher than or equal to the given id. + * It will return 0 if there is no such id. + * @param tree The tree + * @param id The id + * @return The first id in the tree that is higher than or equal to the given id + */ + function findFirstLeft(TreeUint24 storage tree, uint24 id) internal view returns (uint24) { + bytes32 leaves; + + bytes32 key2 = bytes32(uint256(id) >> 8); + uint8 bit = uint8(id & type(uint8).max); + + if (bit != type(uint8).max) { + leaves = tree.level2[key2]; + uint256 closestBit = _closestBitLeft(leaves, bit); + + if (closestBit != type(uint256).max) return uint24(uint256(key2) << 8 | closestBit); + } + + bytes32 key1 = key2 >> 8; + bit = uint8(uint256(key2) & type(uint8).max); + + if (bit != type(uint8).max) { + leaves = tree.level1[key1]; + uint256 closestBit = _closestBitLeft(leaves, bit); + + if (closestBit != type(uint256).max) { + key2 = bytes32(uint256(key1) << 8 | closestBit); + leaves = tree.level2[key2]; + + return uint24(uint256(key2) << 8 | uint256(leaves).leastSignificantBit()); + } + } + + bit = uint8(uint256(key1) & type(uint8).max); + + if (bit != type(uint8).max) { + leaves = tree.level0; + uint256 closestBit = _closestBitLeft(leaves, bit); + + if (closestBit != type(uint256).max) { + key1 = bytes32(closestBit); + leaves = tree.level1[key1]; + + key2 = bytes32(uint256(key1) << 8 | uint256(leaves).leastSignificantBit()); + leaves = tree.level2[key2]; + + return uint24(uint256(key2) << 8 | uint256(leaves).leastSignificantBit()); + } + } + + return 0; + } + + /** + * @dev Returns the first bit in the given leaves that is strictly lower than the given bit. + * It will return type(uint256).max if there is no such bit. + * @param leaves The leaves + * @param bit The bit + * @return The first bit in the given leaves that is strictly lower than the given bit + */ + function _closestBitRight(bytes32 leaves, uint8 bit) private pure returns (uint256) { + unchecked { + return uint256(leaves).closestBitRight(bit - 1); + } + } + + /** + * @dev Returns the first bit in the given leaves that is strictly higher than the given bit. + * It will return type(uint256).max if there is no such bit. + * @param leaves The leaves + * @param bit The bit + * @return The first bit in the given leaves that is strictly higher than the given bit + */ + function _closestBitLeft(bytes32 leaves, uint8 bit) private pure returns (uint256) { + unchecked { + return uint256(leaves).closestBitLeft(bit + 1); + } + } +} diff --git a/src/libraries/Math128x128.sol b/src/libraries/math/Uint128x128Math.sol similarity index 56% rename from src/libraries/Math128x128.sol rename to src/libraries/math/Uint128x128Math.sol index 4bc51c87..f1bd5709 100644 --- a/src/libraries/Math128x128.sol +++ b/src/libraries/math/Uint128x128Math.sol @@ -2,36 +2,38 @@ pragma solidity 0.8.10; -import "../LBErrors.sol"; +import "../Constants.sol"; import "./BitMath.sol"; -import "./Constants.sol"; -import "./Math512Bits.sol"; - -/// @title Liquidity Book Math Helper Library -/// @author Trader Joe -/// @notice Helper contract used for power and log calculations -library Math128x128 { - using Math512Bits for uint256; +import "./Uint256x256Math.sol"; + +/** + * @title Liquidity Book Uint128x128 Math Library + * @author Trader Joe + * @notice Helper contract used for power and log calculations + */ +library Uint128x128Math { + using Uint256x256Math for uint256; using BitMath for uint256; + error Uint128x128Math__LogUnderflow(); + error Uint128x128Math__PowUnderflow(uint256 x, int256 y); + uint256 constant LOG_SCALE_OFFSET = 127; uint256 constant LOG_SCALE = 1 << LOG_SCALE_OFFSET; uint256 constant LOG_SCALE_SQUARED = LOG_SCALE * LOG_SCALE; - /// @notice Calculates the binary logarithm of x. - /// - /// @dev Based on the iterative approximation algorithm. - /// https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation - /// - /// Requirements: - /// - x must be greater than zero. - /// - /// Caveats: - /// - The results are not perfectly accurate to the last decimal, due to the lossy precision of the iterative approximation - /// Also because x is converted to an unsigned 129.127-binary fixed-point number during the operation to optimize the multiplication - /// - /// @param x The unsigned 128.128-binary fixed-point number for which to calculate the binary logarithm. - /// @return result The binary logarithm as a signed 128.128-binary fixed-point number. + /** + * @notice Calculates the binary logarithm of x. + * @dev Based on the iterative approximation algorithm. + * https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation + * Requirements: + * - x must be greater than zero. + * Caveats: + * - The results are not perfectly accurate to the last decimal, due to the lossy precision of the iterative approximation + * Also because x is converted to an unsigned 129.127-binary fixed-point number during the operation to optimize the multiplication + * @param x The unsigned 128.128-binary fixed-point number for which to calculate the binary logarithm. + * @return result The binary logarithm as a signed 128.128-binary fixed-point number. + */ function log2(uint256 x) internal pure returns (int256 result) { // Convert x to a unsigned 129.127-binary fixed-point number to optimize the multiplication. // If we use an offset of 128 bits, y would need 129 bits and y**2 would would overflow and we would have to @@ -39,7 +41,7 @@ library Math128x128 { // can use the regular multiplication if (x == 1) return -128; - if (x == 0) revert Math128x128__LogUnderflow(); + if (x == 0) revert Uint128x128Math__LogUnderflow(); x >>= 1; @@ -86,12 +88,13 @@ library Math128x128 { } } - /// @notice Returns the value of x^y. It calculates `1 / x^abs(y)` if x is bigger than 2^128. - /// At the end of the operations, we invert the result if needed. - /// @param x The unsigned 128.128-binary fixed-point number for which to calculate the power - /// @param y A relative number without any decimals, needs to be between ]2^20; 2^20[ - /// @return result The result of `x^y` - function power(uint256 x, int256 y) internal pure returns (uint256 result) { + /** + * @notice Returns the value of x^y. It calculates `1 / x^abs(y)` if x is bigger than 2^128. + * At the end of the operations, we invert the result if needed. + * @param x The unsigned 128.128-binary fixed-point number for which to calculate the power + * @param y A relative number without any decimals, needs to be between ]2^21; 2^21[ + */ + function pow(uint256 x, int256 y) internal pure returns (uint256 result) { bool invert; uint256 absY; @@ -105,59 +108,61 @@ library Math128x128 { } } - if (absY < 0x100000) { + if (absY < 0x200000) { result = Constants.SCALE; assembly { - let pow := x + let squared := x if gt(x, 0xffffffffffffffffffffffffffffffff) { - pow := div(not(0), pow) + squared := div(not(0), squared) invert := iszero(invert) } - if and(absY, 0x1) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x2) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x4) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x8) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x10) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x20) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x40) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x80) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x100) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x200) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x400) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x800) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x1000) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x2000) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x4000) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x8000) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x10000) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x20000) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x40000) { result := shr(128, mul(result, pow)) } - pow := shr(128, mul(pow, pow)) - if and(absY, 0x80000) { result := shr(128, mul(result, pow)) } + if and(absY, 0x1) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x2) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x4) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x8) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x10) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x20) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x40) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x80) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x100) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x200) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x400) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x800) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x1000) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x2000) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x4000) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x8000) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x10000) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x20000) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x40000) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x80000) { result := shr(128, mul(result, squared)) } + squared := shr(128, mul(squared, squared)) + if and(absY, 0x100000) { result := shr(128, mul(result, shr(128, mul(result, squared)))) } } } // revert if y is too big or if x^y underflowed - if (result == 0) revert Math128x128__PowerUnderflow(x, y); + if (result == 0) revert Uint128x128Math__PowUnderflow(x, y); return invert ? type(uint256).max / result : result; } diff --git a/src/libraries/math/Uint256x256Math.sol b/src/libraries/math/Uint256x256Math.sol new file mode 100644 index 00000000..9284a560 --- /dev/null +++ b/src/libraries/math/Uint256x256Math.sol @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "./BitMath.sol"; + +/** + * @title Liquidity Book Uint256x256 Math Library + * @author Trader Joe + * @notice Helper contract used for full precision calculations + */ +library Uint256x256Math { + using BitMath for uint256; + + error Uint256x256Math__MulShiftOverflow(); + error Uint256x256Math__MulDivOverflow(); + + /** + * @notice Calculates floor(x*y/denominator) with full precision + * The result will be rounded down + * @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + * Requirements: + * - The denominator cannot be zero + * - The result must fit within uint256 + * Caveats: + * - This function does not work with fixed-point numbers + * @param x The multiplicand as an uint256 + * @param y The multiplier as an uint256 + * @param denominator The divisor as an uint256 + * @return result The result as an uint256 + */ + function mulDivRoundDown(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { + (uint256 prod0, uint256 prod1) = _getMulProds(x, y); + + return _getEndOfDivRoundDown(x, y, denominator, prod0, prod1); + } + + /** + * @notice Calculates ceil(x*y/denominator) with full precision + * The result will be rounded up + * @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + * Requirements: + * - The denominator cannot be zero + * - The result must fit within uint256 + * Caveats: + * - This function does not work with fixed-point numbers + * @param x The multiplicand as an uint256 + * @param y The multiplier as an uint256 + * @param denominator The divisor as an uint256 + * @return result The result as an uint256 + */ + function mulDivRoundUp(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { + result = mulDivRoundDown(x, y, denominator); + if (mulmod(x, y, denominator) != 0) result += 1; + } + + /** + * @notice Calculates floor(x * y / 2**offset) with full precision + * The result will be rounded down + * @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + * Requirements: + * - The offset needs to be strictly lower than 256 + * - The result must fit within uint256 + * Caveats: + * - This function does not work with fixed-point numbers + * @param x The multiplicand as an uint256 + * @param y The multiplier as an uint256 + * @param offset The offset as an uint256, can't be greater than 256 + * @return result The result as an uint256 + */ + function mulShiftRoundDown(uint256 x, uint256 y, uint8 offset) internal pure returns (uint256 result) { + (uint256 prod0, uint256 prod1) = _getMulProds(x, y); + + if (prod0 != 0) result = prod0 >> offset; + if (prod1 != 0) { + // Make sure the result is less than 2^256. + if (prod1 >= 1 << offset) revert Uint256x256Math__MulShiftOverflow(); + + unchecked { + result += prod1 << (256 - offset); + } + } + } + + /** + * @notice Calculates floor(x * y / 2**offset) with full precision + * The result will be rounded down + * @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + * Requirements: + * - The offset needs to be strictly lower than 256 + * - The result must fit within uint256 + * Caveats: + * - This function does not work with fixed-point numbers + * @param x The multiplicand as an uint256 + * @param y The multiplier as an uint256 + * @param offset The offset as an uint256, can't be greater than 256 + * @return result The result as an uint256 + */ + function mulShiftRoundUp(uint256 x, uint256 y, uint8 offset) internal pure returns (uint256 result) { + result = mulShiftRoundDown(x, y, offset); + if (mulmod(x, y, 1 << offset) != 0) result += 1; + } + + /** + * @notice Calculates floor(x << offset / y) with full precision + * The result will be rounded down + * @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + * Requirements: + * - The offset needs to be strictly lower than 256 + * - The result must fit within uint256 + * Caveats: + * - This function does not work with fixed-point numbers + * @param x The multiplicand as an uint256 + * @param offset The number of bit to shift x as an uint256 + * @param denominator The divisor as an uint256 + * @return result The result as an uint256 + */ + function shiftDivRoundDown(uint256 x, uint8 offset, uint256 denominator) internal pure returns (uint256 result) { + uint256 prod0; + uint256 prod1; + + prod0 = x << offset; // Least significant 256 bits of the product + unchecked { + prod1 = x >> (256 - offset); // Most significant 256 bits of the product + } + + return _getEndOfDivRoundDown(x, 1 << offset, denominator, prod0, prod1); + } + + /** + * @notice Calculates ceil(x << offset / y) with full precision + * The result will be rounded up + * @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + * Requirements: + * - The offset needs to be strictly lower than 256 + * - The result must fit within uint256 + * Caveats: + * - This function does not work with fixed-point numbers + * @param x The multiplicand as an uint256 + * @param offset The number of bit to shift x as an uint256 + * @param denominator The divisor as an uint256 + * @return result The result as an uint256 + */ + function shiftDivRoundUp(uint256 x, uint8 offset, uint256 denominator) internal pure returns (uint256 result) { + result = shiftDivRoundDown(x, offset, denominator); + if (mulmod(x, 1 << offset, denominator) != 0) result += 1; + } + + /** + * @notice Helper function to return the result of `x * y` as 2 uint256 + * @param x The multiplicand as an uint256 + * @param y The multiplier as an uint256 + * @return prod0 The least significant 256 bits of the product + * @return prod1 The most significant 256 bits of the product + */ + function _getMulProds(uint256 x, uint256 y) private pure returns (uint256 prod0, uint256 prod1) { + // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use + // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2^256 + prod0. + assembly { + let mm := mulmod(x, y, not(0)) + prod0 := mul(x, y) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + } + + /** + * @notice Helper function to return the result of `x * y / denominator` with full precision + * @param x The multiplicand as an uint256 + * @param y The multiplier as an uint256 + * @param denominator The divisor as an uint256 + * @param prod0 The least significant 256 bits of the product + * @param prod1 The most significant 256 bits of the product + * @return result The result as an uint256 + */ + function _getEndOfDivRoundDown(uint256 x, uint256 y, uint256 denominator, uint256 prod0, uint256 prod1) + private + pure + returns (uint256 result) + { + // Handle non-overflow cases, 256 by 256 division + if (prod1 == 0) { + unchecked { + result = prod0 / denominator; + } + } else { + // Make sure the result is less than 2^256. Also prevents denominator == 0 + if (prod1 >= denominator) revert Uint256x256Math__MulDivOverflow(); + + // Make division exact by subtracting the remainder from [prod1 prod0]. + uint256 remainder; + assembly { + // Compute remainder using mulmod. + remainder := mulmod(x, y, denominator) + + // Subtract 256 bit number from 512 bit number. + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1 + // See https://cs.stackexchange.com/q/138556/92363 + unchecked { + // Does not overflow because the denominator cannot be zero at this stage in the function + uint256 lpotdod = denominator & (~denominator + 1); + assembly { + // Divide denominator by lpotdod. + denominator := div(denominator, lpotdod) + + // Divide [prod1 prod0] by lpotdod. + prod0 := div(prod0, lpotdod) + + // Flip lpotdod such that it is 2^256 / lpotdod. If lpotdod is zero, then it becomes one + lpotdod := add(div(sub(0, lpotdod), lpotdod), 1) + } + + // Shift in bits from prod1 into prod0 + prod0 |= prod1 * lpotdod; + + // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such + // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for + // four bits. That is, denominator * inv = 1 mod 2^4 + uint256 inverse = (3 * denominator) ^ 2; + + // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works + // in modular arithmetic, doubling the correct bits in each step + inverse *= 2 - denominator * inverse; // inverse mod 2^8 + inverse *= 2 - denominator * inverse; // inverse mod 2^16 + inverse *= 2 - denominator * inverse; // inverse mod 2^32 + inverse *= 2 - denominator * inverse; // inverse mod 2^64 + inverse *= 2 - denominator * inverse; // inverse mod 2^128 + inverse *= 2 - denominator * inverse; // inverse mod 2^256 + + // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. + // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is + // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inverse; + } + } + } +} diff --git a/test/BitMath.t.sol b/test/BitMath.t.sol new file mode 100644 index 00000000..416f5d7d --- /dev/null +++ b/test/BitMath.t.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../src/libraries/math/BitMath.sol"; + +contract BitMathTest is Test { + using BitMath for uint256; + + function test_MostSignificantBit() external { + for (uint256 i = 0; i < 256; i++) { + assertEq(uint256(1 << i).mostSignificantBit(), i, "test_MostSignificantBit::1"); + } + } + + function testFuzz_MostSignificantBit(uint256 x) external { + uint256 msb = x.mostSignificantBit(); + + if (x == 0) { + assertEq(msb, 0, "testFuzz_MostSignificantBit::1"); + } else { + assertEq(x >> msb, 1, "testFuzz_MostSignificantBit::2"); + } + } + + function test_LeastSignificantBit() external { + for (uint256 i = 0; i < 256; i++) { + assertEq(uint256(1 << i).leastSignificantBit(), i, "test_LeastSignificantBit::1"); + } + } + + function testFuzz_LeastSignificantBit(uint256 x) external { + uint256 lsb = x.leastSignificantBit(); + + if (x == 0) { + assertEq(lsb, 255, "testFuzz_LeastSignificantBit::1"); + } else { + assertEq(x << (255 - lsb), 1 << 255, "testFuzz_LeastSignificantBit::2"); + } + } + + function test_ClosestBitRight() external { + for (uint256 i = 0; i < 256; i++) { + assertEq(uint256(1 << i).closestBitRight(255), i, "test_ClosestBitRight::1"); + } + } + + function testFuzz_ClosestBitRight(uint256 x, uint8 bit) external { + uint256 cbr = x.closestBitRight(bit); + + if (cbr == type(uint256).max) { + assertEq(x << (255 - bit), 0, "testFuzz_ClosestBitRight::1"); + } else { + assertLe(cbr, bit, "testFuzz_ClosestBitRight::2"); + assertGe(x << (255 - cbr), 1 << 255, "testFuzz_ClosestBitRight::3"); + } + } + + function test_ClosestBitLeft() external { + for (uint256 i = 0; i < 256; i++) { + assertEq(uint256(1 << i).closestBitLeft(0), i, "test_ClosestBitLeft::1"); + } + } + + function testFuzz_ClosestBitLeft(uint256 x, uint8 bit) external { + uint256 cbl = x.closestBitLeft(bit); + + if (cbl == type(uint256).max) { + assertEq(x >> bit, 0, "testFuzz_ClosestBitLeft::1"); + } else { + assertGe(cbl, bit, "testFuzz_ClosestBitLeft::2"); + assertGe(x >> cbl, 1, "testFuzz_ClosestBitLeft::3"); + } + } +} diff --git a/test/Faucet.t.sol b/test/Faucet.t.sol index d9bf5a21..6c75b37b 100644 --- a/test/Faucet.t.sol +++ b/test/Faucet.t.sol @@ -5,7 +5,8 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; import "test/mocks/ERC20.sol"; import "test/mocks/Faucet.sol"; -import "src/LBErrors.sol"; + +import "../src/interfaces/IPendingOwnable.sol"; contract FaucetTest is Test { Faucet private faucet; @@ -52,7 +53,7 @@ contract FaucetTest is Test { function testLockRequest() external { vm.startPrank(ALICE, ALICE); - vm.expectRevert(abi.encodeWithSelector(PendingOwnable__NotOwner.selector)); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); faucet.setUnlockedRequest(false); vm.stopPrank(); @@ -75,7 +76,7 @@ contract FaucetTest is Test { newToken.mint(address(faucet), 1_000e18); vm.startPrank(ALICE, ALICE); - vm.expectRevert(abi.encodeWithSelector(PendingOwnable__NotOwner.selector)); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); faucet.addFaucetToken(IERC20(newToken), 1e18); vm.stopPrank(); @@ -87,7 +88,7 @@ contract FaucetTest is Test { function testRemoveToken() external { vm.startPrank(ALICE, ALICE); - vm.expectRevert(abi.encodeWithSelector(PendingOwnable__NotOwner.selector)); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); faucet.removeFaucetToken(IERC20(address(1))); vm.stopPrank(); @@ -165,7 +166,7 @@ contract FaucetTest is Test { uint96 newRequestAvaxAmount = 2e18; vm.startPrank(ALICE, ALICE); - vm.expectRevert(abi.encodeWithSelector(PendingOwnable__NotOwner.selector)); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); faucet.setAmountPerRequest(AVAX, newRequestToken6Amount); vm.stopPrank(); @@ -188,7 +189,7 @@ contract FaucetTest is Test { assertEq(ALICE.balance, 1e18); vm.startPrank(ALICE, ALICE); - vm.expectRevert(abi.encodeWithSelector(PendingOwnable__NotOwner.selector)); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); faucet.withdrawToken(AVAX, ALICE, 1e18); vm.stopPrank(); @@ -212,7 +213,7 @@ contract FaucetTest is Test { assertEq(token12.balanceOf(ALICE), 2 * TOKEN6_PER_REQUEST); vm.startPrank(ALICE, ALICE); - vm.expectRevert(abi.encodeWithSelector(PendingOwnable__NotOwner.selector)); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); faucet.withdrawToken(IERC20(token6), ALICE, 1e18); vm.stopPrank(); @@ -257,7 +258,7 @@ contract FaucetTest is Test { faucet.setRequestCooldown(1 hours); vm.startPrank(ALICE, ALICE); - vm.expectRevert(abi.encodeWithSelector(PendingOwnable__NotOwner.selector)); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); faucet.setRequestCooldown(10 hours); vm.stopPrank(); diff --git a/test/LBToken.t.sol b/test/LBToken.t.sol new file mode 100644 index 00000000..96e74178 --- /dev/null +++ b/test/LBToken.t.sol @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "openzeppelin/utils/structs/EnumerableMap.sol"; + +import "../src/LBToken.sol"; + +contract LBTokenTest is Test { + using EnumerableMap for EnumerableMap.AddressToUintMap; + + LBTokenCoverage public lbToken; + + mapping(uint256 => uint256) public idToAmount0; + mapping(uint256 => uint256) public idToAmount1; + + EnumerableMap.AddressToUintMap private accountToBalance; + + struct MintCase { + uint256 id; + uint256 mintAmount; + } + + struct BurnCase { + uint256 id; + uint256 mintAmount; + uint256 burnAmount; + } + + struct BalanceCase { + address account; + uint256 id; + uint256 mintAmount; + } + + function setUp() external { + lbToken = new LBTokenCoverage(); + } + + function test_Name() external { + assertEq(lbToken.name(), "Liquidity Book Token", "test_Name::1"); + } + + function test_Symbol() external { + assertEq(lbToken.symbol(), "LBT", "test_Symbol::1"); + } + + function testFuzz_BatchMint(address to, MintCase[] memory mints) external { + vm.assume(to != address(0) && to != address(lbToken) && mints.length > 0); + + uint256[] memory ids = new uint256[](mints.length); + uint256[] memory amounts = new uint256[](mints.length); + + for (uint256 i = 0; i < mints.length; i++) { + _updateMintAmount(mints[i]); + + amounts[i] = mints[i].mintAmount; + ids[i] = mints[i].id; + + idToAmount0[mints[i].id] += mints[i].mintAmount; + } + + lbToken.mintBatch(to, ids, amounts); + + for (uint256 i = 0; i < ids.length; i++) { + assertEq(lbToken.balanceOf(to, ids[i]), idToAmount0[ids[i]], "testFuzz_BatchMint::1"); + assertEq(lbToken.totalSupply(ids[i]), idToAmount0[ids[i]], "testFuzz_BatchMint::2"); + } + } + + function testFuzz_BatchBurn(address from, BurnCase[] memory burns) external { + vm.assume(from != address(0) && from != address(lbToken) && burns.length > 0); + + uint256[] memory ids = new uint256[](burns.length); + uint256[] memory mintAmounts = new uint256[](burns.length); + uint256[] memory burnAmounts = new uint256[](burns.length); + + for (uint256 i = 0; i < burns.length; i++) { + MintCase memory mint = MintCase(burns[i].id, burns[i].mintAmount); + _updateMintAmount(mint); + _updateBurnAmount(burns[i]); + + idToAmount0[mint.id] += mint.mintAmount; + idToAmount1[mint.id] += burns[i].burnAmount; + + ids[i] = mint.id; + mintAmounts[i] = mint.mintAmount; + burnAmounts[i] = burns[i].burnAmount; + } + + lbToken.mintBatch(from, ids, mintAmounts); + lbToken.batchBurnFrom(from, ids, burnAmounts); + + for (uint256 i = 0; i < ids.length; i++) { + assertEq( + lbToken.balanceOf(from, ids[i]), idToAmount0[ids[i]] - idToAmount1[ids[i]], "testFuzz_BatchBurn::1" + ); + assertEq(lbToken.totalSupply(ids[i]), idToAmount0[ids[i]] - idToAmount1[ids[i]], "testFuzz_BatchBurn::2"); + } + } + + function testFuzz_BatchTransfer(address from, address to, MintCase[] memory mints) external { + vm.assume( + from != address(0) && from != address(lbToken) && to != address(0) && to != address(lbToken) && from != to + && mints.length > 0 + ); + + uint256[] memory ids = new uint256[](mints.length); + uint256[] memory amounts = new uint256[](mints.length); + + for (uint256 i = 0; i < mints.length; i++) { + uint256 id = mints[i].id; + uint256 mintAmount = mints[i].mintAmount; + + uint256 minted = idToAmount0[id]; + + if (mintAmount > type(uint256).max - minted) { + mintAmount = type(uint256).max - minted; + } + + minted += mintAmount; + idToAmount0[id] = minted; + + ids[i] = id; + amounts[i] = mintAmount; + } + + vm.startPrank(from); + lbToken.mintBatch(from, ids, amounts); + lbToken.batchTransferFrom(from, to, ids, amounts); + vm.stopPrank(); + + for (uint256 i = 0; i < ids.length; i++) { + assertEq(lbToken.balanceOf(from, ids[i]), 0, "testFuzz_BatchTransfer::1"); + assertEq(lbToken.balanceOf(to, ids[i]), idToAmount0[ids[i]], "testFuzz_BatchTransfer::2"); + assertEq(lbToken.totalSupply(ids[i]), idToAmount0[ids[i]], "testFuzz_BatchTransfer::3"); + } + } + + function testFuzz_BatchTransferFromPartial(address from, address to, MintCase[] memory mints) external { + vm.assume( + from != address(0) && from != address(lbToken) && to != address(0) && to != address(lbToken) && from != to + && mints.length > 0 + ); + + uint256[] memory ids = new uint256[](mints.length); + uint256[] memory amounts = new uint256[](mints.length); + uint256[] memory transferAmounts = new uint256[](mints.length); + + for (uint256 i = 0; i < mints.length; i++) { + uint256 id = mints[i].id; + uint256 mintAmount = mints[i].mintAmount; + + uint256 minted = idToAmount0[id]; + + if (mintAmount > type(uint256).max - minted) { + mintAmount = type(uint256).max - minted; + } + + idToAmount0[id] += mintAmount; + idToAmount1[id] += mintAmount / 2; + + ids[i] = id; + amounts[i] = mintAmount; + transferAmounts[i] = mintAmount / 2; + } + + vm.startPrank(from); + lbToken.mintBatch(from, ids, amounts); + lbToken.batchTransferFrom(from, to, ids, transferAmounts); + vm.stopPrank(); + + for (uint256 i = 0; i < ids.length; i++) { + uint256 id = ids[i]; + + uint256 transferAmount = idToAmount1[id]; + uint256 remainingAmount = idToAmount0[id] - transferAmount; + + assertEq(lbToken.balanceOf(from, id), remainingAmount, "testFuzz_BatchTransferFromPartial::1"); + assertEq(lbToken.balanceOf(to, id), transferAmount, "testFuzz_BatchTransferFromPartial::2"); + assertEq(lbToken.totalSupply(id), idToAmount0[id], "testFuzz_BatchTransferFromPartial::3"); + } + } + + function testFuzz_BalanceOfBatch(BalanceCase[] memory cases) external { + vm.assume(cases.length > 0); + + uint256[] memory sIds = new uint256[](1); + uint256[] memory sAmounts = new uint256[](1); + + uint256[] memory ids = new uint256[](cases.length); + address[] memory accounts = new address[](cases.length); + + for (uint256 i = 0; i < cases.length; i++) { + vm.assume(cases[i].account != address(0) && cases[i].account != address(lbToken)); + + MintCase memory mint = MintCase(cases[i].id, cases[i].mintAmount); + _updateMintAmount(mint); + + idToAmount0[mint.id] += mint.mintAmount; + + sIds[0] = mint.id; + sAmounts[0] = mint.mintAmount; + + lbToken.mintBatch(cases[i].account, sIds, sAmounts); + + ids[i] = mint.id; + accounts[i] = cases[i].account; + } + + uint256[] memory balances = lbToken.balanceOfBatch(accounts, ids); + + for (uint256 i = 0; i < cases.length; i++) { + assertEq(balances[i], lbToken.balanceOf(cases[i].account, cases[i].id), "testFuzz_BalanceOfBatch::1"); + } + } + + function testFuzz_ApprovedForAll(address from, address to, uint256 id, uint256 amount) external { + vm.assume( + from != address(0) && to != address(lbToken) && to != address(0) && to != address(lbToken) && from != to + ); + + uint256[] memory ids = new uint256[](1); + uint256[] memory amounts = new uint256[](1); + + ids[0] = id; + amounts[0] = amount; + + lbToken.mintBatch(from, ids, amounts); + + vm.startPrank(to); + vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__SpenderNotApproved.selector, from, to)); + lbToken.batchTransferFrom(from, to, ids, amounts); + vm.stopPrank(); + + vm.startPrank(from); + lbToken.setApprovalForAll(to, true); + vm.stopPrank(); + + assertEq(lbToken.isApprovedForAll(from, to), true, "testFuzz_ApprovedForAll::1"); + + vm.startPrank(to); + lbToken.batchTransferFrom(from, to, ids, amounts); + vm.stopPrank(); + + assertEq(lbToken.balanceOf(from, id), 0, "testFuzz_ApprovedForAll::2"); + assertEq(lbToken.balanceOf(to, id), amount, "testFuzz_ApprovedForAll::3"); + + vm.startPrank(from); + lbToken.setApprovalForAll(to, false); + vm.stopPrank(); + + assertEq(lbToken.isApprovedForAll(from, to), false, "testFuzz_ApprovedForAll::4"); + + vm.startPrank(to); + vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__SpenderNotApproved.selector, from, to)); + lbToken.batchTransferFrom(from, to, ids, amounts); + vm.stopPrank(); + } + + function test_RevertForAddressZeroOrThis() external { + uint256[] memory ids = new uint256[](1); + uint256[] memory amounts = new uint256[](1); + + vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__AddressThisOrZero.selector)); + lbToken.mintBatch(address(0), ids, amounts); + + vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__AddressThisOrZero.selector)); + vm.prank(address(1)); + lbToken.batchTransferFrom(address(1), address(0), ids, amounts); + + vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__AddressThisOrZero.selector)); + vm.prank(address(1)); + lbToken.batchBurnFrom(address(0), ids, amounts); + + vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__AddressThisOrZero.selector)); + lbToken.mintBatch(address(lbToken), ids, amounts); + + vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__AddressThisOrZero.selector)); + vm.prank(address(1)); + lbToken.batchTransferFrom(address(1), address(lbToken), ids, amounts); + + vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__AddressThisOrZero.selector)); + vm.prank(address(1)); + lbToken.batchBurnFrom(address(lbToken), ids, amounts); + } + + function testFuzz_SetApprovalOnSelf(address account) external { + vm.assume(account != address(0) && account != address(lbToken)); + + vm.startPrank(account); + vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__SelfApproval.selector, account)); + lbToken.setApprovalForAll(account, true); + vm.stopPrank(); + + assertEq(lbToken.isApprovedForAll(account, account), true, "testFuzz_SetApprovalOnSelf::1"); + } + + function testFuzz_RevertOnInvalidLength(uint256[] memory ids, uint256[] memory amounts) external { + vm.assume(ids.length != amounts.length && ids.length != 0); + + vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__InvalidLength.selector)); + lbToken.mintBatch(address(1), ids, amounts); + + vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__InvalidLength.selector)); + vm.prank(address(1)); + lbToken.batchTransferFrom(address(1), address(2), ids, amounts); + } + + function testFuzz_RevertFromBalanceExceeded(uint256 id, uint256 amount) external { + vm.assume(amount != type(uint256).max); + + uint256[] memory ids = new uint256[](1); + uint256[] memory amounts = new uint256[](1); + + ids[0] = id; + amounts[0] = amount; + + lbToken.mintBatch(address(1), ids, amounts); + + amounts[0] = amount + 1; + vm.expectRevert( + abi.encodeWithSelector(ILBToken.LBToken__TransferExceedsBalance.selector, address(1), id, amount + 1) + ); + vm.prank(address(1)); + lbToken.batchTransferFrom(address(1), address(2), ids, amounts); + + vm.expectRevert( + abi.encodeWithSelector(ILBToken.LBToken__BurnExceedsBalance.selector, address(1), id, amount + 1) + ); + vm.prank(address(1)); + lbToken.batchBurnFrom(address(1), ids, amounts); + } + + // Helper Functions + + function _updateMintAmount(MintCase memory mint) internal view { + uint256 id = mint.id; + uint256 mintAmount = mint.mintAmount; + + uint256 minted = idToAmount0[id]; + + if (mintAmount > type(uint256).max - minted) { + mint.mintAmount = type(uint256).max - minted; + } + } + + function _updateBurnAmount(BurnCase memory burn) internal view { + uint256 id = burn.id; + uint256 burnAmount = burn.burnAmount; + + uint256 burned = idToAmount0[id] - idToAmount1[id]; + + if (burnAmount > burned) { + burn.burnAmount = burned; + } + } +} + +contract LBTokenCoverage is LBToken { + function mintBatch(address to, uint256[] calldata ids, uint256[] calldata amounts) external { + _mintBatch(to, ids, amounts); + } + + function batchBurnFrom(address from, uint256[] calldata ids, uint256[] calldata amounts) external { + _burnBatch(from, ids, amounts); + } +} diff --git a/test/LiquidityConfigurations.t.sol b/test/LiquidityConfigurations.t.sol new file mode 100644 index 00000000..6ed7fe1d --- /dev/null +++ b/test/LiquidityConfigurations.t.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../src/libraries/math/LiquidityConfigurations.sol"; + +contract LiquidityConfigurationsTest is Test { + using PackedUint128Math for bytes32; + using PackedUint128Math for uint128; + using LiquidityConfigurations for bytes32; + + function testFuzz_EncodeParams(uint64 distributionX, uint64 distributionY, uint24 id) external { + bytes32 config = LiquidityConfigurations.encodeParams(distributionX, distributionY, id); + + assertEq( + uint256(config), + uint256(distributionX) << 88 | uint256(distributionY) << 24 | id, + "testFuzz_EncodeParams::1" + ); + } + + function testFuzz_DecodeParams(bytes32 config) external { + uint64 distributionX = uint64(uint256(config) >> 88); + uint64 distributionY = uint64(uint256(config) >> 24); + uint24 id = uint24(uint256(config)); + + if (uint256(config) > type(uint152).max || distributionX > 1e18 || distributionY > 1e18) { + vm.expectRevert(LiquidityConfigurations.LiquidityConfigurations__InvalidConfig.selector); + } + + (uint64 _distributionX, uint64 _distributionY, uint24 _id) = config.decodeParams(); + + assertEq(_distributionX, distributionX, "testFuzz_DecodeParams::1"); + assertEq(_distributionY, distributionY, "testFuzz_DecodeParams::2"); + assertEq(_id, id, "testFuzz_DecodeParams::3"); + } + + function testFuzz_GetAmountsAndId(bytes32 config, bytes32 amounts) external { + uint64 distributionX = uint64(uint256(config) >> 88); + uint64 distributionY = uint64(uint256(config) >> 24); + uint24 id = uint24(uint256(config)); + + if (uint256(config) > type(uint152).max || distributionX > 1e18 || distributionY > 1e18) { + vm.expectRevert(LiquidityConfigurations.LiquidityConfigurations__InvalidConfig.selector); + } + + (distributionX, distributionY, id) = config.decodeParams(); + + (uint128 x1, uint128 x2) = amounts.decode(); + + uint128 y1 = uint128(uint256(x1) * distributionX / 1e18); + uint128 y2 = uint128(uint256(x2) * distributionY / 1e18); + + bytes32 amountsInToBin = y1.encode(y2); + + (bytes32 _amountsInToBin, uint24 _id) = config.getAmountsAndId(amounts); + + assertEq(_amountsInToBin, amountsInToBin, "testFuzz_GetAmountsAndId::1"); + assertEq(_id, id, "testFuzz_GetAmountsAndId::2"); + } +} diff --git a/test/PackedUint128Math.t.sol b/test/PackedUint128Math.t.sol new file mode 100644 index 00000000..599e297d --- /dev/null +++ b/test/PackedUint128Math.t.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../src/libraries/math/PackedUint128Math.sol"; + +contract PackedUint128MathTest is Test { + using PackedUint128Math for bytes32; + using PackedUint128Math for uint128; + + function testFuzz_Encode(uint128 x1, uint128 x2) external { + assertEq(bytes32(x1 | uint256(x2) << 128), x1.encode(x2), "testFuzz_Encode::1"); + } + + function testFuzz_EncodeFirst(uint128 x1) external { + assertEq(bytes32(uint256(x1)), x1.encodeFirst(), "testFuzz_EncodeFirst::1"); + } + + function testFuzz_EncodeSecond(uint128 x2) external { + assertEq(bytes32(uint256(x2) << 128), x2.encodeSecond(), "testFuzz_EncodeSecond::1"); + } + + function testFuzz_EncodeBool(uint128 x, bool first) external { + assertEq(bytes32(uint256(x) << (first ? 0 : 128)), x.encode(first), "testFuzz_EncodeBool::1"); + } + + function testFuzz_Decode(bytes32 x) external { + (uint128 x1, uint128 x2) = x.decode(); + + assertEq(x1, uint128(uint256(x)), "testFuzz_Decode::1"); + assertEq(x2, uint128(uint256(x) >> 128), "testFuzz_Decode::2"); + } + + function testFuzz_DecodeFirst(bytes32 x) external { + assertEq(uint128(uint256(x)), x.decodeFirst(), "testFuzz_DecodeFirst::1"); + } + + function testFuzz_DecodeSecond(bytes32 x) external { + assertEq(uint128(uint256(x) >> 128), x.decodeSecond(), "testFuzz_DecodeSecond::1"); + } + + function testFuzz_DecodeBool(bytes32 x, bool first) external { + assertEq(uint128(uint256(x) >> (first ? 0 : 128)), x.decode(first), "testFuzz_DecodeBool::1"); + } + + function test_AddSelf() external { + bytes32 x = bytes32(uint256(1 << 128 | 1)); + + assertEq(x.add(x), bytes32(uint256(2 << 128 | 2)), "testFuzz_AddSelf::1"); + } + + function test_AddOverflow() external { + bytes32 x = bytes32(type(uint256).max); + + bytes32 y1 = bytes32(uint256(1)); + bytes32 y2 = bytes32(uint256(1 << 128)); + bytes32 y3 = y1 | y2; + + vm.expectRevert(PackedUint128Math.PackedUint128Math__AddOverflow.selector); + x.add(y1); + + vm.expectRevert(PackedUint128Math.PackedUint128Math__AddOverflow.selector); + x.add(y2); + + vm.expectRevert(PackedUint128Math.PackedUint128Math__AddOverflow.selector); + x.add(y3); + + vm.expectRevert(PackedUint128Math.PackedUint128Math__AddOverflow.selector); + y1.add(x); + + vm.expectRevert(PackedUint128Math.PackedUint128Math__AddOverflow.selector); + y2.add(x); + + vm.expectRevert(PackedUint128Math.PackedUint128Math__AddOverflow.selector); + y3.add(x); + } + + function testFuzz_Add(bytes32 x, bytes32 y) external { + uint128 x1 = uint128(uint256(x)); + uint128 x2 = uint128(uint256(x >> 128)); + + uint128 y1 = uint128(uint256(y)); + uint128 y2 = uint128(uint256(y >> 128)); + + if (x1 <= type(uint128).max - y1 && x2 <= type(uint128).max - y2) { + assertEq(x.add(y), bytes32(uint256(x1 + y1) | uint256(x2 + y2) << 128), "testFuzz_Add::1"); + } else { + vm.expectRevert(PackedUint128Math.PackedUint128Math__AddOverflow.selector); + x.add(y); + } + } + + function test_SubSelf() external { + bytes32 x = bytes32(uint256(1 << 128 | 1)); + + assertEq(x.sub(x), bytes32(0), "testFuzz_SubSelf::1"); + } + + function test_SubUnderflow() external { + bytes32 x = bytes32(0); + + bytes32 y1 = bytes32(uint256(1)); + bytes32 y2 = bytes32(uint256(1 << 128)); + bytes32 y3 = y1 | y2; + + assertEq(y1.sub(x), y1, "testFuzz_SubUnderflow::1"); + assertEq(y2.sub(x), y2, "testFuzz_SubUnderflow::2"); + assertEq(y3.sub(x), y3, "testFuzz_SubUnderflow::3"); + + vm.expectRevert(PackedUint128Math.PackedUint128Math__SubUnderflow.selector); + x.sub(y1); + + vm.expectRevert(PackedUint128Math.PackedUint128Math__SubUnderflow.selector); + x.sub(y2); + + vm.expectRevert(PackedUint128Math.PackedUint128Math__SubUnderflow.selector); + x.sub(y3); + } + + function testFuzz_Sub(bytes32 x, bytes32 y) external { + uint128 x1 = uint128(uint256(x)); + uint128 x2 = uint128(uint256(x >> 128)); + + uint128 y1 = uint128(uint256(y)); + uint128 y2 = uint128(uint256(y >> 128)); + + if (x1 >= y1 && x2 >= y2) { + assertEq(x.sub(y), bytes32(uint256(x1 - y1) | uint256(x2 - y2) << 128), "testFuzz_Sub::1"); + } else { + vm.expectRevert(PackedUint128Math.PackedUint128Math__SubUnderflow.selector); + x.sub(y); + } + } + + function testFuzz_LessThan(bytes32 x, bytes32 y) external { + (uint128 x1, uint128 x2) = x.decode(); + (uint128 y1, uint128 y2) = y.decode(); + + assertEq(x.lt(y), x1 < y1 || x2 < y2, "testFuzz_LessThan::1"); + } + + function testFuzz_GreaterThan(bytes32 x, bytes32 y) external { + (uint128 x1, uint128 x2) = x.decode(); + (uint128 y1, uint128 y2) = y.decode(); + + assertEq(x.gt(y), x1 > y1 || x2 > y2, "testFuzz_GreaterThan::1"); + } + + function testFuzz_ScalarMulShift128RoundUp(bytes32 x, uint128 multiplier) external { + (uint128 x1, uint128 x2) = x.decode(); + + uint256 y1 = uint256(x1) * multiplier; + uint256 y2 = uint256(x2) * multiplier; + + uint256 z1 = y1 == 0 ? 0 : ((y1 - 1) >> 128) + 1; + uint256 z2 = y2 == 0 ? 0 : ((y2 - 1) >> 128) + 1; + + assertLe(z1, type(uint128).max, "testFuzz_ScalarMulShift128RoundUp::1"); + assertLe(z2, type(uint128).max, "testFuzz_ScalarMulShift128RoundUp::2"); + + assertEq( + x.scalarMulShift128RoundUp(multiplier), + uint128(z1).encode(uint128(z2)), + "testFuzz_ScalarMulShift128RoundUp::3" + ); + } + + function testFuzz_ScalarMulDivBasisPointRoundDown(bytes32 x, uint128 multipilier) external { + (uint128 x1, uint128 x2) = x.decode(); + + uint256 y1 = uint256(x1) * multipilier; + uint256 y2 = uint256(x2) * multipilier; + + uint256 z1 = y1 / Constants.BASIS_POINT_MAX; + uint256 z2 = y2 / Constants.BASIS_POINT_MAX; + + if (multipilier > Constants.BASIS_POINT_MAX) { + vm.expectRevert(PackedUint128Math.PackedUint128Math__MultiplierBiggerThanMax.selector); + x.scalarMulDivBasisPointRoundDown(multipilier); + } else { + assertLe(z1, type(uint128).max, "testFuzz_ScalarMulDivBasisPointRoundDown::1"); + assertLe(z2, type(uint128).max, "testFuzz_ScalarMulDivBasisPointRoundDown::2"); + + assertEq( + x.scalarMulDivBasisPointRoundDown(multipilier), + uint128(z1).encode(uint128(z2)), + "testFuzz_ScalarMulDivBasisPointRoundDown::3" + ); + } + } +} diff --git a/test/PriceHelper.t.sol b/test/PriceHelper.t.sol new file mode 100644 index 00000000..bdb821ae --- /dev/null +++ b/test/PriceHelper.t.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../src/libraries/PriceHelper.sol"; + +contract PriceHelperTest is Test { + using PriceHelper for bytes32; + //TODO +} diff --git a/test/SafeCast.t.sol b/test/SafeCast.t.sol new file mode 100644 index 00000000..2d3474b9 --- /dev/null +++ b/test/SafeCast.t.sol @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../src/libraries/math/SafeCast.sol"; + +contract SafeCastTest is Test { + using SafeCast for uint256; + + function testFuzz_SafeCast248(uint256 x) external { + if (x > type(uint248).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds248Bits.selector); + x.safe248(); + } else { + assertEq(x.safe248(), uint248(x), "testFuzz_SafeCast248::1"); + } + } + + function testFuzz_SafeCast240(uint256 x) external { + if (x > type(uint240).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds240Bits.selector); + x.safe240(); + } else { + assertEq(x.safe240(), uint240(x), "testFuzz_SafeCast240::1"); + } + } + + function testFuzz_SafeCast232(uint256 x) external { + if (x > type(uint232).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds232Bits.selector); + x.safe232(); + } else { + assertEq(x.safe232(), uint232(x), "testFuzz_SafeCast232::1"); + } + } + + function testFuzz_SafeCast224(uint256 x) external { + if (x > type(uint224).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds224Bits.selector); + x.safe224(); + } else { + assertEq(x.safe224(), uint224(x), "testFuzz_SafeCast224::1"); + } + } + + function testFuzz_SafeCast216(uint256 x) external { + if (x > type(uint216).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds216Bits.selector); + x.safe216(); + } else { + assertEq(x.safe216(), uint216(x), "testFuzz_SafeCast216::1"); + } + } + + function testFuzz_SafeCast208(uint256 x) external { + if (x > type(uint208).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds208Bits.selector); + x.safe208(); + } else { + assertEq(x.safe208(), uint208(x), "testFuzz_SafeCast208::1"); + } + } + + function testFuzz_SafeCast200(uint256 x) external { + if (x > type(uint200).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds200Bits.selector); + x.safe200(); + } else { + assertEq(x.safe200(), uint200(x), "testFuzz_SafeCast200::1"); + } + } + + function testFuzz_SafeCast192(uint256 x) external { + if (x > type(uint192).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds192Bits.selector); + x.safe192(); + } else { + assertEq(x.safe192(), uint192(x), "testFuzz_SafeCast192::1"); + } + } + + function testFuzz_SafeCast184(uint256 x) external { + if (x > type(uint184).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds184Bits.selector); + x.safe184(); + } else { + assertEq(x.safe184(), uint184(x), "testFuzz_SafeCast184::1"); + } + } + + function testFuzz_SafeCast176(uint256 x) external { + if (x > type(uint176).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds176Bits.selector); + x.safe176(); + } else { + assertEq(x.safe176(), uint176(x), "testFuzz_SafeCast176::1"); + } + } + + function testFuzz_SafeCast168(uint256 x) external { + if (x > type(uint168).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds168Bits.selector); + x.safe168(); + } else { + assertEq(x.safe168(), uint168(x), "testFuzz_SafeCast168::1"); + } + } + + function testFuzz_SafeCast160(uint256 x) external { + if (x > type(uint160).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds160Bits.selector); + x.safe160(); + } else { + assertEq(x.safe160(), uint160(x), "testFuzz_SafeCast160::1"); + } + } + + function testFuzz_SafeCast152(uint256 x) external { + if (x > type(uint152).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds152Bits.selector); + x.safe152(); + } else { + assertEq(x.safe152(), uint152(x), "testFuzz_SafeCast152::1"); + } + } + + function testFuzz_SafeCast144(uint256 x) external { + if (x > type(uint144).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds144Bits.selector); + x.safe144(); + } else { + assertEq(x.safe144(), uint144(x), "testFuzz_SafeCast144::1"); + } + } + + function testFuzz_SafeCast136(uint256 x) external { + if (x > type(uint136).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds136Bits.selector); + x.safe136(); + } else { + assertEq(x.safe136(), uint136(x), "testFuzz_SafeCast136::1"); + } + } + + function testFuzz_SafeCast128(uint256 x) external { + if (x > type(uint128).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds128Bits.selector); + x.safe128(); + } else { + assertEq(x.safe128(), uint128(x), "testFuzz_SafeCast128::1"); + } + } + + function testFuzz_SafeCast120(uint256 x) external { + if (x > type(uint120).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds120Bits.selector); + x.safe120(); + } else { + assertEq(x.safe120(), uint120(x), "testFuzz_SafeCast120::1"); + } + } + + function testFuzz_SafeCast112(uint256 x) external { + if (x > type(uint112).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds112Bits.selector); + x.safe112(); + } else { + assertEq(x.safe112(), uint112(x), "testFuzz_SafeCast112::1"); + } + } + + function testFuzz_SafeCast104(uint256 x) external { + if (x > type(uint104).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds104Bits.selector); + x.safe104(); + } else { + assertEq(x.safe104(), uint104(x), "testFuzz_SafeCast104::1"); + } + } + + function testFuzz_SafeCast96(uint256 x) external { + if (x > type(uint96).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds96Bits.selector); + x.safe96(); + } else { + assertEq(x.safe96(), uint96(x), "testFuzz_SafeCast96::1"); + } + } + + function testFuzz_SafeCast88(uint256 x) external { + if (x > type(uint88).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds88Bits.selector); + x.safe88(); + } else { + assertEq(x.safe88(), uint88(x), "testFuzz_SafeCast88::1"); + } + } + + function testFuzz_SafeCast80(uint256 x) external { + if (x > type(uint80).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds80Bits.selector); + x.safe80(); + } else { + assertEq(x.safe80(), uint80(x), "testFuzz_SafeCast80::1"); + } + } + + function testFuzz_SafeCast72(uint256 x) external { + if (x > type(uint72).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds72Bits.selector); + x.safe72(); + } else { + assertEq(x.safe72(), uint72(x), "testFuzz_SafeCast72::1"); + } + } + + function testFuzz_SafeCast64(uint256 x) external { + if (x > type(uint64).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds64Bits.selector); + x.safe64(); + } else { + assertEq(x.safe64(), uint64(x), "testFuzz_SafeCast64::1"); + } + } + + function testFuzz_SafeCast56(uint256 x) external { + if (x > type(uint56).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds56Bits.selector); + x.safe56(); + } else { + assertEq(x.safe56(), uint56(x), "testFuzz_SafeCast56::1"); + } + } + + function testFuzz_SafeCast48(uint256 x) external { + if (x > type(uint48).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds48Bits.selector); + x.safe48(); + } else { + assertEq(x.safe48(), uint48(x), "testFuzz_SafeCast48::1"); + } + } + + function testFuzz_SafeCast40(uint256 x) external { + if (x > type(uint40).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds40Bits.selector); + x.safe40(); + } else { + assertEq(x.safe40(), uint40(x), "testFuzz_SafeCast40::1"); + } + } + + function testFuzz_SafeCast32(uint256 x) external { + if (x > type(uint32).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds32Bits.selector); + x.safe32(); + } else { + assertEq(x.safe32(), uint32(x), "testFuzz_SafeCast32::1"); + } + } + + function testFuzz_SafeCast24(uint256 x) external { + if (x > type(uint24).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds24Bits.selector); + x.safe24(); + } else { + assertEq(x.safe24(), uint24(x), "testFuzz_SafeCast24::1"); + } + } + + function testFuzz_SafeCast16(uint256 x) external { + if (x > type(uint16).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds16Bits.selector); + x.safe16(); + } else { + assertEq(x.safe16(), uint16(x), "testFuzz_SafeCast16::1"); + } + } + + function testFuzz_SafeCast8(uint256 x) external { + if (x > type(uint8).max) { + vm.expectRevert(SafeCast.SafeCast__Exceeds8Bits.selector); + x.safe8(); + } else { + assertEq(x.safe8(), uint8(x), "testFuzz_SafeCast8::1"); + } + } +} diff --git a/test/SampleMath.t.sol b/test/SampleMath.t.sol new file mode 100644 index 00000000..1b0b50f0 --- /dev/null +++ b/test/SampleMath.t.sol @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../src/libraries/math/SampleMath.sol"; + +contract SampleMathTest is Test { + using SampleMath for bytes32; + + function testFuzz_GetOracleLength(bytes32 sample) external { + uint256 oracleLength = sample.getOracleLength(); + assertLe(oracleLength, type(uint16).max, "testFuzz_GetOracleLength::1"); + assertEq(uint16(uint256(sample)), oracleLength, "testFuzz_GetOracleLength::2"); + } + + function testFuzz_GetCumulativeId(bytes32 sample) external { + uint256 cumulativeId = sample.getCumulativeId(); + assertLe(cumulativeId, type(uint64).max, "testFuzz_GetCumulativeId::1"); + assertEq(uint64(uint256(sample) >> 16), cumulativeId, "testFuzz_GetCumulativeId::2"); + } + + function testFuzz_GetCumulativeVolatility(bytes32 sample) external { + uint256 cumulativeVolatility = sample.getCumulativeVolatility(); + assertLe(cumulativeVolatility, type(uint64).max, "testFuzz_GetCumulativeVolatility::1"); + assertEq(uint64(uint256(sample) >> 80), cumulativeVolatility, "testFuzz_GetCumulativeVolatility::2"); + } + + function testFuzz_GetCumulativeBinCrossed(bytes32 sample) external { + uint256 cumulativeBinCrossed = sample.getCumulativeBinCrossed(); + assertLe(cumulativeBinCrossed, type(uint64).max, "testFuzz_GetCumulativeBinCrossed::1"); + assertEq(uint64(uint256(sample) >> 144), cumulativeBinCrossed, "testFuzz_GetCumulativeBinCrossed::2"); + } + + function testFuzz_GetSampleLifetime(bytes32 sample) external { + uint256 sampleLifetime = sample.getSampleLifetime(); + assertLe(sampleLifetime, type(uint8).max, "testFuzz_GetSampleLifetime::1"); + assertEq(uint8(uint256(sample) >> 208), sampleLifetime, "testFuzz_GetSampleLifetime::2"); + } + + function testFuzz_GetSampleCreation(bytes32 sample) external { + uint256 sampleCreation = sample.getSampleCreation(); + assertLe(sampleCreation, type(uint40).max, "testFuzz_GetSampleCreation::1"); + assertEq(uint40(uint256(sample) >> 216), sampleCreation, "testFuzz_GetSampleCreation::2"); + } + + function testFuzz_GetSampleLastUpdate(bytes32 sample) external { + uint40 sampleCreation = sample.getSampleCreation(); + uint8 sampleLifetime = sample.getSampleLifetime(); + + if (sampleCreation > type(uint40).max - sampleLifetime) { + vm.expectRevert(); + sample.getSampleLastUpdate(); + } else { + uint40 sampleLastUpdate = sample.getSampleLastUpdate(); + assertEq(sampleLastUpdate, sampleCreation + sampleLifetime, "testFuzz_GetSampleLastUpdate::1"); + } + } + + function testFuzz_encode( + uint16 oracleLength, + uint64 cumulativeId, + uint64 cumulativeVolatility, + uint64 cumulativeBinCrossed, + uint8 sampleLifetime, + uint40 createdAt + ) external { + bytes32 sample = SampleMath.encode( + oracleLength, cumulativeId, cumulativeVolatility, cumulativeBinCrossed, sampleLifetime, createdAt + ); + + assertEq(sample.getOracleLength(), oracleLength, "testFuzz_encode::1"); + assertEq(sample.getCumulativeId(), cumulativeId, "testFuzz_encode::2"); + assertEq(sample.getCumulativeVolatility(), cumulativeVolatility, "testFuzz_encode::3"); + assertEq(sample.getCumulativeBinCrossed(), cumulativeBinCrossed, "testFuzz_encode::4"); + assertEq(sample.getSampleLifetime(), sampleLifetime, "testFuzz_encode::5"); + assertEq(sample.getSampleCreation(), createdAt, "testFuzz_encode::6"); + } + + function testFuzz_decode(bytes32 sample) external { + ( + uint16 oracleLength, + uint64 cumulativeId, + uint64 cumulativeVolatility, + uint64 cumulativeBinCrossed, + uint8 sampleLifetime, + uint40 createdAt + ) = SampleMath.decode(sample); + + assertEq(oracleLength, sample.getOracleLength(), "testFuzz_decode::1"); + assertEq(cumulativeId, sample.getCumulativeId(), "testFuzz_decode::2"); + assertEq(cumulativeVolatility, sample.getCumulativeVolatility(), "testFuzz_decode::3"); + assertEq(cumulativeBinCrossed, sample.getCumulativeBinCrossed(), "testFuzz_decode::4"); + assertEq(sampleLifetime, sample.getSampleLifetime(), "testFuzz_decode::5"); + assertEq(createdAt, sample.getSampleCreation(), "testFuzz_decode::6"); + } + + function testFuzz_GetWeightedAverage(bytes32 sample1, bytes32 sample2, uint40 weight1, uint40 weight2) external { + uint256 totalWeight = uint256(weight1) + weight2; + + if (totalWeight == 0) { + vm.expectRevert(); + sample1.getWeightedAverage(sample2, weight1, weight2); + } + + (uint256 wAverageId, uint256 wAverageVolatility, uint256 wAverageBinCrossed) = (0, 0, 0); + + { + uint256 cId1 = sample1.getCumulativeId(); + uint256 cVol1 = sample1.getCumulativeVolatility(); + uint256 cBin1 = sample1.getCumulativeBinCrossed(); + + uint256 cId2 = sample2.getCumulativeId(); + uint256 cVol2 = sample2.getCumulativeVolatility(); + uint256 cBin2 = sample2.getCumulativeBinCrossed(); + + wAverageId = (cId1 * weight1 + cId2 * weight2) / totalWeight; + wAverageVolatility = (cVol1 * weight1 + cVol2 * weight2) / totalWeight; + wAverageBinCrossed = (cBin1 * weight1 + cBin2 * weight2) / totalWeight; + } + + if ( + wAverageId > type(uint64).max || wAverageVolatility > type(uint64).max + || wAverageBinCrossed > type(uint64).max + ) { + vm.expectRevert(); + sample1.getWeightedAverage(sample2, weight1, weight2); + } else { + (uint64 weightedAverageId, uint64 weightedAverageVolatility, uint64 weightedAverageBinCrossed) = + sample1.getWeightedAverage(sample2, weight1, weight2); + + assertEq(weightedAverageId, wAverageId, "testFuzz_GetWeightedAverage::1"); + assertEq(weightedAverageVolatility, wAverageVolatility, "testFuzz_GetWeightedAverage::2"); + assertEq(weightedAverageBinCrossed, wAverageBinCrossed, "testFuzz_GetWeightedAverage::3"); + } + } + + function testFuzz_update(uint40 deltaTime, uint24 activeId, uint24 volatilityAccumulated, uint24 binCrossed) + external + { + (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = + bytes32(0).update(deltaTime, activeId, volatilityAccumulated, binCrossed); + + assertEq(cumulativeId, uint64(activeId) * deltaTime, "testFuzz_update::1"); + assertEq(cumulativeVolatility, uint64(volatilityAccumulated) * deltaTime, "testFuzz_update::2"); + assertEq(cumulativeBinCrossed, uint64(binCrossed) * deltaTime, "testFuzz_update::3"); + } + + function testFuzz_updateWithSample( + bytes32 sample, + uint40 deltaTime, + uint24 activeId, + uint24 volatilityAccumulated, + uint24 binCrossed + ) external { + uint64 currentCumulativeId = sample.getCumulativeId(); + uint64 currentCumulativeVolatility = sample.getCumulativeVolatility(); + uint64 currentCumulativeBinCrossed = sample.getCumulativeBinCrossed(); + + uint64(deltaTime) * activeId; + uint64(deltaTime) * volatilityAccumulated; + uint64(deltaTime) * binCrossed; + + if ( + uint64(deltaTime) * activeId > type(uint64).max - currentCumulativeId + || uint64(deltaTime) * volatilityAccumulated > type(uint64).max - currentCumulativeVolatility + || uint64(deltaTime) * binCrossed > type(uint64).max - currentCumulativeBinCrossed + ) { + vm.expectRevert(); + sample.update(deltaTime, activeId, volatilityAccumulated, binCrossed); + } else { + (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = + sample.update(deltaTime, activeId, volatilityAccumulated, binCrossed); + + assertEq( + uint64(cumulativeId), currentCumulativeId + uint64(activeId) * deltaTime, "testFuzz_updateWithSample::1" + ); + assertEq( + uint64(cumulativeVolatility), + currentCumulativeVolatility + uint64(volatilityAccumulated) * deltaTime, + "testFuzz_updateWithSample::2" + ); + assertEq( + uint64(cumulativeBinCrossed), + currentCumulativeBinCrossed + uint64(binCrossed) * deltaTime, + "testFuzz_updateWithSample::3" + ); + } + } +} diff --git a/test/TestImmutableClone.sol b/test/TestImmutableClone.sol new file mode 100644 index 00000000..f27c7e62 --- /dev/null +++ b/test/TestImmutableClone.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../src/libraries/Clone.sol"; +import "../src/libraries/ImmutableClone.sol"; + +contract TestImmutableClone is Test { + function testFuzz_CloneDeterministic(bytes32 salt) public { + address clone = address(ImmutableClone.cloneDeterministic(address(1), "", salt)); + + assertEq( + clone, + ImmutableClone.predictDeterministicAddress(address(1), "", salt, address(this)), + "testFuzz_CloneDeterministic::1" + ); + + // Check that cloning twice with the same salt reverts + vm.expectRevert(ImmutableClone.DeploymentFailed.selector); + ImmutableClone.cloneDeterministic(address(1), "", salt); + } + + function testFuzz_Implementation(bytes memory data) public { + vm.assume(data.length <= 0xffca); + + address implementation = address(new Implementation()); + bytes32 salt = keccak256("salt"); + + address clone = address(ImmutableClone.cloneDeterministic(implementation, data, salt)); + + Implementation implementationClone = Implementation(clone); + + assertEq(implementationClone.getBytes(data.length), data, "testFuzz_Implementation::1"); + } + + function testFuzz_Pair(address tokenX, address tokenY, uint8 binStep) public { + address implementation = address(new Pair()); + bytes32 salt = keccak256("salt"); + + address clone = + address(ImmutableClone.cloneDeterministic(implementation, abi.encodePacked(tokenX, tokenY, binStep), salt)); + + Pair pair = Pair(clone); + + assertEq(pair.getTokenX(), tokenX, "testFuzz_Pair::1"); + assertEq(pair.getTokenY(), tokenY, "testFuzz_Pair::2"); + assertEq(pair.getBinStep(), binStep, "testFuzz_Pair::3"); + } + + function test_CloneDeterministicMaxLength() public { + bytes memory b = new bytes(0xffc8); + + assembly { + mstore8(add(b, 0x20), 0xff) + mstore8(add(b, mload(b)), 0xca) + } + + address implementation = address(new Implementation()); + address clone = ImmutableClone.cloneDeterministic(implementation, b, bytes32(0)); + + assertEq(Implementation(clone).getBytes(b.length), b, "testFuzz_CloneDeterministicMaxLength::1"); + } + + function test_CloneDeterministicTooBig() public { + bytes memory b = new bytes(0xffc8 + 1); + vm.expectRevert(ImmutableClone.PackedDataTooBig.selector); + ImmutableClone.cloneDeterministic(address(1), b, bytes32(0)); + } +} + +contract Pair is Clone { + function getTokenX() public pure returns (address) { + return _getArgAddress(0); + } + + function getTokenY() public pure returns (address) { + return _getArgAddress(20); + } + + function getBinStep() public pure returns (uint8) { + return _getArgUint8(40); + } +} + +contract Implementation is Clone { + function getBytes(uint256 length) public pure returns (bytes memory) { + return _getArgBytes(0, length); + } +} diff --git a/test/TreeMath.t.sol b/test/TreeMath.t.sol new file mode 100644 index 00000000..25863f3a --- /dev/null +++ b/test/TreeMath.t.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../src/libraries/math/TreeMath.sol"; + +contract TreeMathTest is Test { + using TreeMath for TreeMath.TreeUint24; + + TreeMath.TreeUint24 private _tree; + + function testFuzz_AddToTree(uint24[] calldata ids) external { + for (uint256 i = 0; i < ids.length; i++) { + bool contains = _tree.contains(ids[i]); + assertEq(_tree.add(ids[i]), !contains, "testFuzz_AddToTree::1"); + assertEq(_tree.contains(ids[i]), true, "testFuzz_AddToTree::2"); + } + } + + function testFuzz_RemoveFromTree(uint24[] calldata ids) external { + for (uint256 i = 0; i < ids.length; i++) { + _tree.add(ids[i]); + } + + for (uint256 i = 0; i < ids.length; i++) { + bool contains = _tree.contains(ids[i]); + assertEq(_tree.remove(ids[i]), contains, "testFuzz_RemoveFromTree::1"); + assertEq(_tree.contains(ids[i]), false, "testFuzz_RemoveFromTree::2"); + } + } + + function test_FindFirst() external { + _tree.add(0); + _tree.add(1); + _tree.add(2); + + assertEq(_tree.findFirstRight(2), 1, "testFuzz_FindFirst::1"); + assertEq(_tree.findFirstRight(1), 0, "testFuzz_FindFirst::2"); + + assertEq(_tree.findFirstLeft(0), 1, "testFuzz_FindFirst::3"); + assertEq(_tree.findFirstLeft(1), 2, "testFuzz_FindFirst::4"); + + assertEq(_tree.findFirstRight(0), type(uint24).max, "testFuzz_FindFirst::5"); + assertEq(_tree.findFirstLeft(2), 0, "testFuzz_FindFirst::6"); + } + + function test_FindFirstFar() external { + _tree.add(0); + _tree.add(type(uint24).max); + + assertEq(_tree.findFirstRight(type(uint24).max), 0, "testFuzz_FindFirstFar::1"); + + assertEq(_tree.findFirstLeft(0), type(uint24).max, "testFuzz_FindFirstFar::2"); + } + + function testFuzz_FindFirst(uint24[] calldata ids) external { + vm.assume(ids.length > 0); + + for (uint256 i = 0; i < ids.length; i++) { + _tree.add(ids[i]); + } + + for (uint256 i = 0; i < ids.length; i++) { + uint24 id = ids[i]; + + uint24 firstRight = _tree.findFirstRight(id); + uint24 firstLeft = _tree.findFirstLeft(id); + + if (firstRight != type(uint24).max) { + assertEq(_tree.contains(firstRight), true, "testFuzz_FindFirst::1"); + assertEq(firstRight < id, true, "testFuzz_FindFirst::2"); + } + + if (firstLeft != 0) { + assertEq(_tree.contains(firstLeft), true, "testFuzz_FindFirst::3"); + assertEq(firstLeft > id, true, "testFuzz_FindFirst::4"); + } + } + } +} diff --git a/test/Uint128x128Math.t.sol b/test/Uint128x128Math.t.sol new file mode 100644 index 00000000..8fe888fb --- /dev/null +++ b/test/Uint128x128Math.t.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../src/libraries/math/Uint128x128Math.sol"; + +contract Uint128x128MathTest is Test { + using Uint128x128Math for uint256; + + function test_Pow() external { + uint256 res = _toUint128x128(1.0001e18).pow(100_000); + + assertApproxEqRel(_toUint256(res), 22015.456048527954e18, 1e12, "test_Pow::1"); + } + + function test_PowAndLog() external { + uint256 base = _toUint128x128(1.0001e18); + uint256 res = base.pow(100_000); + + int256 baselog2 = base.log2(); + + assertGt(baselog2, 0, "test_PowAndLog::1"); + assertApproxEqRel(res.log2() / baselog2, 100_000, 1e12, "test_PowAndLog::2"); + } + + function _toUint128x128(uint256 x) internal pure returns (uint256) { + return (x << 128) / 1e18; + } + + function _toUint256(uint256 x) internal pure returns (uint256) { + return (x * 1e18) >> 128; + } + + function _abs(int256 x) internal pure returns (uint256) { + return x < 0 ? uint256(-x) : uint256(x); + } +} diff --git a/test/Uint256x256Math.t.sol b/test/Uint256x256Math.t.sol new file mode 100644 index 00000000..0429872a --- /dev/null +++ b/test/Uint256x256Math.t.sol @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../src/libraries/math/Uint256x256Math.sol"; + +contract Uint256x256MathTest is Test { + using Uint256x256Math for uint256; + + function testFuzz_MulDivRoundDown(uint256 x, uint256 y, uint256 denominator) external { + if (denominator == 0) { + vm.expectRevert(); + x.mulDivRoundDown(y, denominator); + } else { + if (x == 0 || y == 0) { + assertEq(x.mulDivRoundDown(y, denominator), 0, "testFuzz_MulDivRoundDown::1"); + } else { + (, uint256 prod1) = _getProds(x, y); + + if (prod1 != 0 && denominator <= prod1) { + vm.expectRevert(Uint256x256Math.Uint256x256Math__MulDivOverflow.selector); + x.mulDivRoundDown(y, denominator); + } else { + assertEq( + x.mulDivRoundDown(y, denominator), + _trustedMulDiv(x, y, denominator), + "testFuzz_MulDivRoundDown::2" + ); + } + } + } + } + + function testFuzz_MulDivRoundUp(uint256 x, uint256 y, uint256 denominator) external { + if (denominator == 0) { + vm.expectRevert(); + x.mulDivRoundUp(y, denominator); + } + + (, uint256 prod1) = _getProds(x, y); + + if (prod1 != 0 && denominator <= prod1) { + vm.expectRevert(Uint256x256Math.Uint256x256Math__MulDivOverflow.selector); + x.mulDivRoundDown(y, denominator); + } else { + uint256 result = x.mulDivRoundDown(y, denominator); + if (mulmod(x, y, denominator) != 0) { + if (result == type(uint256).max) { + vm.expectRevert(); + x.mulDivRoundUp(y, denominator); + return; + } else { + result += 1; + } + } + + assertEq(x.mulDivRoundUp(y, denominator), result, "testFuzz_MulDivRoundUp::1"); + } + } + + function testFuzz_mulShiftRoundDown(uint256 x, uint256 y, uint8 shift) external { + (, uint256 prod1) = _getProds(x, y); + if (prod1 >> shift != 0) { + vm.expectRevert(Uint256x256Math.Uint256x256Math__MulShiftOverflow.selector); + x.mulShiftRoundDown(y, shift); + } else { + assertEq(x.mulShiftRoundDown(y, shift), x.mulDivRoundDown(y, 1 << shift), "testFuzz_MulShiftRoundDown::1"); + } + } + + function testFuzz_mulShiftRoundUp(uint256 x, uint256 y, uint8 shift) external { + (, uint256 prod1) = _getProds(x, y); + if (prod1 >> shift != 0) { + vm.expectRevert(Uint256x256Math.Uint256x256Math__MulShiftOverflow.selector); + x.mulShiftRoundUp(y, shift); + } else { + assertEq(x.mulShiftRoundUp(y, shift), x.mulDivRoundUp(y, 1 << shift), "testFuzz_MulShiftRoundUp::1"); + } + } + + function testFuzz_ShiftDivRoundDown(uint256 x, uint8 shift, uint256 denominator) external { + if (denominator == 0) { + vm.expectRevert(); + x.shiftDivRoundDown(shift, denominator); + } else { + (, uint256 prod1) = _getProds(x, 1 << shift); + + if (prod1 != 0 && denominator <= prod1) { + vm.expectRevert(Uint256x256Math.Uint256x256Math__MulDivOverflow.selector); + x.shiftDivRoundDown(shift, denominator); + } else { + assertEq( + x.shiftDivRoundDown(shift, denominator), + _trustedMulDiv(x, 1 << shift, denominator), + "testFuzz_ShiftDivRoundDown::1" + ); + } + } + } + + function testFuzz_ShiftDivRoundUp(uint256 x, uint8 shift, uint256 denominator) external { + if (denominator == 0) { + vm.expectRevert(); + x.shiftDivRoundUp(shift, denominator); + } else { + (, uint256 prod1) = _getProds(x, 1 << shift); + + if (prod1 != 0 && denominator <= prod1) { + vm.expectRevert(Uint256x256Math.Uint256x256Math__MulDivOverflow.selector); + x.shiftDivRoundUp(shift, denominator); + } else { + uint256 result = _trustedMulDiv(x, 1 << shift, denominator); + if (mulmod(x, 1 << shift, denominator) != 0) { + if (result == type(uint256).max) { + vm.expectRevert(); + x.shiftDivRoundUp(shift, denominator); + return; + } else { + result += 1; + } + } + + assertEq(x.shiftDivRoundUp(shift, denominator), result, "testFuzz_ShiftDivRoundUp::1"); + } + } + } + + function _getProds(uint256 x, uint256 y) private pure returns (uint256 prod0, uint256 prod1) { + assembly { + let mm := mulmod(x, y, not(0)) + prod0 := mul(x, y) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + } + + /** + * Trusted muldiv implementation, used to verify that the muldiv is right. + */ + + /** + * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) + * with further edits by Uniswap Labs also under MIT license. + */ + function _trustedMulDiv(uint256 x, uint256 y, uint256 denominator) private pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use + // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2^256 + prod0. + uint256 prod0; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(x, y, not(0)) + prod0 := mul(x, y) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division. + if (prod1 == 0) { + return prod0 / denominator; + } + + // Make sure the result is less than 2^256. Also prevents denominator == 0. + require(denominator > prod1); + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0]. + uint256 remainder; + assembly { + // Compute remainder using mulmod. + remainder := mulmod(x, y, denominator) + + // Subtract 256 bit number from 512 bit number. + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. + // See https://cs.stackexchange.com/q/138556/92363. + + // Does not overflow because the denominator cannot be zero at this stage in the function. + uint256 twos = denominator & (~denominator + 1); + assembly { + // Divide denominator by twos. + denominator := div(denominator, twos) + + // Divide [prod1 prod0] by twos. + prod0 := div(prod0, twos) + + // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. + twos := add(div(sub(0, twos), twos), 1) + } + + // Shift in bits from prod1 into prod0. + prod0 |= prod1 * twos; + + // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such + // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for + // four bits. That is, denominator * inv = 1 mod 2^4. + uint256 inverse = (3 * denominator) ^ 2; + + // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works + // in modular arithmetic, doubling the correct bits in each step. + inverse *= 2 - denominator * inverse; // inverse mod 2^8 + inverse *= 2 - denominator * inverse; // inverse mod 2^16 + inverse *= 2 - denominator * inverse; // inverse mod 2^32 + inverse *= 2 - denominator * inverse; // inverse mod 2^64 + inverse *= 2 - denominator * inverse; // inverse mod 2^128 + inverse *= 2 - denominator * inverse; // inverse mod 2^256 + + // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. + // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is + // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inverse; + return result; + } + } +} From c25a6336d2eb7016e7333dba458aeb7493b535e1 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 25 Jan 2023 17:33:13 +0100 Subject: [PATCH 04/47] missing and fixing natspec --- src/libraries/BinHelper.sol | 5 +++++ src/libraries/Clone.sol | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index d6bbe2a5..8488948d 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -13,6 +13,11 @@ import "./FeeHelper.sol"; import "./PriceHelper.sol"; import "./TokenHelper.sol"; +/** + * @title Liquidity Book Bin Helper Library + * @author Trader Joe + * @notice This library contains functions to help interaction with bins. + */ library BinHelper { using PackedUint128Math for bytes32; using PackedUint128Math for uint128; diff --git a/src/libraries/Clone.sol b/src/libraries/Clone.sol index 1ddef455..30e1596e 100644 --- a/src/libraries/Clone.sol +++ b/src/libraries/Clone.sol @@ -2,12 +2,6 @@ pragma solidity 0.8.10; -/// @notice Class with helper read functions for clone with immutable args. -/// @author Trader Joe -/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Clone.sol) -/// @author Adapted from clones with immutable args by zefram.eth, Saw-mon & Natalie -/// (https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args) - /** * @title Clone * @notice Class with helper read functions for clone with immutable args. From 294b1ccc053379e81ada7458cd0806ff37ba379d Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 25 Jan 2023 17:34:54 +0100 Subject: [PATCH 05/47] uncommenting to merge --- src/LBFactory.sol | 1232 ++++++++++----------- src/LBQuoter.sol | 434 ++++---- src/LBRouter.sol | 1940 ++++++++++++++++----------------- src/interfaces/ILBFactory.sol | 234 ++-- src/interfaces/ILBRouter.sol | 360 +++--- test/BinHelper.T.sol | 28 +- test/helpers/TestHelper.sol | 550 +++++----- 7 files changed, 2387 insertions(+), 2391 deletions(-) diff --git a/src/LBFactory.sol b/src/LBFactory.sol index ed7f1dc1..bd001aef 100644 --- a/src/LBFactory.sol +++ b/src/LBFactory.sol @@ -2,619 +2,619 @@ pragma solidity 0.8.10; -// import "openzeppelin/proxy/Clones.sol"; -// import "openzeppelin/utils/structs/EnumerableSet.sol"; - -// import "./LBErrors.sol"; -// import "./libraries/BinHelper.sol"; -// import "./libraries/Constants.sol"; -// import "./libraries/Decoder.sol"; -// import "./libraries/PendingOwnable.sol"; -// import "./libraries/math/SafeCast.sol"; -// import "./interfaces/ILBFactory.sol"; - -// /// @title Liquidity Book Factory -// /// @author Trader Joe -// /// @notice Contract used to deploy and register new LBPairs. -// /// Enables setting fee parameters, flashloan fees and LBPair implementation. -// /// Unless the `creationUnlocked` is `true`, only the owner of the factory can create pairs. -// contract LBFactory is PendingOwnable, ILBFactory { -// using SafeCast for uint256; -// using Decoder for bytes32; -// using EnumerableSet for EnumerableSet.AddressSet; - -// uint256 public constant override MAX_FEE = 0.1e18; // 10% - -// uint256 public constant override MIN_BIN_STEP = 1; // 0.01% -// uint256 public constant override MAX_BIN_STEP = 100; // 1%, can't be greater than 247 for indexing reasons - -// uint256 public constant override MAX_PROTOCOL_SHARE = 2_500; // 25% - -// address public override LBPairImplementation; - -// address public override feeRecipient; - -// /// @notice Whether the createLBPair function is unlocked and can be called by anyone (true) or only by owner (false) -// bool public override creationUnlocked; - -// uint256 public override flashLoanFee; - -// ILBPair[] public override allLBPairs; - -// /// @dev Mapping from a (tokenA, tokenB, binStep) to a LBPair. The tokens are ordered to save gas, but they can be -// /// in the reverse order in the actual pair. Always query one of the 2 tokens of the pair to assert the order of the 2 tokens -// mapping(IERC20 => mapping(IERC20 => mapping(uint256 => LBPairInformation))) private _LBPairsInfo; - -// /// @dev Whether a preset was set or not, if the bit at `index` is 1, it means that the binStep `index` was set -// /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas -// bytes32 private _availablePresets; - -// // The parameters presets -// mapping(uint256 => bytes32) private _presets; - -// EnumerableSet.AddressSet private _quoteAssetWhitelist; - -// /// @dev Whether a LBPair was created with a bin step, if the bit at `index` is 1, it means that the LBPair with binStep `index` exists -// /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas -// mapping(IERC20 => mapping(IERC20 => bytes32)) private _availableLBPairBinSteps; - -// /// @notice Constructor -// /// @param _feeRecipient The address of the fee recipient -// /// @param _flashLoanFee The value of the fee for flash loan -// constructor(address _feeRecipient, uint256 _flashLoanFee) { -// if (_flashLoanFee > MAX_FEE) revert LBFactory__FlashLoanFeeAboveMax(_flashLoanFee, MAX_FEE); - -// _setFeeRecipient(_feeRecipient); - -// flashLoanFee = _flashLoanFee; -// emit FlashLoanFeeSet(0, _flashLoanFee); -// } - -// /// @notice View function to return the number of LBPairs created -// /// @return The number of LBPair -// function getNumberOfLBPairs() external view override returns (uint256) { -// return allLBPairs.length; -// } - -// /// @notice View function to return the number of quote assets whitelisted -// /// @return The number of quote assets -// function getNumberOfQuoteAssets() external view override returns (uint256) { -// return _quoteAssetWhitelist.length(); -// } - -// /// @notice View function to return the quote asset whitelisted at index `index` -// /// @param _index The index -// /// @return The address of the _quoteAsset at index `index` -// function getQuoteAsset(uint256 _index) external view override returns (IERC20) { -// return IERC20(_quoteAssetWhitelist.at(_index)); -// } - -// /// @notice View function to return whether a token is a quotedAsset (true) or not (false) -// /// @param _token The address of the asset -// /// @return Whether the token is a quote asset or not -// function isQuoteAsset(IERC20 _token) external view override returns (bool) { -// return _quoteAssetWhitelist.contains(address(_token)); -// } - -// /// @notice Returns the LBPairInformation if it exists, -// /// if not, then the address 0 is returned. The order doesn't matter -// /// @param _tokenA The address of the first token of the pair -// /// @param _tokenB The address of the second token of the pair -// /// @param _binStep The bin step of the LBPair -// /// @return The LBPairInformation -// function getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep) -// external -// view -// override -// returns (LBPairInformation memory) -// { -// return _getLBPairInformation(_tokenA, _tokenB, _binStep); -// } - -// /// @notice View function to return the different parameters of the preset -// /// @param _binStep The bin step of the preset -// /// @return baseFactor The base factor -// /// @return filterPeriod The filter period of the preset -// /// @return decayPeriod The decay period of the preset -// /// @return reductionFactor The reduction factor of the preset -// /// @return variableFeeControl The variable fee control of the preset -// /// @return protocolShare The protocol share of the preset -// /// @return maxVolatilityAccumulated The max volatility accumulated of the preset -// /// @return sampleLifetime The sample lifetime of the preset -// function getPreset(uint16 _binStep) -// external -// view -// override -// returns ( -// uint256 baseFactor, -// uint256 filterPeriod, -// uint256 decayPeriod, -// uint256 reductionFactor, -// uint256 variableFeeControl, -// uint256 protocolShare, -// uint256 maxVolatilityAccumulated, -// uint256 sampleLifetime -// ) -// { -// bytes32 _preset = _presets[_binStep]; -// if (_preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); - -// uint256 _shift; - -// // Safety check -// require(_binStep == _preset.decode(type(uint16).max, _shift)); - -// baseFactor = _preset.decode(type(uint16).max, _shift += 16); -// filterPeriod = _preset.decode(type(uint16).max, _shift += 16); -// decayPeriod = _preset.decode(type(uint16).max, _shift += 16); -// reductionFactor = _preset.decode(type(uint16).max, _shift += 16); -// variableFeeControl = _preset.decode(type(uint24).max, _shift += 16); -// protocolShare = _preset.decode(type(uint16).max, _shift += 24); -// maxVolatilityAccumulated = _preset.decode(type(uint24).max, _shift += 16); - -// sampleLifetime = _preset.decode(type(uint16).max, 240); -// } - -// /// @notice View function to return the list of available binStep with a preset -// /// @return presetsBinStep The list of binStep -// function getAllBinSteps() external view override returns (uint256[] memory presetsBinStep) { -// unchecked { -// bytes32 _avPresets = _availablePresets; -// uint256 _nbPresets = _avPresets.decode(type(uint8).max, 248); - -// if (_nbPresets > 0) { -// presetsBinStep = new uint256[](_nbPresets); - -// uint256 _index; -// for (uint256 i = MIN_BIN_STEP; i <= MAX_BIN_STEP; ++i) { -// if (_avPresets.decode(1, i) == 1) { -// presetsBinStep[_index] = i; -// if (++_index == _nbPresets) break; -// } -// } -// } -// } -// } - -// /// @notice View function to return all the LBPair of a pair of tokens -// /// @param _tokenX The first token of the pair -// /// @param _tokenY The second token of the pair -// /// @return LBPairsAvailable The list of available LBPairs -// function getAllLBPairs(IERC20 _tokenX, IERC20 _tokenY) -// external -// view -// override -// returns (LBPairInformation[] memory LBPairsAvailable) -// { -// unchecked { -// (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); - -// bytes32 _avLBPairBinSteps = _availableLBPairBinSteps[_tokenA][_tokenB]; -// uint256 _nbAvailable = _avLBPairBinSteps.decode(type(uint8).max, 248); - -// if (_nbAvailable > 0) { -// LBPairsAvailable = new LBPairInformation[](_nbAvailable); - -// uint256 _index; -// for (uint256 i = MIN_BIN_STEP; i <= MAX_BIN_STEP; ++i) { -// if (_avLBPairBinSteps.decode(1, i) == 1) { -// LBPairInformation memory _LBPairInformation = _LBPairsInfo[_tokenA][_tokenB][i]; - -// LBPairsAvailable[_index] = LBPairInformation({ -// binStep: i.safe16(), -// LBPair: _LBPairInformation.LBPair, -// createdByOwner: _LBPairInformation.createdByOwner, -// ignoredForRouting: _LBPairInformation.ignoredForRouting -// }); -// if (++_index == _nbAvailable) break; -// } -// } -// } -// } -// } - -// /// @notice Set the LBPair implementation address -// /// @dev Needs to be called by the owner -// /// @param _LBPairImplementation The address of the implementation -// function setLBPairImplementation(address _LBPairImplementation) external override onlyOwner { -// if (ILBPair(_LBPairImplementation).factory() != this) { -// revert LBFactory__LBPairSafetyCheckFailed(_LBPairImplementation); -// } - -// address _oldLBPairImplementation = LBPairImplementation; -// if (_oldLBPairImplementation == _LBPairImplementation) { -// revert LBFactory__SameImplementation(_LBPairImplementation); -// } - -// LBPairImplementation = _LBPairImplementation; - -// emit LBPairImplementationSet(_oldLBPairImplementation, _LBPairImplementation); -// } - -// /// @notice Create a liquidity bin LBPair for _tokenX and _tokenY -// /// @param _tokenX The address of the first token -// /// @param _tokenY The address of the second token -// /// @param _activeId The active id of the pair -// /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) -// /// @return _LBPair The address of the newly created LBPair -// function createLBPair(IERC20 _tokenX, IERC20 _tokenY, uint24 _activeId, uint16 _binStep) -// external -// override -// returns (ILBPair _LBPair) -// { -// address _owner = owner(); -// if (!creationUnlocked && msg.sender != _owner) revert LBFactory__FunctionIsLockedForUsers(msg.sender); - -// address _LBPairImplementation = LBPairImplementation; - -// if (_LBPairImplementation == address(0)) revert LBFactory__ImplementationNotSet(); - -// if (!_quoteAssetWhitelist.contains(address(_tokenY))) revert LBFactory__QuoteAssetNotWhitelisted(_tokenY); - -// if (_tokenX == _tokenY) revert LBFactory__IdenticalAddresses(_tokenX); - -// // safety check, making sure that the price can be calculated -// BinHelper.getPriceFromId(_activeId, _binStep); - -// // We sort token for storage efficiency, only one input needs to be stored because they are sorted -// (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); -// // single check is sufficient -// if (address(_tokenA) == address(0)) revert LBFactory__AddressZero(); -// if (address(_LBPairsInfo[_tokenA][_tokenB][_binStep].LBPair) != address(0)) { -// revert LBFactory__LBPairAlreadyExists(_tokenX, _tokenY, _binStep); -// } - -// bytes32 _preset = _presets[_binStep]; -// if (_preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); - -// uint256 _sampleLifetime = _preset.decode(type(uint16).max, 240); -// // We remove the bits that are not part of the feeParameters -// _preset &= bytes32(uint256(type(uint144).max)); - -// bytes32 _salt = keccak256(abi.encode(_tokenA, _tokenB, _binStep)); -// _LBPair = ILBPair(Clones.cloneDeterministic(_LBPairImplementation, _salt)); - -// _LBPair.initialize(_tokenX, _tokenY, _activeId, uint16(_sampleLifetime), _preset); - -// _LBPairsInfo[_tokenA][_tokenB][_binStep] = LBPairInformation({ -// binStep: _binStep, -// LBPair: _LBPair, -// createdByOwner: msg.sender == _owner, -// ignoredForRouting: false -// }); - -// allLBPairs.push(_LBPair); - -// { -// bytes32 _avLBPairBinSteps = _availableLBPairBinSteps[_tokenA][_tokenB]; -// // We add a 1 at bit `_binStep` as this binStep is now set -// _avLBPairBinSteps = bytes32(uint256(_avLBPairBinSteps) | (1 << _binStep)); - -// // Increase the number of lb pairs by 1 -// _avLBPairBinSteps = bytes32(uint256(_avLBPairBinSteps) + (1 << 248)); - -// // Save the changes -// _availableLBPairBinSteps[_tokenA][_tokenB] = _avLBPairBinSteps; -// } - -// emit LBPairCreated(_tokenX, _tokenY, _binStep, _LBPair, allLBPairs.length - 1); - -// emit FeeParametersSet( -// msg.sender, -// _LBPair, -// _binStep, -// _preset.decode(type(uint16).max, 16), -// _preset.decode(type(uint16).max, 32), -// _preset.decode(type(uint16).max, 48), -// _preset.decode(type(uint16).max, 64), -// _preset.decode(type(uint24).max, 80), -// _preset.decode(type(uint16).max, 104), -// _preset.decode(type(uint24).max, 120) -// ); -// } - -// /// @notice Function to set whether the pair is ignored or not for routing, it will make the pair unusable by the router -// /// @param _tokenX The address of the first token of the pair -// /// @param _tokenY The address of the second token of the pair -// /// @param _binStep The bin step in basis point of the pair -// /// @param _ignored Whether to ignore (true) or not (false) the pair for routing -// function setLBPairIgnored(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep, bool _ignored) -// external -// override -// onlyOwner -// { -// (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); - -// LBPairInformation memory _LBPairInformation = _LBPairsInfo[_tokenA][_tokenB][_binStep]; -// if (address(_LBPairInformation.LBPair) == address(0)) revert LBFactory__AddressZero(); - -// if (_LBPairInformation.ignoredForRouting == _ignored) revert LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); - -// _LBPairsInfo[_tokenA][_tokenB][_binStep].ignoredForRouting = _ignored; - -// emit LBPairIgnoredStateChanged(_LBPairInformation.LBPair, _ignored); -// } - -// /// @notice Sets the preset parameters of a bin step -// /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) -// /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep -// /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam -// /// @param _decayPeriod The period where the accumulator value is halved -// /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator -// /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it -// /// @param _protocolShare The share of the fees received by the protocol -// /// @param _maxVolatilityAccumulated The max value of the volatility accumulated -// /// @param _sampleLifetime The lifetime of an oracle's sample -// function setPreset( -// uint16 _binStep, -// uint16 _baseFactor, -// uint16 _filterPeriod, -// uint16 _decayPeriod, -// uint16 _reductionFactor, -// uint24 _variableFeeControl, -// uint16 _protocolShare, -// uint24 _maxVolatilityAccumulated, -// uint16 _sampleLifetime -// ) external override onlyOwner { -// bytes32 _packedFeeParameters = _getPackedFeeParameters( -// _binStep, -// _baseFactor, -// _filterPeriod, -// _decayPeriod, -// _reductionFactor, -// _variableFeeControl, -// _protocolShare, -// _maxVolatilityAccumulated -// ); - -// // The last 16 bits are reserved for sampleLifetime -// bytes32 _preset = -// bytes32((uint256(_packedFeeParameters) & type(uint144).max) | (uint256(_sampleLifetime) << 240)); - -// _presets[_binStep] = _preset; - -// bytes32 _avPresets = _availablePresets; -// if (_avPresets.decode(1, _binStep) == 0) { -// // We add a 1 at bit `_binStep` as this binStep is now set -// _avPresets = bytes32(uint256(_avPresets) | (1 << _binStep)); - -// // Increase the number of preset by 1 -// _avPresets = bytes32(uint256(_avPresets) + (1 << 248)); - -// // Save the changes -// _availablePresets = _avPresets; -// } - -// emit PresetSet( -// _binStep, -// _baseFactor, -// _filterPeriod, -// _decayPeriod, -// _reductionFactor, -// _variableFeeControl, -// _protocolShare, -// _maxVolatilityAccumulated, -// _sampleLifetime -// ); -// } - -// /// @notice Remove the preset linked to a binStep -// /// @param _binStep The bin step to remove -// function removePreset(uint16 _binStep) external override onlyOwner { -// if (_presets[_binStep] == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); - -// // Set the bit `_binStep` to 0 -// bytes32 _avPresets = _availablePresets; - -// _avPresets &= bytes32(type(uint256).max - (1 << _binStep)); -// _avPresets = bytes32(uint256(_avPresets) - (1 << 248)); - -// // Save the changes -// _availablePresets = _avPresets; -// delete _presets[_binStep]; - -// emit PresetRemoved(_binStep); -// } - -// /// @notice Function to set the fee parameter of a LBPair -// /// @param _tokenX The address of the first token -// /// @param _tokenY The address of the second token -// /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) -// /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep -// /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam -// /// @param _decayPeriod The period where the accumulator value is halved -// /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator -// /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it -// /// @param _protocolShare The share of the fees received by the protocol -// /// @param _maxVolatilityAccumulated The max value of volatility accumulated -// function setFeesParametersOnPair( -// IERC20 _tokenX, -// IERC20 _tokenY, -// uint16 _binStep, -// uint16 _baseFactor, -// uint16 _filterPeriod, -// uint16 _decayPeriod, -// uint16 _reductionFactor, -// uint24 _variableFeeControl, -// uint16 _protocolShare, -// uint24 _maxVolatilityAccumulated -// ) external override onlyOwner { -// ILBPair _LBPair = _getLBPairInformation(_tokenX, _tokenY, _binStep).LBPair; - -// if (address(_LBPair) == address(0)) revert LBFactory__LBPairNotCreated(_tokenX, _tokenY, _binStep); - -// bytes32 _packedFeeParameters = _getPackedFeeParameters( -// _binStep, -// _baseFactor, -// _filterPeriod, -// _decayPeriod, -// _reductionFactor, -// _variableFeeControl, -// _protocolShare, -// _maxVolatilityAccumulated -// ); - -// _LBPair.setFeesParameters(_packedFeeParameters); - -// emit FeeParametersSet( -// msg.sender, -// _LBPair, -// _binStep, -// _baseFactor, -// _filterPeriod, -// _decayPeriod, -// _reductionFactor, -// _variableFeeControl, -// _protocolShare, -// _maxVolatilityAccumulated -// ); -// } - -// /// @notice Function to set the recipient of the fees. This address needs to be able to receive ERC20s -// /// @param _feeRecipient The address of the recipient -// function setFeeRecipient(address _feeRecipient) external override onlyOwner { -// _setFeeRecipient(_feeRecipient); -// } - -// /// @notice Function to set the flash loan fee -// /// @param _flashLoanFee The value of the fee for flash loan -// function setFlashLoanFee(uint256 _flashLoanFee) external override onlyOwner { -// uint256 _oldFlashLoanFee = flashLoanFee; - -// if (_oldFlashLoanFee == _flashLoanFee) revert LBFactory__SameFlashLoanFee(_flashLoanFee); -// if (_flashLoanFee > MAX_FEE) revert LBFactory__FlashLoanFeeAboveMax(_flashLoanFee, MAX_FEE); - -// flashLoanFee = _flashLoanFee; -// emit FlashLoanFeeSet(_oldFlashLoanFee, _flashLoanFee); -// } - -// /// @notice Function to set the creation restriction of the Factory -// /// @param _locked If the creation is restricted (true) or not (false) -// function setFactoryLockedState(bool _locked) external override onlyOwner { -// if (creationUnlocked != _locked) revert LBFactory__FactoryLockIsAlreadyInTheSameState(); -// creationUnlocked = !_locked; -// emit FactoryLockedStatusUpdated(_locked); -// } - -// /// @notice Function to add an asset to the whitelist of quote assets -// /// @param _quoteAsset The quote asset (e.g: AVAX, USDC...) -// function addQuoteAsset(IERC20 _quoteAsset) external override onlyOwner { -// if (!_quoteAssetWhitelist.add(address(_quoteAsset))) { -// revert LBFactory__QuoteAssetAlreadyWhitelisted(_quoteAsset); -// } - -// emit QuoteAssetAdded(_quoteAsset); -// } - -// /// @notice Function to remove an asset from the whitelist of quote assets -// /// @param _quoteAsset The quote asset (e.g: AVAX, USDC...) -// function removeQuoteAsset(IERC20 _quoteAsset) external override onlyOwner { -// if (!_quoteAssetWhitelist.remove(address(_quoteAsset))) revert LBFactory__QuoteAssetNotWhitelisted(_quoteAsset); - -// emit QuoteAssetRemoved(_quoteAsset); -// } - -// /// @notice Internal function to set the recipient of the fee -// /// @param _feeRecipient The address of the recipient -// function _setFeeRecipient(address _feeRecipient) internal { -// if (_feeRecipient == address(0)) revert LBFactory__AddressZero(); - -// address _oldFeeRecipient = feeRecipient; -// if (_oldFeeRecipient == _feeRecipient) revert LBFactory__SameFeeRecipient(_feeRecipient); - -// feeRecipient = _feeRecipient; -// emit FeeRecipientSet(_oldFeeRecipient, _feeRecipient); -// } - -// function forceDecay(ILBPair _LBPair) external override onlyOwner { -// _LBPair.forceDecay(); -// } - -// /// @notice Internal function to set the fee parameter of a LBPair -// /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) -// /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep -// /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam -// /// @param _decayPeriod The period where the accumulator value is halved -// /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator -// /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it -// /// @param _protocolShare The share of the fees received by the protocol -// /// @param _maxVolatilityAccumulated The max value of volatility accumulated -// function _getPackedFeeParameters( -// uint16 _binStep, -// uint16 _baseFactor, -// uint16 _filterPeriod, -// uint16 _decayPeriod, -// uint16 _reductionFactor, -// uint24 _variableFeeControl, -// uint16 _protocolShare, -// uint24 _maxVolatilityAccumulated -// ) private pure returns (bytes32) { -// if (_binStep < MIN_BIN_STEP || _binStep > MAX_BIN_STEP) { -// revert LBFactory__BinStepRequirementsBreached(MIN_BIN_STEP, _binStep, MAX_BIN_STEP); -// } - -// if (_filterPeriod >= _decayPeriod) revert LBFactory__DecreasingPeriods(_filterPeriod, _decayPeriod); - -// if (_reductionFactor > Constants.BASIS_POINT_MAX) { -// revert LBFactory__ReductionFactorOverflows(_reductionFactor, Constants.BASIS_POINT_MAX); -// } - -// if (_protocolShare > MAX_PROTOCOL_SHARE) { -// revert LBFactory__ProtocolShareOverflows(_protocolShare, MAX_PROTOCOL_SHARE); -// } - -// { -// uint256 _baseFee = (uint256(_baseFactor) * _binStep) * 1e10; - -// // Can't overflow as the max value is `max(uint24) * (max(uint24) * max(uint16)) ** 2 < max(uint104)` -// // It returns 18 decimals as: -// // decimals(variableFeeControl * (volatilityAccumulated * binStep)**2 / 100) = 4 + (4 + 4) * 2 - 2 = 18 -// uint256 _prod = uint256(_maxVolatilityAccumulated) * _binStep; -// uint256 _maxVariableFee = (_prod * _prod * _variableFeeControl) / 100; - -// if (_baseFee + _maxVariableFee > MAX_FEE) { -// revert LBFactory__FeesAboveMax(_baseFee + _maxVariableFee, MAX_FEE); -// } -// } - -// /// @dev It's very important that the sum of the sizes of those values is exactly 256 bits -// /// here, (112 + 24) + 16 + 24 + 16 + 16 + 16 + 16 + 16 = 256 -// return bytes32( -// abi.encodePacked( -// uint136(_maxVolatilityAccumulated), // The first 112 bits are reserved for the dynamic parameters -// _protocolShare, -// _variableFeeControl, -// _reductionFactor, -// _decayPeriod, -// _filterPeriod, -// _baseFactor, -// _binStep -// ) -// ); -// } - -// /// @notice Returns the LBPairInformation if it exists, -// /// if not, then the address 0 is returned. The order doesn't matter -// /// @param _tokenA The address of the first token of the pair -// /// @param _tokenB The address of the second token of the pair -// /// @param _binStep The bin step of the LBPair -// /// @return The LBPairInformation -// function _getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep) -// private -// view -// returns (LBPairInformation memory) -// { -// (_tokenA, _tokenB) = _sortTokens(_tokenA, _tokenB); -// return _LBPairsInfo[_tokenA][_tokenB][_binStep]; -// } - -// /// @notice Private view function to sort 2 tokens in ascending order -// /// @param _tokenA The first token -// /// @param _tokenB The second token -// /// @return The sorted first token -// /// @return The sorted second token -// function _sortTokens(IERC20 _tokenA, IERC20 _tokenB) private pure returns (IERC20, IERC20) { -// if (_tokenA > _tokenB) (_tokenA, _tokenB) = (_tokenB, _tokenA); -// return (_tokenA, _tokenB); -// } -// } +import "openzeppelin/proxy/Clones.sol"; +import "openzeppelin/utils/structs/EnumerableSet.sol"; + +import "./LBErrors.sol"; +import "./libraries/BinHelper.sol"; +import "./libraries/Constants.sol"; +import "./libraries/Decoder.sol"; +import "./libraries/PendingOwnable.sol"; +import "./libraries/math/SafeCast.sol"; +import "./interfaces/ILBFactory.sol"; + +/// @title Liquidity Book Factory +/// @author Trader Joe +/// @notice Contract used to deploy and register new LBPairs. +/// Enables setting fee parameters, flashloan fees and LBPair implementation. +/// Unless the `creationUnlocked` is `true`, only the owner of the factory can create pairs. +contract LBFactory is PendingOwnable, ILBFactory { + using SafeCast for uint256; + using Decoder for bytes32; + using EnumerableSet for EnumerableSet.AddressSet; + + uint256 public constant override MAX_FEE = 0.1e18; // 10% + + uint256 public constant override MIN_BIN_STEP = 1; // 0.01% + uint256 public constant override MAX_BIN_STEP = 100; // 1%, can't be greater than 247 for indexing reasons + + uint256 public constant override MAX_PROTOCOL_SHARE = 2_500; // 25% + + address public override LBPairImplementation; + + address public override feeRecipient; + + /// @notice Whether the createLBPair function is unlocked and can be called by anyone (true) or only by owner (false) + bool public override creationUnlocked; + + uint256 public override flashLoanFee; + + ILBPair[] public override allLBPairs; + + /// @dev Mapping from a (tokenA, tokenB, binStep) to a LBPair. The tokens are ordered to save gas, but they can be + /// in the reverse order in the actual pair. Always query one of the 2 tokens of the pair to assert the order of the 2 tokens + mapping(IERC20 => mapping(IERC20 => mapping(uint256 => LBPairInformation))) private _LBPairsInfo; + + /// @dev Whether a preset was set or not, if the bit at `index` is 1, it means that the binStep `index` was set + /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas + bytes32 private _availablePresets; + + // The parameters presets + mapping(uint256 => bytes32) private _presets; + + EnumerableSet.AddressSet private _quoteAssetWhitelist; + + /// @dev Whether a LBPair was created with a bin step, if the bit at `index` is 1, it means that the LBPair with binStep `index` exists + /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas + mapping(IERC20 => mapping(IERC20 => bytes32)) private _availableLBPairBinSteps; + + /// @notice Constructor + /// @param _feeRecipient The address of the fee recipient + /// @param _flashLoanFee The value of the fee for flash loan + constructor(address _feeRecipient, uint256 _flashLoanFee) { + if (_flashLoanFee > MAX_FEE) revert LBFactory__FlashLoanFeeAboveMax(_flashLoanFee, MAX_FEE); + + _setFeeRecipient(_feeRecipient); + + flashLoanFee = _flashLoanFee; + emit FlashLoanFeeSet(0, _flashLoanFee); + } + + /// @notice View function to return the number of LBPairs created + /// @return The number of LBPair + function getNumberOfLBPairs() external view override returns (uint256) { + return allLBPairs.length; + } + + /// @notice View function to return the number of quote assets whitelisted + /// @return The number of quote assets + function getNumberOfQuoteAssets() external view override returns (uint256) { + return _quoteAssetWhitelist.length(); + } + + /// @notice View function to return the quote asset whitelisted at index `index` + /// @param _index The index + /// @return The address of the _quoteAsset at index `index` + function getQuoteAsset(uint256 _index) external view override returns (IERC20) { + return IERC20(_quoteAssetWhitelist.at(_index)); + } + + /// @notice View function to return whether a token is a quotedAsset (true) or not (false) + /// @param _token The address of the asset + /// @return Whether the token is a quote asset or not + function isQuoteAsset(IERC20 _token) external view override returns (bool) { + return _quoteAssetWhitelist.contains(address(_token)); + } + + /// @notice Returns the LBPairInformation if it exists, + /// if not, then the address 0 is returned. The order doesn't matter + /// @param _tokenA The address of the first token of the pair + /// @param _tokenB The address of the second token of the pair + /// @param _binStep The bin step of the LBPair + /// @return The LBPairInformation + function getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep) + external + view + override + returns (LBPairInformation memory) + { + return _getLBPairInformation(_tokenA, _tokenB, _binStep); + } + + /// @notice View function to return the different parameters of the preset + /// @param _binStep The bin step of the preset + /// @return baseFactor The base factor + /// @return filterPeriod The filter period of the preset + /// @return decayPeriod The decay period of the preset + /// @return reductionFactor The reduction factor of the preset + /// @return variableFeeControl The variable fee control of the preset + /// @return protocolShare The protocol share of the preset + /// @return maxVolatilityAccumulated The max volatility accumulated of the preset + /// @return sampleLifetime The sample lifetime of the preset + function getPreset(uint16 _binStep) + external + view + override + returns ( + uint256 baseFactor, + uint256 filterPeriod, + uint256 decayPeriod, + uint256 reductionFactor, + uint256 variableFeeControl, + uint256 protocolShare, + uint256 maxVolatilityAccumulated, + uint256 sampleLifetime + ) + { + bytes32 _preset = _presets[_binStep]; + if (_preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); + + uint256 _shift; + + // Safety check + require(_binStep == _preset.decode(type(uint16).max, _shift)); + + baseFactor = _preset.decode(type(uint16).max, _shift += 16); + filterPeriod = _preset.decode(type(uint16).max, _shift += 16); + decayPeriod = _preset.decode(type(uint16).max, _shift += 16); + reductionFactor = _preset.decode(type(uint16).max, _shift += 16); + variableFeeControl = _preset.decode(type(uint24).max, _shift += 16); + protocolShare = _preset.decode(type(uint16).max, _shift += 24); + maxVolatilityAccumulated = _preset.decode(type(uint24).max, _shift += 16); + + sampleLifetime = _preset.decode(type(uint16).max, 240); + } + + /// @notice View function to return the list of available binStep with a preset + /// @return presetsBinStep The list of binStep + function getAllBinSteps() external view override returns (uint256[] memory presetsBinStep) { + unchecked { + bytes32 _avPresets = _availablePresets; + uint256 _nbPresets = _avPresets.decode(type(uint8).max, 248); + + if (_nbPresets > 0) { + presetsBinStep = new uint256[](_nbPresets); + + uint256 _index; + for (uint256 i = MIN_BIN_STEP; i <= MAX_BIN_STEP; ++i) { + if (_avPresets.decode(1, i) == 1) { + presetsBinStep[_index] = i; + if (++_index == _nbPresets) break; + } + } + } + } + } + + /// @notice View function to return all the LBPair of a pair of tokens + /// @param _tokenX The first token of the pair + /// @param _tokenY The second token of the pair + /// @return LBPairsAvailable The list of available LBPairs + function getAllLBPairs(IERC20 _tokenX, IERC20 _tokenY) + external + view + override + returns (LBPairInformation[] memory LBPairsAvailable) + { + unchecked { + (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); + + bytes32 _avLBPairBinSteps = _availableLBPairBinSteps[_tokenA][_tokenB]; + uint256 _nbAvailable = _avLBPairBinSteps.decode(type(uint8).max, 248); + + if (_nbAvailable > 0) { + LBPairsAvailable = new LBPairInformation[](_nbAvailable); + + uint256 _index; + for (uint256 i = MIN_BIN_STEP; i <= MAX_BIN_STEP; ++i) { + if (_avLBPairBinSteps.decode(1, i) == 1) { + LBPairInformation memory _LBPairInformation = _LBPairsInfo[_tokenA][_tokenB][i]; + + LBPairsAvailable[_index] = LBPairInformation({ + binStep: i.safe16(), + LBPair: _LBPairInformation.LBPair, + createdByOwner: _LBPairInformation.createdByOwner, + ignoredForRouting: _LBPairInformation.ignoredForRouting + }); + if (++_index == _nbAvailable) break; + } + } + } + } + } + + /// @notice Set the LBPair implementation address + /// @dev Needs to be called by the owner + /// @param _LBPairImplementation The address of the implementation + function setLBPairImplementation(address _LBPairImplementation) external override onlyOwner { + if (ILBPair(_LBPairImplementation).factory() != this) { + revert LBFactory__LBPairSafetyCheckFailed(_LBPairImplementation); + } + + address _oldLBPairImplementation = LBPairImplementation; + if (_oldLBPairImplementation == _LBPairImplementation) { + revert LBFactory__SameImplementation(_LBPairImplementation); + } + + LBPairImplementation = _LBPairImplementation; + + emit LBPairImplementationSet(_oldLBPairImplementation, _LBPairImplementation); + } + + /// @notice Create a liquidity bin LBPair for _tokenX and _tokenY + /// @param _tokenX The address of the first token + /// @param _tokenY The address of the second token + /// @param _activeId The active id of the pair + /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) + /// @return _LBPair The address of the newly created LBPair + function createLBPair(IERC20 _tokenX, IERC20 _tokenY, uint24 _activeId, uint16 _binStep) + external + override + returns (ILBPair _LBPair) + { + address _owner = owner(); + if (!creationUnlocked && msg.sender != _owner) revert LBFactory__FunctionIsLockedForUsers(msg.sender); + + address _LBPairImplementation = LBPairImplementation; + + if (_LBPairImplementation == address(0)) revert LBFactory__ImplementationNotSet(); + + if (!_quoteAssetWhitelist.contains(address(_tokenY))) revert LBFactory__QuoteAssetNotWhitelisted(_tokenY); + + if (_tokenX == _tokenY) revert LBFactory__IdenticalAddresses(_tokenX); + + // safety check, making sure that the price can be calculated + BinHelper.getPriceFromId(_activeId, _binStep); + + // We sort token for storage efficiency, only one input needs to be stored because they are sorted + (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); + // single check is sufficient + if (address(_tokenA) == address(0)) revert LBFactory__AddressZero(); + if (address(_LBPairsInfo[_tokenA][_tokenB][_binStep].LBPair) != address(0)) { + revert LBFactory__LBPairAlreadyExists(_tokenX, _tokenY, _binStep); + } + + bytes32 _preset = _presets[_binStep]; + if (_preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); + + uint256 _sampleLifetime = _preset.decode(type(uint16).max, 240); + // We remove the bits that are not part of the feeParameters + _preset &= bytes32(uint256(type(uint144).max)); + + bytes32 _salt = keccak256(abi.encode(_tokenA, _tokenB, _binStep)); + _LBPair = ILBPair(Clones.cloneDeterministic(_LBPairImplementation, _salt)); + + _LBPair.initialize(_tokenX, _tokenY, _activeId, uint16(_sampleLifetime), _preset); + + _LBPairsInfo[_tokenA][_tokenB][_binStep] = LBPairInformation({ + binStep: _binStep, + LBPair: _LBPair, + createdByOwner: msg.sender == _owner, + ignoredForRouting: false + }); + + allLBPairs.push(_LBPair); + + { + bytes32 _avLBPairBinSteps = _availableLBPairBinSteps[_tokenA][_tokenB]; + // We add a 1 at bit `_binStep` as this binStep is now set + _avLBPairBinSteps = bytes32(uint256(_avLBPairBinSteps) | (1 << _binStep)); + + // Increase the number of lb pairs by 1 + _avLBPairBinSteps = bytes32(uint256(_avLBPairBinSteps) + (1 << 248)); + + // Save the changes + _availableLBPairBinSteps[_tokenA][_tokenB] = _avLBPairBinSteps; + } + + emit LBPairCreated(_tokenX, _tokenY, _binStep, _LBPair, allLBPairs.length - 1); + + emit FeeParametersSet( + msg.sender, + _LBPair, + _binStep, + _preset.decode(type(uint16).max, 16), + _preset.decode(type(uint16).max, 32), + _preset.decode(type(uint16).max, 48), + _preset.decode(type(uint16).max, 64), + _preset.decode(type(uint24).max, 80), + _preset.decode(type(uint16).max, 104), + _preset.decode(type(uint24).max, 120) + ); + } + + /// @notice Function to set whether the pair is ignored or not for routing, it will make the pair unusable by the router + /// @param _tokenX The address of the first token of the pair + /// @param _tokenY The address of the second token of the pair + /// @param _binStep The bin step in basis point of the pair + /// @param _ignored Whether to ignore (true) or not (false) the pair for routing + function setLBPairIgnored(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep, bool _ignored) + external + override + onlyOwner + { + (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); + + LBPairInformation memory _LBPairInformation = _LBPairsInfo[_tokenA][_tokenB][_binStep]; + if (address(_LBPairInformation.LBPair) == address(0)) revert LBFactory__AddressZero(); + + if (_LBPairInformation.ignoredForRouting == _ignored) revert LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); + + _LBPairsInfo[_tokenA][_tokenB][_binStep].ignoredForRouting = _ignored; + + emit LBPairIgnoredStateChanged(_LBPairInformation.LBPair, _ignored); + } + + /// @notice Sets the preset parameters of a bin step + /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) + /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep + /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam + /// @param _decayPeriod The period where the accumulator value is halved + /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator + /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it + /// @param _protocolShare The share of the fees received by the protocol + /// @param _maxVolatilityAccumulated The max value of the volatility accumulated + /// @param _sampleLifetime The lifetime of an oracle's sample + function setPreset( + uint16 _binStep, + uint16 _baseFactor, + uint16 _filterPeriod, + uint16 _decayPeriod, + uint16 _reductionFactor, + uint24 _variableFeeControl, + uint16 _protocolShare, + uint24 _maxVolatilityAccumulated, + uint16 _sampleLifetime + ) external override onlyOwner { + bytes32 _packedFeeParameters = _getPackedFeeParameters( + _binStep, + _baseFactor, + _filterPeriod, + _decayPeriod, + _reductionFactor, + _variableFeeControl, + _protocolShare, + _maxVolatilityAccumulated + ); + + // The last 16 bits are reserved for sampleLifetime + bytes32 _preset = + bytes32((uint256(_packedFeeParameters) & type(uint144).max) | (uint256(_sampleLifetime) << 240)); + + _presets[_binStep] = _preset; + + bytes32 _avPresets = _availablePresets; + if (_avPresets.decode(1, _binStep) == 0) { + // We add a 1 at bit `_binStep` as this binStep is now set + _avPresets = bytes32(uint256(_avPresets) | (1 << _binStep)); + + // Increase the number of preset by 1 + _avPresets = bytes32(uint256(_avPresets) + (1 << 248)); + + // Save the changes + _availablePresets = _avPresets; + } + + emit PresetSet( + _binStep, + _baseFactor, + _filterPeriod, + _decayPeriod, + _reductionFactor, + _variableFeeControl, + _protocolShare, + _maxVolatilityAccumulated, + _sampleLifetime + ); + } + + /// @notice Remove the preset linked to a binStep + /// @param _binStep The bin step to remove + function removePreset(uint16 _binStep) external override onlyOwner { + if (_presets[_binStep] == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); + + // Set the bit `_binStep` to 0 + bytes32 _avPresets = _availablePresets; + + _avPresets &= bytes32(type(uint256).max - (1 << _binStep)); + _avPresets = bytes32(uint256(_avPresets) - (1 << 248)); + + // Save the changes + _availablePresets = _avPresets; + delete _presets[_binStep]; + + emit PresetRemoved(_binStep); + } + + /// @notice Function to set the fee parameter of a LBPair + /// @param _tokenX The address of the first token + /// @param _tokenY The address of the second token + /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) + /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep + /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam + /// @param _decayPeriod The period where the accumulator value is halved + /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator + /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it + /// @param _protocolShare The share of the fees received by the protocol + /// @param _maxVolatilityAccumulated The max value of volatility accumulated + function setFeesParametersOnPair( + IERC20 _tokenX, + IERC20 _tokenY, + uint16 _binStep, + uint16 _baseFactor, + uint16 _filterPeriod, + uint16 _decayPeriod, + uint16 _reductionFactor, + uint24 _variableFeeControl, + uint16 _protocolShare, + uint24 _maxVolatilityAccumulated + ) external override onlyOwner { + ILBPair _LBPair = _getLBPairInformation(_tokenX, _tokenY, _binStep).LBPair; + + if (address(_LBPair) == address(0)) revert LBFactory__LBPairNotCreated(_tokenX, _tokenY, _binStep); + + bytes32 _packedFeeParameters = _getPackedFeeParameters( + _binStep, + _baseFactor, + _filterPeriod, + _decayPeriod, + _reductionFactor, + _variableFeeControl, + _protocolShare, + _maxVolatilityAccumulated + ); + + _LBPair.setFeesParameters(_packedFeeParameters); + + emit FeeParametersSet( + msg.sender, + _LBPair, + _binStep, + _baseFactor, + _filterPeriod, + _decayPeriod, + _reductionFactor, + _variableFeeControl, + _protocolShare, + _maxVolatilityAccumulated + ); + } + + /// @notice Function to set the recipient of the fees. This address needs to be able to receive ERC20s + /// @param _feeRecipient The address of the recipient + function setFeeRecipient(address _feeRecipient) external override onlyOwner { + _setFeeRecipient(_feeRecipient); + } + + /// @notice Function to set the flash loan fee + /// @param _flashLoanFee The value of the fee for flash loan + function setFlashLoanFee(uint256 _flashLoanFee) external override onlyOwner { + uint256 _oldFlashLoanFee = flashLoanFee; + + if (_oldFlashLoanFee == _flashLoanFee) revert LBFactory__SameFlashLoanFee(_flashLoanFee); + if (_flashLoanFee > MAX_FEE) revert LBFactory__FlashLoanFeeAboveMax(_flashLoanFee, MAX_FEE); + + flashLoanFee = _flashLoanFee; + emit FlashLoanFeeSet(_oldFlashLoanFee, _flashLoanFee); + } + + /// @notice Function to set the creation restriction of the Factory + /// @param _locked If the creation is restricted (true) or not (false) + function setFactoryLockedState(bool _locked) external override onlyOwner { + if (creationUnlocked != _locked) revert LBFactory__FactoryLockIsAlreadyInTheSameState(); + creationUnlocked = !_locked; + emit FactoryLockedStatusUpdated(_locked); + } + + /// @notice Function to add an asset to the whitelist of quote assets + /// @param _quoteAsset The quote asset (e.g: AVAX, USDC...) + function addQuoteAsset(IERC20 _quoteAsset) external override onlyOwner { + if (!_quoteAssetWhitelist.add(address(_quoteAsset))) { + revert LBFactory__QuoteAssetAlreadyWhitelisted(_quoteAsset); + } + + emit QuoteAssetAdded(_quoteAsset); + } + + /// @notice Function to remove an asset from the whitelist of quote assets + /// @param _quoteAsset The quote asset (e.g: AVAX, USDC...) + function removeQuoteAsset(IERC20 _quoteAsset) external override onlyOwner { + if (!_quoteAssetWhitelist.remove(address(_quoteAsset))) revert LBFactory__QuoteAssetNotWhitelisted(_quoteAsset); + + emit QuoteAssetRemoved(_quoteAsset); + } + + /// @notice Internal function to set the recipient of the fee + /// @param _feeRecipient The address of the recipient + function _setFeeRecipient(address _feeRecipient) internal { + if (_feeRecipient == address(0)) revert LBFactory__AddressZero(); + + address _oldFeeRecipient = feeRecipient; + if (_oldFeeRecipient == _feeRecipient) revert LBFactory__SameFeeRecipient(_feeRecipient); + + feeRecipient = _feeRecipient; + emit FeeRecipientSet(_oldFeeRecipient, _feeRecipient); + } + + function forceDecay(ILBPair _LBPair) external override onlyOwner { + _LBPair.forceDecay(); + } + + /// @notice Internal function to set the fee parameter of a LBPair + /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) + /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep + /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam + /// @param _decayPeriod The period where the accumulator value is halved + /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator + /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it + /// @param _protocolShare The share of the fees received by the protocol + /// @param _maxVolatilityAccumulated The max value of volatility accumulated + function _getPackedFeeParameters( + uint16 _binStep, + uint16 _baseFactor, + uint16 _filterPeriod, + uint16 _decayPeriod, + uint16 _reductionFactor, + uint24 _variableFeeControl, + uint16 _protocolShare, + uint24 _maxVolatilityAccumulated + ) private pure returns (bytes32) { + if (_binStep < MIN_BIN_STEP || _binStep > MAX_BIN_STEP) { + revert LBFactory__BinStepRequirementsBreached(MIN_BIN_STEP, _binStep, MAX_BIN_STEP); + } + + if (_filterPeriod >= _decayPeriod) revert LBFactory__DecreasingPeriods(_filterPeriod, _decayPeriod); + + if (_reductionFactor > Constants.BASIS_POINT_MAX) { + revert LBFactory__ReductionFactorOverflows(_reductionFactor, Constants.BASIS_POINT_MAX); + } + + if (_protocolShare > MAX_PROTOCOL_SHARE) { + revert LBFactory__ProtocolShareOverflows(_protocolShare, MAX_PROTOCOL_SHARE); + } + + { + uint256 _baseFee = (uint256(_baseFactor) * _binStep) * 1e10; + + // Can't overflow as the max value is `max(uint24) * (max(uint24) * max(uint16)) ** 2 < max(uint104)` + // It returns 18 decimals as: + // decimals(variableFeeControl * (volatilityAccumulated * binStep)**2 / 100) = 4 + (4 + 4) * 2 - 2 = 18 + uint256 _prod = uint256(_maxVolatilityAccumulated) * _binStep; + uint256 _maxVariableFee = (_prod * _prod * _variableFeeControl) / 100; + + if (_baseFee + _maxVariableFee > MAX_FEE) { + revert LBFactory__FeesAboveMax(_baseFee + _maxVariableFee, MAX_FEE); + } + } + + /// @dev It's very important that the sum of the sizes of those values is exactly 256 bits + /// here, (112 + 24) + 16 + 24 + 16 + 16 + 16 + 16 + 16 = 256 + return bytes32( + abi.encodePacked( + uint136(_maxVolatilityAccumulated), // The first 112 bits are reserved for the dynamic parameters + _protocolShare, + _variableFeeControl, + _reductionFactor, + _decayPeriod, + _filterPeriod, + _baseFactor, + _binStep + ) + ); + } + + /// @notice Returns the LBPairInformation if it exists, + /// if not, then the address 0 is returned. The order doesn't matter + /// @param _tokenA The address of the first token of the pair + /// @param _tokenB The address of the second token of the pair + /// @param _binStep The bin step of the LBPair + /// @return The LBPairInformation + function _getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep) + private + view + returns (LBPairInformation memory) + { + (_tokenA, _tokenB) = _sortTokens(_tokenA, _tokenB); + return _LBPairsInfo[_tokenA][_tokenB][_binStep]; + } + + /// @notice Private view function to sort 2 tokens in ascending order + /// @param _tokenA The first token + /// @param _tokenB The second token + /// @return The sorted first token + /// @return The sorted second token + function _sortTokens(IERC20 _tokenA, IERC20 _tokenB) private pure returns (IERC20, IERC20) { + if (_tokenA > _tokenB) (_tokenA, _tokenB) = (_tokenB, _tokenA); + return (_tokenA, _tokenB); + } +} diff --git a/src/LBQuoter.sol b/src/LBQuoter.sol index 4962fb72..387b1a0a 100644 --- a/src/LBQuoter.sol +++ b/src/LBQuoter.sol @@ -2,220 +2,220 @@ pragma solidity 0.8.10; -// import "./LBErrors.sol"; -// import "./libraries/BinHelper.sol"; -// import "./libraries/JoeLibrary.sol"; -// import "./libraries/Math512Bits.sol"; -// import "./interfaces/IJoeFactory.sol"; -// import "./interfaces/IJoePair.sol"; -// import "./interfaces/ILBFactory.sol"; -// import "./interfaces/ILBRouter.sol"; - -// /// @title Liquidity Book Quoter -// /// @author Trader Joe -// /// @notice Helper contract to determine best path through multiple markets -// contract LBQuoter { -// using Math512Bits for uint256; - -// /// @notice Dex V2 router address -// address public immutable routerV2; -// /// @notice Dex V1 factory address -// address public immutable factoryV1; -// /// @notice Dex V2 factory address -// address public immutable factoryV2; - -// struct Quote { -// address[] route; -// address[] pairs; -// uint256[] binSteps; -// uint256[] amounts; -// uint256[] virtualAmountsWithoutSlippage; -// uint256[] fees; -// } - -// /// @notice Constructor -// /// @param _routerV2 Dex V2 router address -// /// @param _factoryV1 Dex V1 factory address -// /// @param _factoryV2 Dex V2 factory address -// constructor(address _routerV2, address _factoryV1, address _factoryV2) { -// routerV2 = _routerV2; -// factoryV1 = _factoryV1; -// factoryV2 = _factoryV2; -// } - -// /// @notice Finds the best path given a list of tokens and the input amount wanted from the swap -// /// @param _route List of the tokens to go through -// /// @param _amountIn Swap amount in -// /// @return quote The Quote structure containing the necessary element to perform the swap -// function findBestPathFromAmountIn(address[] calldata _route, uint256 _amountIn) -// public -// view -// returns (Quote memory quote) -// { -// if (_route.length < 2) { -// revert LBQuoter_InvalidLength(); -// } - -// quote.route = _route; - -// uint256 swapLength = _route.length - 1; -// quote.pairs = new address[](swapLength); -// quote.binSteps = new uint256[](swapLength); -// quote.fees = new uint256[](swapLength); -// quote.amounts = new uint256[](_route.length); -// quote.virtualAmountsWithoutSlippage = new uint256[](_route.length); - -// quote.amounts[0] = _amountIn; -// quote.virtualAmountsWithoutSlippage[0] = _amountIn; - -// for (uint256 i; i < swapLength; i++) { -// // Fetch swap for V1 -// quote.pairs[i] = IJoeFactory(factoryV1).getPair(_route[i], _route[i + 1]); - -// if (quote.pairs[i] != address(0) && quote.amounts[i] > 0) { -// (uint256 reserveIn, uint256 reserveOut) = _getReserves(quote.pairs[i], _route[i], _route[i + 1]); - -// if (reserveIn > 0 && reserveOut > 0) { -// quote.amounts[i + 1] = JoeLibrary.getAmountOut(quote.amounts[i], reserveIn, reserveOut); -// quote.virtualAmountsWithoutSlippage[i + 1] = -// JoeLibrary.quote(quote.virtualAmountsWithoutSlippage[i] * 997, reserveIn * 1000, reserveOut); -// quote.fees[i] = 0.003e18; // 0.3% -// } -// } - -// // Fetch swaps for V2 -// ILBFactory.LBPairInformation[] memory LBPairsAvailable = -// ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i]), IERC20(_route[i + 1])); - -// if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { -// for (uint256 j; j < LBPairsAvailable.length; j++) { -// if (!LBPairsAvailable[j].ignoredForRouting) { -// bool swapForY = address(LBPairsAvailable[j].LBPair.tokenY()) == _route[i + 1]; - -// try ILBRouter(routerV2).getSwapOut(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) -// returns (uint256 swapAmountOut, uint256 fees) { -// if (swapAmountOut > quote.amounts[i + 1]) { -// quote.amounts[i + 1] = swapAmountOut; -// quote.pairs[i] = address(LBPairsAvailable[j].LBPair); -// quote.binSteps[i] = LBPairsAvailable[j].binStep; - -// // Getting current price -// (,, uint256 activeId) = LBPairsAvailable[j].LBPair.getReservesAndId(); -// quote.virtualAmountsWithoutSlippage[i + 1] = _getV2Quote( -// quote.virtualAmountsWithoutSlippage[i] - fees, activeId, quote.binSteps[i], swapForY -// ); - -// quote.fees[i] = (fees * 1e18) / quote.amounts[i]; // fee percentage in amountIn -// } -// } catch {} -// } -// } -// } -// } -// } - -// /// @notice Finds the best path given a list of tokens and the output amount wanted from the swap -// /// @param _route List of the tokens to go through -// /// @param _amountOut Swap amount out -// /// @return quote The Quote structure containing the necessary element to perform the swap -// function findBestPathFromAmountOut(address[] calldata _route, uint256 _amountOut) -// public -// view -// returns (Quote memory quote) -// { -// if (_route.length < 2) { -// revert LBQuoter_InvalidLength(); -// } -// quote.route = _route; - -// uint256 swapLength = _route.length - 1; -// quote.pairs = new address[](swapLength); -// quote.binSteps = new uint256[](swapLength); -// quote.fees = new uint256[](swapLength); -// quote.amounts = new uint256[](_route.length); -// quote.virtualAmountsWithoutSlippage = new uint256[](_route.length); - -// quote.amounts[swapLength] = _amountOut; -// quote.virtualAmountsWithoutSlippage[swapLength] = _amountOut; - -// for (uint256 i = swapLength; i > 0; i--) { -// // Fetch swap for V1 -// quote.pairs[i - 1] = IJoeFactory(factoryV1).getPair(_route[i - 1], _route[i]); -// if (quote.pairs[i - 1] != address(0) && quote.amounts[i] > 0) { -// (uint256 reserveIn, uint256 reserveOut) = _getReserves(quote.pairs[i - 1], _route[i - 1], _route[i]); - -// if (reserveIn > 0 && reserveOut > quote.amounts[i]) { -// quote.amounts[i - 1] = JoeLibrary.getAmountIn(quote.amounts[i], reserveIn, reserveOut); -// quote.virtualAmountsWithoutSlippage[i - 1] = -// JoeLibrary.quote(quote.virtualAmountsWithoutSlippage[i] * 1000, reserveOut * 997, reserveIn) + 1; - -// quote.fees[i - 1] = 0.003e18; // 0.3% -// } -// } - -// // Fetch swaps for V2 -// ILBFactory.LBPairInformation[] memory LBPairsAvailable = -// ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i - 1]), IERC20(_route[i])); - -// if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { -// for (uint256 j; j < LBPairsAvailable.length; j++) { -// if (!LBPairsAvailable[j].ignoredForRouting) { -// bool swapForY = address(LBPairsAvailable[j].LBPair.tokenY()) == _route[i]; -// try ILBRouter(routerV2).getSwapIn(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) -// returns (uint256 swapAmountIn, uint256 fees) { -// if (swapAmountIn != 0 && (swapAmountIn < quote.amounts[i - 1] || quote.amounts[i - 1] == 0)) -// { -// quote.amounts[i - 1] = swapAmountIn; -// quote.pairs[i - 1] = address(LBPairsAvailable[j].LBPair); -// quote.binSteps[i - 1] = LBPairsAvailable[j].binStep; - -// // Getting current price -// (,, uint256 activeId) = LBPairsAvailable[j].LBPair.getReservesAndId(); -// quote.virtualAmountsWithoutSlippage[i - 1] = _getV2Quote( -// quote.virtualAmountsWithoutSlippage[i], activeId, quote.binSteps[i - 1], !swapForY -// ) + fees; - -// quote.fees[i - 1] = (fees * 1e18) / quote.amounts[i - 1]; // fee percentage in amountIn -// } -// } catch {} -// } -// } -// } -// } -// } - -// /// @dev Forked from JoeLibrary -// /// @dev Doesn't rely on the init code hash of the factory -// /// @param _pair Address of the pair -// /// @param _tokenA Address of token A -// /// @param _tokenB Address of token B -// /// @return reserveA Reserve of token A in the pair -// /// @return reserveB Reserve of token B in the pair -// function _getReserves(address _pair, address _tokenA, address _tokenB) -// internal -// view -// returns (uint256 reserveA, uint256 reserveB) -// { -// (address token0,) = JoeLibrary.sortTokens(_tokenA, _tokenB); -// (uint256 reserve0, uint256 reserve1,) = IJoePair(_pair).getReserves(); -// (reserveA, reserveB) = _tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); -// } - -// /// @dev Calculates a quote for a V2 pair -// /// @param _amount Amount in to consider -// /// @param _activeId Current active Id of the considred pair -// /// @param _binStep Bin step of the considered pair -// /// @param _swapForY Boolean describing if we are swapping from X to Y or the opposite -// /// @return quote Amount Out if _amount was swapped with no slippage and no fees -// function _getV2Quote(uint256 _amount, uint256 _activeId, uint256 _binStep, bool _swapForY) -// internal -// pure -// returns (uint256 quote) -// { -// if (_swapForY) { -// quote = BinHelper.getPriceFromId(_activeId, _binStep).mulShiftRoundDown(_amount, Constants.SCALE_OFFSET); -// } else { -// quote = _amount.shiftDivRoundDown(Constants.SCALE_OFFSET, BinHelper.getPriceFromId(_activeId, _binStep)); -// } -// } -// } +import "./LBErrors.sol"; +import "./libraries/BinHelper.sol"; +import "./libraries/JoeLibrary.sol"; +import "./libraries/Math512Bits.sol"; +import "./interfaces/IJoeFactory.sol"; +import "./interfaces/IJoePair.sol"; +import "./interfaces/ILBFactory.sol"; +import "./interfaces/ILBRouter.sol"; + +/// @title Liquidity Book Quoter +/// @author Trader Joe +/// @notice Helper contract to determine best path through multiple markets +contract LBQuoter { + using Math512Bits for uint256; + + /// @notice Dex V2 router address + address public immutable routerV2; + /// @notice Dex V1 factory address + address public immutable factoryV1; + /// @notice Dex V2 factory address + address public immutable factoryV2; + + struct Quote { + address[] route; + address[] pairs; + uint256[] binSteps; + uint256[] amounts; + uint256[] virtualAmountsWithoutSlippage; + uint256[] fees; + } + + /// @notice Constructor + /// @param _routerV2 Dex V2 router address + /// @param _factoryV1 Dex V1 factory address + /// @param _factoryV2 Dex V2 factory address + constructor(address _routerV2, address _factoryV1, address _factoryV2) { + routerV2 = _routerV2; + factoryV1 = _factoryV1; + factoryV2 = _factoryV2; + } + + /// @notice Finds the best path given a list of tokens and the input amount wanted from the swap + /// @param _route List of the tokens to go through + /// @param _amountIn Swap amount in + /// @return quote The Quote structure containing the necessary element to perform the swap + function findBestPathFromAmountIn(address[] calldata _route, uint256 _amountIn) + public + view + returns (Quote memory quote) + { + if (_route.length < 2) { + revert LBQuoter_InvalidLength(); + } + + quote.route = _route; + + uint256 swapLength = _route.length - 1; + quote.pairs = new address[](swapLength); + quote.binSteps = new uint256[](swapLength); + quote.fees = new uint256[](swapLength); + quote.amounts = new uint256[](_route.length); + quote.virtualAmountsWithoutSlippage = new uint256[](_route.length); + + quote.amounts[0] = _amountIn; + quote.virtualAmountsWithoutSlippage[0] = _amountIn; + + for (uint256 i; i < swapLength; i++) { + // Fetch swap for V1 + quote.pairs[i] = IJoeFactory(factoryV1).getPair(_route[i], _route[i + 1]); + + if (quote.pairs[i] != address(0) && quote.amounts[i] > 0) { + (uint256 reserveIn, uint256 reserveOut) = _getReserves(quote.pairs[i], _route[i], _route[i + 1]); + + if (reserveIn > 0 && reserveOut > 0) { + quote.amounts[i + 1] = JoeLibrary.getAmountOut(quote.amounts[i], reserveIn, reserveOut); + quote.virtualAmountsWithoutSlippage[i + 1] = + JoeLibrary.quote(quote.virtualAmountsWithoutSlippage[i] * 997, reserveIn * 1000, reserveOut); + quote.fees[i] = 0.003e18; // 0.3% + } + } + + // Fetch swaps for V2 + ILBFactory.LBPairInformation[] memory LBPairsAvailable = + ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i]), IERC20(_route[i + 1])); + + if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { + for (uint256 j; j < LBPairsAvailable.length; j++) { + if (!LBPairsAvailable[j].ignoredForRouting) { + bool swapForY = address(LBPairsAvailable[j].LBPair.tokenY()) == _route[i + 1]; + + try ILBRouter(routerV2).getSwapOut(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) + returns (uint256 swapAmountOut, uint256 fees) { + if (swapAmountOut > quote.amounts[i + 1]) { + quote.amounts[i + 1] = swapAmountOut; + quote.pairs[i] = address(LBPairsAvailable[j].LBPair); + quote.binSteps[i] = LBPairsAvailable[j].binStep; + + // Getting current price + (,, uint256 activeId) = LBPairsAvailable[j].LBPair.getReservesAndId(); + quote.virtualAmountsWithoutSlippage[i + 1] = _getV2Quote( + quote.virtualAmountsWithoutSlippage[i] - fees, activeId, quote.binSteps[i], swapForY + ); + + quote.fees[i] = (fees * 1e18) / quote.amounts[i]; // fee percentage in amountIn + } + } catch {} + } + } + } + } + } + + /// @notice Finds the best path given a list of tokens and the output amount wanted from the swap + /// @param _route List of the tokens to go through + /// @param _amountOut Swap amount out + /// @return quote The Quote structure containing the necessary element to perform the swap + function findBestPathFromAmountOut(address[] calldata _route, uint256 _amountOut) + public + view + returns (Quote memory quote) + { + if (_route.length < 2) { + revert LBQuoter_InvalidLength(); + } + quote.route = _route; + + uint256 swapLength = _route.length - 1; + quote.pairs = new address[](swapLength); + quote.binSteps = new uint256[](swapLength); + quote.fees = new uint256[](swapLength); + quote.amounts = new uint256[](_route.length); + quote.virtualAmountsWithoutSlippage = new uint256[](_route.length); + + quote.amounts[swapLength] = _amountOut; + quote.virtualAmountsWithoutSlippage[swapLength] = _amountOut; + + for (uint256 i = swapLength; i > 0; i--) { + // Fetch swap for V1 + quote.pairs[i - 1] = IJoeFactory(factoryV1).getPair(_route[i - 1], _route[i]); + if (quote.pairs[i - 1] != address(0) && quote.amounts[i] > 0) { + (uint256 reserveIn, uint256 reserveOut) = _getReserves(quote.pairs[i - 1], _route[i - 1], _route[i]); + + if (reserveIn > 0 && reserveOut > quote.amounts[i]) { + quote.amounts[i - 1] = JoeLibrary.getAmountIn(quote.amounts[i], reserveIn, reserveOut); + quote.virtualAmountsWithoutSlippage[i - 1] = + JoeLibrary.quote(quote.virtualAmountsWithoutSlippage[i] * 1000, reserveOut * 997, reserveIn) + 1; + + quote.fees[i - 1] = 0.003e18; // 0.3% + } + } + + // Fetch swaps for V2 + ILBFactory.LBPairInformation[] memory LBPairsAvailable = + ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i - 1]), IERC20(_route[i])); + + if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { + for (uint256 j; j < LBPairsAvailable.length; j++) { + if (!LBPairsAvailable[j].ignoredForRouting) { + bool swapForY = address(LBPairsAvailable[j].LBPair.tokenY()) == _route[i]; + try ILBRouter(routerV2).getSwapIn(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) + returns (uint256 swapAmountIn, uint256 fees) { + if (swapAmountIn != 0 && (swapAmountIn < quote.amounts[i - 1] || quote.amounts[i - 1] == 0)) + { + quote.amounts[i - 1] = swapAmountIn; + quote.pairs[i - 1] = address(LBPairsAvailable[j].LBPair); + quote.binSteps[i - 1] = LBPairsAvailable[j].binStep; + + // Getting current price + (,, uint256 activeId) = LBPairsAvailable[j].LBPair.getReservesAndId(); + quote.virtualAmountsWithoutSlippage[i - 1] = _getV2Quote( + quote.virtualAmountsWithoutSlippage[i], activeId, quote.binSteps[i - 1], !swapForY + ) + fees; + + quote.fees[i - 1] = (fees * 1e18) / quote.amounts[i - 1]; // fee percentage in amountIn + } + } catch {} + } + } + } + } + } + + /// @dev Forked from JoeLibrary + /// @dev Doesn't rely on the init code hash of the factory + /// @param _pair Address of the pair + /// @param _tokenA Address of token A + /// @param _tokenB Address of token B + /// @return reserveA Reserve of token A in the pair + /// @return reserveB Reserve of token B in the pair + function _getReserves(address _pair, address _tokenA, address _tokenB) + internal + view + returns (uint256 reserveA, uint256 reserveB) + { + (address token0,) = JoeLibrary.sortTokens(_tokenA, _tokenB); + (uint256 reserve0, uint256 reserve1,) = IJoePair(_pair).getReserves(); + (reserveA, reserveB) = _tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); + } + + /// @dev Calculates a quote for a V2 pair + /// @param _amount Amount in to consider + /// @param _activeId Current active Id of the considred pair + /// @param _binStep Bin step of the considered pair + /// @param _swapForY Boolean describing if we are swapping from X to Y or the opposite + /// @return quote Amount Out if _amount was swapped with no slippage and no fees + function _getV2Quote(uint256 _amount, uint256 _activeId, uint256 _binStep, bool _swapForY) + internal + pure + returns (uint256 quote) + { + if (_swapForY) { + quote = BinHelper.getPriceFromId(_activeId, _binStep).mulShiftRoundDown(_amount, Constants.SCALE_OFFSET); + } else { + quote = _amount.shiftDivRoundDown(Constants.SCALE_OFFSET, BinHelper.getPriceFromId(_activeId, _binStep)); + } + } +} diff --git a/src/LBRouter.sol b/src/LBRouter.sol index feb1eae4..d784e147 100644 --- a/src/LBRouter.sol +++ b/src/LBRouter.sol @@ -2,973 +2,973 @@ pragma solidity 0.8.10; -// import "openzeppelin/token/ERC20/IERC20.sol"; - -// import "./LBErrors.sol"; -// import "./libraries/BinHelper.sol"; -// import "./libraries/Constants.sol"; -// import "./libraries/FeeHelper.sol"; -// import "./libraries/JoeLibrary.sol"; -// import "./libraries/Math512Bits.sol"; -// import "./libraries/SwapHelper.sol"; -// import "./libraries/TokenHelper.sol"; -// import "./interfaces/IJoePair.sol"; -// import "./interfaces/ILBToken.sol"; -// import "./interfaces/ILBRouter.sol"; - -// /// @title Liquidity Book Router -// /// @author Trader Joe -// /// @notice Main contract to interact with to swap and manage liquidity on Joe V2 exchange. -// contract LBRouter is ILBRouter { -// using TokenHelper for IERC20; -// using TokenHelper for IWAVAX; -// using FeeHelper for FeeHelper.FeeParameters; -// using Math512Bits for uint256; -// using SwapHelper for ILBPair.Bin; -// using JoeLibrary for uint256; - -// ILBFactory public immutable override factory; -// IJoeFactory public immutable override oldFactory; -// IWAVAX public immutable override wavax; - -// modifier onlyFactoryOwner() { -// if (msg.sender != factory.owner()) revert LBRouter__NotFactoryOwner(); -// _; -// } - -// modifier ensure(uint256 _deadline) { -// if (block.timestamp > _deadline) revert LBRouter__DeadlineExceeded(_deadline, block.timestamp); -// _; -// } - -// modifier verifyInputs(uint256[] memory _pairBinSteps, IERC20[] memory _tokenPath) { -// if (_pairBinSteps.length == 0 || _pairBinSteps.length + 1 != _tokenPath.length) { -// revert LBRouter__LengthsMismatch(); -// } -// _; -// } - -// /// @notice Constructor -// /// @param _factory LBFactory address -// /// @param _oldFactory Address of old factory (Joe V1) -// /// @param _wavax Address of WAVAX -// constructor(ILBFactory _factory, IJoeFactory _oldFactory, IWAVAX _wavax) { -// factory = _factory; -// oldFactory = _oldFactory; -// wavax = _wavax; -// } - -// /// @dev Receive function that only accept AVAX from the WAVAX contract -// receive() external payable { -// if (msg.sender != address(wavax)) revert LBRouter__SenderIsNotWAVAX(); -// } - -// /// @notice Returns the approximate id corresponding to the inputted price. -// /// Warning, the returned id may be inaccurate close to the start price of a bin -// /// @param _LBPair The address of the LBPair -// /// @param _price The price of y per x (multiplied by 1e36) -// /// @return The id corresponding to this price -// function getIdFromPrice(ILBPair _LBPair, uint256 _price) external view override returns (uint24) { -// return BinHelper.getIdFromPrice(_price, _LBPair.feeParameters().binStep); -// } - -// /// @notice Returns the price corresponding to the inputted id -// /// @param _LBPair The address of the LBPair -// /// @param _id The id -// /// @return The price corresponding to this id -// function getPriceFromId(ILBPair _LBPair, uint24 _id) external view override returns (uint256) { -// return BinHelper.getPriceFromId(_id, _LBPair.feeParameters().binStep); -// } - -// /// @notice Simulate a swap in -// /// @param _LBPair The address of the LBPair -// /// @param _amountOut The amount of token to receive -// /// @param _swapForY Whether you swap X for Y (true), or Y for X (false) -// /// @return amountIn The amount of token to send in order to receive _amountOut token -// /// @return feesIn The amount of fees paid in token sent -// function getSwapIn(ILBPair _LBPair, uint256 _amountOut, bool _swapForY) -// public -// view -// override -// returns (uint256 amountIn, uint256 feesIn) -// { -// (uint256 _pairReserveX, uint256 _pairReserveY, uint256 _activeId) = _LBPair.getReservesAndId(); - -// if (_amountOut == 0 || (_swapForY ? _amountOut > _pairReserveY : _amountOut > _pairReserveX)) { -// revert LBRouter__WrongAmounts(_amountOut, _swapForY ? _pairReserveY : _pairReserveX); -// } // If this is wrong, then we're sure the amounts sent are wrong - -// FeeHelper.FeeParameters memory _fp = _LBPair.feeParameters(); -// _fp.updateVariableFeeParameters(_activeId); - -// uint256 _amountOutOfBin; -// uint256 _amountInWithFees; -// uint256 _reserve; -// // Performs the actual swap, bin per bin -// // It uses the findFirstNonEmptyBinId function to make sure the bin we're currently looking at -// // has liquidity in it. -// while (true) { -// { -// (uint256 _reserveX, uint256 _reserveY) = _LBPair.getBin(uint24(_activeId)); -// _reserve = _swapForY ? _reserveY : _reserveX; -// } -// uint256 _price = BinHelper.getPriceFromId(_activeId, _fp.binStep); -// if (_reserve != 0) { -// _amountOutOfBin = _amountOut >= _reserve ? _reserve : _amountOut; -// uint256 _amountInToBin = _swapForY -// ? _amountOutOfBin.shiftDivRoundUp(Constants.SCALE_OFFSET, _price) -// : _price.mulShiftRoundUp(_amountOutOfBin, Constants.SCALE_OFFSET); - -// // We update the fee, but we don't store the new volatility reference, volatility accumulated and indexRef to not penalize traders -// _fp.updateVolatilityAccumulated(_activeId); -// uint256 _fee = _fp.getFeeAmount(_amountInToBin); -// _amountInWithFees = _amountInToBin + _fee; - -// if (_amountInWithFees + _reserve > type(uint112).max) revert LBRouter__SwapOverflows(_activeId); -// amountIn += _amountInWithFees; -// feesIn += _fee; -// _amountOut -= _amountOutOfBin; -// } - -// if (_amountOut != 0) { -// _activeId = _LBPair.findFirstNonEmptyBinId(uint24(_activeId), _swapForY); -// } else { -// break; -// } -// } -// if (_amountOut != 0) revert LBRouter__BrokenSwapSafetyCheck(); // Safety check, but should never be false as it would have reverted on transfer -// } - -// /// @notice Simulate a swap out -// /// @param _LBPair The address of the LBPair -// /// @param _amountIn The amount of token sent -// /// @param _swapForY Whether you swap X for Y (true), or Y for X (false) -// /// @return amountOut The amount of token received if _amountIn tokenX are sent -// /// @return feesIn The amount of fees paid in token sent -// function getSwapOut(ILBPair _LBPair, uint256 _amountIn, bool _swapForY) -// external -// view -// override -// returns (uint256 amountOut, uint256 feesIn) -// { -// (,, uint256 _activeId) = _LBPair.getReservesAndId(); - -// FeeHelper.FeeParameters memory _fp = _LBPair.feeParameters(); -// _fp.updateVariableFeeParameters(_activeId); -// ILBPair.Bin memory _bin; - -// // Performs the actual swap, bin per bin -// // It uses the findFirstNonEmptyBinId function to make sure the bin we're currently looking at -// // has liquidity in it. -// while (true) { -// { -// (uint256 _reserveX, uint256 _reserveY) = _LBPair.getBin(uint24(_activeId)); -// _bin = ILBPair.Bin(uint112(_reserveX), uint112(_reserveY), 0, 0); -// } -// if (_bin.reserveX != 0 || _bin.reserveY != 0) { -// (uint256 _amountInToBin, uint256 _amountOutOfBin, FeeHelper.FeesDistribution memory _fees) = -// _bin.getAmounts(_fp, _activeId, _swapForY, _amountIn); - -// if (_amountInToBin > type(uint112).max) revert LBRouter__BinReserveOverflows(_activeId); - -// _amountIn -= _amountInToBin + _fees.total; -// feesIn += _fees.total; -// amountOut += _amountOutOfBin; -// } - -// if (_amountIn != 0) { -// _activeId = _LBPair.findFirstNonEmptyBinId(uint24(_activeId), _swapForY); -// } else { -// break; -// } -// } -// if (_amountIn != 0) revert LBRouter__TooMuchTokensIn(_amountIn); -// } - -// /// @notice Create a liquidity bin LBPair for _tokenX and _tokenY using the factory -// /// @param _tokenX The address of the first token -// /// @param _tokenY The address of the second token -// /// @param _activeId The active id of the pair -// /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) -// /// @return pair The address of the newly created LBPair -// function createLBPair(IERC20 _tokenX, IERC20 _tokenY, uint24 _activeId, uint16 _binStep) -// external -// override -// returns (ILBPair pair) -// { -// pair = factory.createLBPair(_tokenX, _tokenY, _activeId, _binStep); -// } - -// /// @notice Add liquidity while performing safety checks -// /// @dev This function is compliant with fee on transfer tokens -// /// @param _liquidityParameters The liquidity parameters -// /// @return depositIds Bin ids where the liquidity was actually deposited -// /// @return liquidityMinted Amounts of LBToken minted for each bin -// function addLiquidity(LiquidityParameters calldata _liquidityParameters) -// external -// override -// returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) -// { -// ILBPair _LBPair = _getLBPairInformation( -// _liquidityParameters.tokenX, _liquidityParameters.tokenY, _liquidityParameters.binStep -// ); -// if (_liquidityParameters.tokenX != _LBPair.tokenX()) revert LBRouter__WrongTokenOrder(); - -// _liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountX); -// _liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountY); - -// (depositIds, liquidityMinted) = _addLiquidity(_liquidityParameters, _LBPair); -// } - -// /// @notice Add liquidity with AVAX while performing safety checks -// /// @dev This function is compliant with fee on transfer tokens -// /// @param _liquidityParameters The liquidity parameters -// /// @return depositIds Bin ids where the liquidity was actually deposited -// /// @return liquidityMinted Amounts of LBToken minted for each bin -// function addLiquidityAVAX(LiquidityParameters calldata _liquidityParameters) -// external -// payable -// override -// returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) -// { -// ILBPair _LBPair = _getLBPairInformation( -// _liquidityParameters.tokenX, _liquidityParameters.tokenY, _liquidityParameters.binStep -// ); -// if (_liquidityParameters.tokenX != _LBPair.tokenX()) revert LBRouter__WrongTokenOrder(); - -// if (_liquidityParameters.tokenX == wavax && _liquidityParameters.amountX == msg.value) { -// _wavaxDepositAndTransfer(address(_LBPair), msg.value); -// _liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountY); -// } else if (_liquidityParameters.tokenY == wavax && _liquidityParameters.amountY == msg.value) { -// _liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountX); -// _wavaxDepositAndTransfer(address(_LBPair), msg.value); -// } else { -// revert LBRouter__WrongAvaxLiquidityParameters( -// address(_liquidityParameters.tokenX), -// address(_liquidityParameters.tokenY), -// _liquidityParameters.amountX, -// _liquidityParameters.amountY, -// msg.value -// ); -// } - -// (depositIds, liquidityMinted) = _addLiquidity(_liquidityParameters, _LBPair); -// } - -// /// @notice Remove liquidity while performing safety checks -// /// @dev This function is compliant with fee on transfer tokens -// /// @param _tokenX The address of token X -// /// @param _tokenY The address of token Y -// /// @param _binStep The bin step of the LBPair -// /// @param _amountXMin The min amount to receive of token X -// /// @param _amountYMin The min amount to receive of token Y -// /// @param _ids The list of ids to burn -// /// @param _amounts The list of amounts to burn of each id in `_ids` -// /// @param _to The address of the recipient -// /// @param _deadline The deadline of the tx -// /// @return amountX Amount of token X returned -// /// @return amountY Amount of token Y returned -// function removeLiquidity( -// IERC20 _tokenX, -// IERC20 _tokenY, -// uint16 _binStep, -// uint256 _amountXMin, -// uint256 _amountYMin, -// uint256[] memory _ids, -// uint256[] memory _amounts, -// address _to, -// uint256 _deadline -// ) external override ensure(_deadline) returns (uint256 amountX, uint256 amountY) { -// ILBPair _LBPair = _getLBPairInformation(_tokenX, _tokenY, _binStep); -// bool _isWrongOrder = _tokenX != _LBPair.tokenX(); - -// if (_isWrongOrder) (_amountXMin, _amountYMin) = (_amountYMin, _amountXMin); - -// (amountX, amountY) = _removeLiquidity(_LBPair, _amountXMin, _amountYMin, _ids, _amounts, _to); - -// if (_isWrongOrder) (amountX, amountY) = (amountY, amountX); -// } - -// /// @notice Remove AVAX liquidity while performing safety checks -// /// @dev This function is **NOT** compliant with fee on transfer tokens. -// /// This is wanted as it would make users pays the fee on transfer twice, -// /// use the `removeLiquidity` function to remove liquidity with fee on transfer tokens. -// /// @param _token The address of token -// /// @param _binStep The bin step of the LBPair -// /// @param _amountTokenMin The min amount to receive of token -// /// @param _amountAVAXMin The min amount to receive of AVAX -// /// @param _ids The list of ids to burn -// /// @param _amounts The list of amounts to burn of each id in `_ids` -// /// @param _to The address of the recipient -// /// @param _deadline The deadline of the tx -// /// @return amountToken Amount of token returned -// /// @return amountAVAX Amount of AVAX returned -// function removeLiquidityAVAX( -// IERC20 _token, -// uint16 _binStep, -// uint256 _amountTokenMin, -// uint256 _amountAVAXMin, -// uint256[] memory _ids, -// uint256[] memory _amounts, -// address payable _to, -// uint256 _deadline -// ) external override ensure(_deadline) returns (uint256 amountToken, uint256 amountAVAX) { -// ILBPair _LBPair = _getLBPairInformation(_token, IERC20(wavax), _binStep); - -// bool _isAVAXTokenY = IERC20(wavax) == _LBPair.tokenY(); -// { -// if (!_isAVAXTokenY) { -// (_amountTokenMin, _amountAVAXMin) = (_amountAVAXMin, _amountTokenMin); -// } - -// (uint256 _amountX, uint256 _amountY) = -// _removeLiquidity(_LBPair, _amountTokenMin, _amountAVAXMin, _ids, _amounts, address(this)); - -// (amountToken, amountAVAX) = _isAVAXTokenY ? (_amountX, _amountY) : (_amountY, _amountX); -// } - -// _token.safeTransfer(_to, amountToken); - -// wavax.withdraw(amountAVAX); -// _safeTransferAVAX(_to, amountAVAX); -// } - -// /// @notice Swaps exact tokens for tokens while performing safety checks -// /// @param _amountIn The amount of token to send -// /// @param _amountOutMin The min amount of token to receive -// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) -// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` -// /// @param _to The address of the recipient -// /// @param _deadline The deadline of the tx -// /// @return amountOut Output amount of the swap -// function swapExactTokensForTokens( -// uint256 _amountIn, -// uint256 _amountOutMin, -// uint256[] memory _pairBinSteps, -// IERC20[] memory _tokenPath, -// address _to, -// uint256 _deadline -// ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { -// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - -// _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); - -// amountOut = _swapExactTokensForTokens(_amountIn, _pairs, _pairBinSteps, _tokenPath, _to); - -// if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); -// } - -// /// @notice Swaps exact tokens for AVAX while performing safety checks -// /// @param _amountIn The amount of token to send -// /// @param _amountOutMinAVAX The min amount of AVAX to receive -// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) -// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` -// /// @param _to The address of the recipient -// /// @param _deadline The deadline of the tx -// /// @return amountOut Output amount of the swap -// function swapExactTokensForAVAX( -// uint256 _amountIn, -// uint256 _amountOutMinAVAX, -// uint256[] memory _pairBinSteps, -// IERC20[] memory _tokenPath, -// address payable _to, -// uint256 _deadline -// ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { -// if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { -// revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); -// } - -// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - -// _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); - -// amountOut = _swapExactTokensForTokens(_amountIn, _pairs, _pairBinSteps, _tokenPath, address(this)); - -// if (_amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMinAVAX, amountOut); - -// wavax.withdraw(amountOut); -// _safeTransferAVAX(_to, amountOut); -// } - -// /// @notice Swaps exact AVAX for tokens while performing safety checks -// /// @param _amountOutMin The min amount of token to receive -// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) -// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` -// /// @param _to The address of the recipient -// /// @param _deadline The deadline of the tx -// /// @return amountOut Output amount of the swap -// function swapExactAVAXForTokens( -// uint256 _amountOutMin, -// uint256[] memory _pairBinSteps, -// IERC20[] memory _tokenPath, -// address _to, -// uint256 _deadline -// ) external payable override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { -// if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); - -// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - -// _wavaxDepositAndTransfer(_pairs[0], msg.value); - -// amountOut = _swapExactTokensForTokens(msg.value, _pairs, _pairBinSteps, _tokenPath, _to); - -// if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); -// } - -// /// @notice Swaps tokens for exact tokens while performing safety checks -// /// @param _amountOut The amount of token to receive -// /// @param _amountInMax The max amount of token to send -// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) -// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` -// /// @param _to The address of the recipient -// /// @param _deadline The deadline of the tx -// /// @return amountsIn Input amounts for every step of the swap -// function swapTokensForExactTokens( -// uint256 _amountOut, -// uint256 _amountInMax, -// uint256[] memory _pairBinSteps, -// IERC20[] memory _tokenPath, -// address _to, -// uint256 _deadline -// ) -// external -// override -// ensure(_deadline) -// verifyInputs(_pairBinSteps, _tokenPath) -// returns (uint256[] memory amountsIn) -// { -// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); -// amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountOut); - -// if (amountsIn[0] > _amountInMax) revert LBRouter__MaxAmountInExceeded(_amountInMax, amountsIn[0]); - -// _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], amountsIn[0]); - -// uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, _to); - -// if (_amountOutReal < _amountOut) revert LBRouter__InsufficientAmountOut(_amountOut, _amountOutReal); -// } - -// /// @notice Swaps tokens for exact AVAX while performing safety checks -// /// @param _amountAVAXOut The amount of AVAX to receive -// /// @param _amountInMax The max amount of token to send -// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) -// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` -// /// @param _to The address of the recipient -// /// @param _deadline The deadline of the tx -// /// @return amountsIn Input amounts for every step of the swap -// function swapTokensForExactAVAX( -// uint256 _amountAVAXOut, -// uint256 _amountInMax, -// uint256[] memory _pairBinSteps, -// IERC20[] memory _tokenPath, -// address payable _to, -// uint256 _deadline -// ) -// external -// override -// ensure(_deadline) -// verifyInputs(_pairBinSteps, _tokenPath) -// returns (uint256[] memory amountsIn) -// { -// if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { -// revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); -// } - -// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); -// amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountAVAXOut); - -// if (amountsIn[0] > _amountInMax) revert LBRouter__MaxAmountInExceeded(_amountInMax, amountsIn[0]); - -// _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], amountsIn[0]); - -// uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, address(this)); - -// if (_amountOutReal < _amountAVAXOut) revert LBRouter__InsufficientAmountOut(_amountAVAXOut, _amountOutReal); - -// wavax.withdraw(_amountOutReal); -// _safeTransferAVAX(_to, _amountOutReal); -// } - -// /// @notice Swaps AVAX for exact tokens while performing safety checks -// /// @dev Will refund any AVAX amount sent in excess to `msg.sender` -// /// @param _amountOut The amount of tokens to receive -// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) -// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` -// /// @param _to The address of the recipient -// /// @param _deadline The deadline of the tx -// /// @return amountsIn Input amounts for every step of the swap -// function swapAVAXForExactTokens( -// uint256 _amountOut, -// uint256[] memory _pairBinSteps, -// IERC20[] memory _tokenPath, -// address _to, -// uint256 _deadline -// ) -// external -// payable -// override -// ensure(_deadline) -// verifyInputs(_pairBinSteps, _tokenPath) -// returns (uint256[] memory amountsIn) -// { -// if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); - -// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); -// amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountOut); - -// if (amountsIn[0] > msg.value) revert LBRouter__MaxAmountInExceeded(msg.value, amountsIn[0]); - -// _wavaxDepositAndTransfer(_pairs[0], amountsIn[0]); - -// uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, _to); - -// if (_amountOutReal < _amountOut) revert LBRouter__InsufficientAmountOut(_amountOut, _amountOutReal); - -// if (msg.value > amountsIn[0]) _safeTransferAVAX(msg.sender, msg.value - amountsIn[0]); -// } - -// /// @notice Swaps exact tokens for tokens while performing safety checks supporting for fee on transfer tokens -// /// @param _amountIn The amount of token to send -// /// @param _amountOutMin The min amount of token to receive -// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) -// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` -// /// @param _to The address of the recipient -// /// @param _deadline The deadline of the tx -// /// @return amountOut Output amount of the swap -// function swapExactTokensForTokensSupportingFeeOnTransferTokens( -// uint256 _amountIn, -// uint256 _amountOutMin, -// uint256[] memory _pairBinSteps, -// IERC20[] memory _tokenPath, -// address _to, -// uint256 _deadline -// ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { -// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - -// IERC20 _targetToken = _tokenPath[_pairs.length]; - -// uint256 _balanceBefore = _targetToken.balanceOf(_to); - -// _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); - -// _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, _to); - -// amountOut = _targetToken.balanceOf(_to) - _balanceBefore; -// if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); -// } - -// /// @notice Swaps exact tokens for AVAX while performing safety checks supporting for fee on transfer tokens -// /// @param _amountIn The amount of token to send -// /// @param _amountOutMinAVAX The min amount of AVAX to receive -// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) -// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` -// /// @param _to The address of the recipient -// /// @param _deadline The deadline of the tx -// /// @return amountOut Output amount of the swap -// function swapExactTokensForAVAXSupportingFeeOnTransferTokens( -// uint256 _amountIn, -// uint256 _amountOutMinAVAX, -// uint256[] memory _pairBinSteps, -// IERC20[] memory _tokenPath, -// address payable _to, -// uint256 _deadline -// ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { -// if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { -// revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); -// } - -// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - -// uint256 _balanceBefore = wavax.balanceOf(address(this)); - -// _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); - -// _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, address(this)); - -// amountOut = wavax.balanceOf(address(this)) - _balanceBefore; -// if (_amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMinAVAX, amountOut); - -// wavax.withdraw(amountOut); -// _safeTransferAVAX(_to, amountOut); -// } - -// /// @notice Swaps exact AVAX for tokens while performing safety checks supporting for fee on transfer tokens -// /// @param _amountOutMin The min amount of token to receive -// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) -// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` -// /// @param _to The address of the recipient -// /// @param _deadline The deadline of the tx -// /// @return amountOut Output amount of the swap -// function swapExactAVAXForTokensSupportingFeeOnTransferTokens( -// uint256 _amountOutMin, -// uint256[] memory _pairBinSteps, -// IERC20[] memory _tokenPath, -// address _to, -// uint256 _deadline -// ) external payable override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { -// if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); - -// address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - -// IERC20 _targetToken = _tokenPath[_pairs.length]; - -// uint256 _balanceBefore = _targetToken.balanceOf(_to); - -// _wavaxDepositAndTransfer(_pairs[0], msg.value); - -// _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, _to); - -// amountOut = _targetToken.balanceOf(_to) - _balanceBefore; -// if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); -// } - -// /// @notice Unstuck tokens that are sent to this contract by mistake -// /// @dev Only callable by the factory owner -// /// @param _token The address of the token -// /// @param _to The address of the user to send back the tokens -// /// @param _amount The amount to send -// function sweep(IERC20 _token, address _to, uint256 _amount) external override onlyFactoryOwner { -// if (address(_token) == address(0)) { -// if (_amount == type(uint256).max) _amount = address(this).balance; -// _safeTransferAVAX(_to, _amount); -// } else { -// if (_amount == type(uint256).max) _amount = _token.balanceOf(address(this)); -// _token.safeTransfer(_to, _amount); -// } -// } - -// /// @notice Unstuck LBTokens that are sent to this contract by mistake -// /// @dev Only callable by the factory owner -// /// @param _lbToken The address of the LBToken -// /// @param _to The address of the user to send back the tokens -// /// @param _ids The list of token ids -// /// @param _amounts The list of amounts to send -// function sweepLBToken(ILBToken _lbToken, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) -// external -// override -// onlyFactoryOwner -// { -// _lbToken.safeBatchTransferFrom(address(this), _to, _ids, _amounts); -// } - -// /// @notice Helper function to add liquidity -// /// @param _liq The liquidity parameter -// /// @param _LBPair LBPair where liquidity is deposited -// /// @return depositIds Bin ids where the liquidity was actually deposited -// /// @return liquidityMinted Amounts of LBToken minted for each bin -// function _addLiquidity(LiquidityParameters calldata _liq, ILBPair _LBPair) -// private -// ensure(_liq.deadline) -// returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) -// { -// unchecked { -// if (_liq.deltaIds.length != _liq.distributionX.length && _liq.deltaIds.length != _liq.distributionY.length) -// { -// revert LBRouter__LengthsMismatch(); -// } - -// if (_liq.activeIdDesired > type(uint24).max || _liq.idSlippage > type(uint24).max) { -// revert LBRouter__IdDesiredOverflows(_liq.activeIdDesired, _liq.idSlippage); -// } - -// (,, uint256 _activeId) = _LBPair.getReservesAndId(); -// if ( -// _liq.activeIdDesired + _liq.idSlippage < _activeId || _activeId + _liq.idSlippage < _liq.activeIdDesired -// ) revert LBRouter__IdSlippageCaught(_liq.activeIdDesired, _liq.idSlippage, _activeId); - -// depositIds = new uint256[](_liq.deltaIds.length); -// for (uint256 i; i < depositIds.length; ++i) { -// int256 _id = int256(_activeId) + _liq.deltaIds[i]; -// if (_id < 0 || uint256(_id) > type(uint24).max) revert LBRouter__IdOverflows(_id); -// depositIds[i] = uint256(_id); -// } - -// uint256 _amountXAdded; -// uint256 _amountYAdded; - -// (_amountXAdded, _amountYAdded, liquidityMinted) = -// _LBPair.mint(depositIds, _liq.distributionX, _liq.distributionY, _liq.to); - -// if (_amountXAdded < _liq.amountXMin || _amountYAdded < _liq.amountYMin) { -// revert LBRouter__AmountSlippageCaught(_liq.amountXMin, _amountXAdded, _liq.amountYMin, _amountYAdded); -// } -// } -// } - -// /// @notice Helper function to return the amounts in -// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) -// /// @param _pairs The list of pairs -// /// @param _tokenPath The swap path -// /// @param _amountOut The amount out -// /// @return amountsIn The list of amounts in -// function _getAmountsIn( -// uint256[] memory _pairBinSteps, -// address[] memory _pairs, -// IERC20[] memory _tokenPath, -// uint256 _amountOut -// ) private view returns (uint256[] memory amountsIn) { -// amountsIn = new uint256[](_tokenPath.length); -// // Avoid doing -1, as `_pairs.length == _pairBinSteps.length-1` -// amountsIn[_pairs.length] = _amountOut; - -// for (uint256 i = _pairs.length; i != 0; i--) { -// IERC20 _token = _tokenPath[i - 1]; -// uint256 _binStep = _pairBinSteps[i - 1]; - -// address _pair = _pairs[i - 1]; - -// if (_binStep == 0) { -// (uint256 _reserveIn, uint256 _reserveOut,) = IJoePair(_pair).getReserves(); -// if (_token > _tokenPath[i]) { -// (_reserveIn, _reserveOut) = (_reserveOut, _reserveIn); -// } - -// uint256 amountOut_ = amountsIn[i]; -// amountsIn[i - 1] = amountOut_.getAmountIn(_reserveIn, _reserveOut); -// } else { -// (amountsIn[i - 1],) = getSwapIn(ILBPair(_pair), amountsIn[i], ILBPair(_pair).tokenX() == _token); -// } -// } -// } - -// /// @notice Helper function to remove liquidity -// /// @param _LBPair The address of the LBPair -// /// @param _amountXMin The min amount to receive of token X -// /// @param _amountYMin The min amount to receive of token Y -// /// @param _ids The list of ids to burn -// /// @param _amounts The list of amounts to burn of each id in `_ids` -// /// @param _to The address of the recipient -// /// @return amountX The amount of token X sent by the pair -// /// @return amountY The amount of token Y sent by the pair -// function _removeLiquidity( -// ILBPair _LBPair, -// uint256 _amountXMin, -// uint256 _amountYMin, -// uint256[] memory _ids, -// uint256[] memory _amounts, -// address _to -// ) private returns (uint256 amountX, uint256 amountY) { -// ILBToken(address(_LBPair)).safeBatchTransferFrom(msg.sender, address(_LBPair), _ids, _amounts); -// (amountX, amountY) = _LBPair.burn(_ids, _amounts, _to); -// if (amountX < _amountXMin || amountY < _amountYMin) { -// revert LBRouter__AmountSlippageCaught(_amountXMin, amountX, _amountYMin, amountY); -// } -// } - -// /// @notice Helper function to swap exact tokens for tokens -// /// @param _amountIn The amount of token sent -// /// @param _pairs The list of pairs -// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) -// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` -// /// @param _to The address of the recipient -// /// @return amountOut The amount of token sent to `_to` -// function _swapExactTokensForTokens( -// uint256 _amountIn, -// address[] memory _pairs, -// uint256[] memory _pairBinSteps, -// IERC20[] memory _tokenPath, -// address _to -// ) private returns (uint256 amountOut) { -// IERC20 _token; -// uint256 _binStep; -// address _recipient; -// address _pair; - -// IERC20 _tokenNext = _tokenPath[0]; -// amountOut = _amountIn; - -// unchecked { -// for (uint256 i; i < _pairs.length; ++i) { -// _pair = _pairs[i]; -// _binStep = _pairBinSteps[i]; - -// _token = _tokenNext; -// _tokenNext = _tokenPath[i + 1]; - -// _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; - -// if (_binStep == 0) { -// (uint256 _reserve0, uint256 _reserve1,) = IJoePair(_pair).getReserves(); - -// if (_token < _tokenNext) { -// amountOut = amountOut.getAmountOut(_reserve0, _reserve1); -// IJoePair(_pair).swap(0, amountOut, _recipient, ""); -// } else { -// amountOut = amountOut.getAmountOut(_reserve1, _reserve0); -// IJoePair(_pair).swap(amountOut, 0, _recipient, ""); -// } -// } else { -// bool _swapForY = _tokenNext == ILBPair(_pair).tokenY(); - -// (uint256 _amountXOut, uint256 _amountYOut) = ILBPair(_pair).swap(_swapForY, _recipient); - -// if (_swapForY) amountOut = _amountYOut; -// else amountOut = _amountXOut; -// } -// } -// } -// } - -// /// @notice Helper function to swap tokens for exact tokens -// /// @param _pairs The array of pairs -// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) -// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` -// /// @param _amountsIn The list of amounts in -// /// @param _to The address of the recipient -// /// @return amountOut The amount of token sent to `_to` -// function _swapTokensForExactTokens( -// address[] memory _pairs, -// uint256[] memory _pairBinSteps, -// IERC20[] memory _tokenPath, -// uint256[] memory _amountsIn, -// address _to -// ) private returns (uint256 amountOut) { -// IERC20 _token; -// uint256 _binStep; -// address _recipient; -// address _pair; - -// IERC20 _tokenNext = _tokenPath[0]; - -// unchecked { -// for (uint256 i; i < _pairs.length; ++i) { -// _pair = _pairs[i]; -// _binStep = _pairBinSteps[i]; - -// _token = _tokenNext; -// _tokenNext = _tokenPath[i + 1]; - -// _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; - -// if (_binStep == 0) { -// amountOut = _amountsIn[i + 1]; -// if (_token < _tokenNext) { -// IJoePair(_pair).swap(0, amountOut, _recipient, ""); -// } else { -// IJoePair(_pair).swap(amountOut, 0, _recipient, ""); -// } -// } else { -// bool _swapForY = _tokenNext == ILBPair(_pair).tokenY(); - -// (uint256 _amountXOut, uint256 _amountYOut) = ILBPair(_pair).swap(_swapForY, _recipient); - -// if (_swapForY) amountOut = _amountYOut; -// else amountOut = _amountXOut; -// } -// } -// } -// } - -// /// @notice Helper function to swap exact tokens supporting for fee on transfer tokens -// /// @param _pairs The list of pairs -// /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) -// /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` -// /// @param _to The address of the recipient -// function _swapSupportingFeeOnTransferTokens( -// address[] memory _pairs, -// uint256[] memory _pairBinSteps, -// IERC20[] memory _tokenPath, -// address _to -// ) private { -// IERC20 _token; -// uint256 _binStep; -// address _recipient; -// address _pair; - -// IERC20 _tokenNext = _tokenPath[0]; - -// unchecked { -// for (uint256 i; i < _pairs.length; ++i) { -// _pair = _pairs[i]; -// _binStep = _pairBinSteps[i]; - -// _token = _tokenNext; -// _tokenNext = _tokenPath[i + 1]; - -// _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; - -// if (_binStep == 0) { -// (uint256 _reserve0, uint256 _reserve1,) = IJoePair(_pair).getReserves(); -// if (_token < _tokenNext) { -// uint256 _amountIn = _token.balanceOf(_pair) - _reserve0; -// uint256 _amountOut = _amountIn.getAmountOut(_reserve0, _reserve1); - -// IJoePair(_pair).swap(0, _amountOut, _recipient, ""); -// } else { -// uint256 _amountIn = _token.balanceOf(_pair) - _reserve1; -// uint256 _amountOut = _amountIn.getAmountOut(_reserve1, _reserve0); - -// IJoePair(_pair).swap(_amountOut, 0, _recipient, ""); -// } -// } else { -// ILBPair(_pair).swap(_tokenNext == ILBPair(_pair).tokenY(), _recipient); -// } -// } -// } -// } - -// /// @notice Helper function to return the address of the LBPair -// /// @dev Revert if the pair is not created yet -// /// @param _tokenX The address of the tokenX -// /// @param _tokenY The address of the tokenY -// /// @param _binStep The bin step of the LBPair -// /// @return The address of the LBPair -// function _getLBPairInformation(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep) private view returns (ILBPair) { -// ILBPair _LBPair = factory.getLBPairInformation(_tokenX, _tokenY, _binStep).LBPair; -// if (address(_LBPair) == address(0)) { -// revert LBRouter__PairNotCreated(address(_tokenX), address(_tokenY), _binStep); -// } -// return _LBPair; -// } - -// /// @notice Helper function to return the address of the pair (v1 or v2, according to `_binStep`) -// /// @dev Revert if the pair is not created yet -// /// @param _binStep The bin step of the LBPair, 0 means using V1 pair, any other value will use V2 -// /// @param _tokenX The address of the tokenX -// /// @param _tokenY The address of the tokenY -// /// @return _pair The address of the pair of binStep `_binStep` -// function _getPair(uint256 _binStep, IERC20 _tokenX, IERC20 _tokenY) private view returns (address _pair) { -// if (_binStep == 0) { -// _pair = oldFactory.getPair(address(_tokenX), address(_tokenY)); -// if (_pair == address(0)) revert LBRouter__PairNotCreated(address(_tokenX), address(_tokenY), _binStep); -// } else { -// _pair = address(_getLBPairInformation(_tokenX, _tokenY, _binStep)); -// } -// } - -// function _getPairs(uint256[] memory _pairBinSteps, IERC20[] memory _tokenPath) -// private -// view -// returns (address[] memory pairs) -// { -// pairs = new address[](_pairBinSteps.length); - -// IERC20 _token; -// IERC20 _tokenNext = _tokenPath[0]; -// unchecked { -// for (uint256 i; i < pairs.length; ++i) { -// _token = _tokenNext; -// _tokenNext = _tokenPath[i + 1]; - -// pairs[i] = _getPair(_pairBinSteps[i], _token, _tokenNext); -// } -// } -// } - -// /// @notice Helper function to transfer AVAX -// /// @param _to The address of the recipient -// /// @param _amount The AVAX amount to send -// function _safeTransferAVAX(address _to, uint256 _amount) private { -// (bool success,) = _to.call{value: _amount}(""); -// if (!success) revert LBRouter__FailedToSendAVAX(_to, _amount); -// } - -// /// @notice Helper function to deposit and transfer wavax -// /// @param _to The address of the recipient -// /// @param _amount The AVAX amount to wrap -// function _wavaxDepositAndTransfer(address _to, uint256 _amount) private { -// wavax.deposit{value: _amount}(); -// wavax.safeTransfer(_to, _amount); -// } -// } +import "openzeppelin/token/ERC20/IERC20.sol"; + +import "./LBErrors.sol"; +import "./libraries/BinHelper.sol"; +import "./libraries/Constants.sol"; +import "./libraries/FeeHelper.sol"; +import "./libraries/JoeLibrary.sol"; +import "./libraries/Math512Bits.sol"; +import "./libraries/SwapHelper.sol"; +import "./libraries/TokenHelper.sol"; +import "./interfaces/IJoePair.sol"; +import "./interfaces/ILBToken.sol"; +import "./interfaces/ILBRouter.sol"; + +/// @title Liquidity Book Router +/// @author Trader Joe +/// @notice Main contract to interact with to swap and manage liquidity on Joe V2 exchange. +contract LBRouter is ILBRouter { + using TokenHelper for IERC20; + using TokenHelper for IWAVAX; + using FeeHelper for FeeHelper.FeeParameters; + using Math512Bits for uint256; + using SwapHelper for ILBPair.Bin; + using JoeLibrary for uint256; + + ILBFactory public immutable override factory; + IJoeFactory public immutable override oldFactory; + IWAVAX public immutable override wavax; + + modifier onlyFactoryOwner() { + if (msg.sender != factory.owner()) revert LBRouter__NotFactoryOwner(); + _; + } + + modifier ensure(uint256 _deadline) { + if (block.timestamp > _deadline) revert LBRouter__DeadlineExceeded(_deadline, block.timestamp); + _; + } + + modifier verifyInputs(uint256[] memory _pairBinSteps, IERC20[] memory _tokenPath) { + if (_pairBinSteps.length == 0 || _pairBinSteps.length + 1 != _tokenPath.length) { + revert LBRouter__LengthsMismatch(); + } + _; + } + + /// @notice Constructor + /// @param _factory LBFactory address + /// @param _oldFactory Address of old factory (Joe V1) + /// @param _wavax Address of WAVAX + constructor(ILBFactory _factory, IJoeFactory _oldFactory, IWAVAX _wavax) { + factory = _factory; + oldFactory = _oldFactory; + wavax = _wavax; + } + + /// @dev Receive function that only accept AVAX from the WAVAX contract + receive() external payable { + if (msg.sender != address(wavax)) revert LBRouter__SenderIsNotWAVAX(); + } + + /// @notice Returns the approximate id corresponding to the inputted price. + /// Warning, the returned id may be inaccurate close to the start price of a bin + /// @param _LBPair The address of the LBPair + /// @param _price The price of y per x (multiplied by 1e36) + /// @return The id corresponding to this price + function getIdFromPrice(ILBPair _LBPair, uint256 _price) external view override returns (uint24) { + return BinHelper.getIdFromPrice(_price, _LBPair.feeParameters().binStep); + } + + /// @notice Returns the price corresponding to the inputted id + /// @param _LBPair The address of the LBPair + /// @param _id The id + /// @return The price corresponding to this id + function getPriceFromId(ILBPair _LBPair, uint24 _id) external view override returns (uint256) { + return BinHelper.getPriceFromId(_id, _LBPair.feeParameters().binStep); + } + + /// @notice Simulate a swap in + /// @param _LBPair The address of the LBPair + /// @param _amountOut The amount of token to receive + /// @param _swapForY Whether you swap X for Y (true), or Y for X (false) + /// @return amountIn The amount of token to send in order to receive _amountOut token + /// @return feesIn The amount of fees paid in token sent + function getSwapIn(ILBPair _LBPair, uint256 _amountOut, bool _swapForY) + public + view + override + returns (uint256 amountIn, uint256 feesIn) + { + (uint256 _pairReserveX, uint256 _pairReserveY, uint256 _activeId) = _LBPair.getReservesAndId(); + + if (_amountOut == 0 || (_swapForY ? _amountOut > _pairReserveY : _amountOut > _pairReserveX)) { + revert LBRouter__WrongAmounts(_amountOut, _swapForY ? _pairReserveY : _pairReserveX); + } // If this is wrong, then we're sure the amounts sent are wrong + + FeeHelper.FeeParameters memory _fp = _LBPair.feeParameters(); + _fp.updateVariableFeeParameters(_activeId); + + uint256 _amountOutOfBin; + uint256 _amountInWithFees; + uint256 _reserve; + // Performs the actual swap, bin per bin + // It uses the findFirstNonEmptyBinId function to make sure the bin we're currently looking at + // has liquidity in it. + while (true) { + { + (uint256 _reserveX, uint256 _reserveY) = _LBPair.getBin(uint24(_activeId)); + _reserve = _swapForY ? _reserveY : _reserveX; + } + uint256 _price = BinHelper.getPriceFromId(_activeId, _fp.binStep); + if (_reserve != 0) { + _amountOutOfBin = _amountOut >= _reserve ? _reserve : _amountOut; + uint256 _amountInToBin = _swapForY + ? _amountOutOfBin.shiftDivRoundUp(Constants.SCALE_OFFSET, _price) + : _price.mulShiftRoundUp(_amountOutOfBin, Constants.SCALE_OFFSET); + + // We update the fee, but we don't store the new volatility reference, volatility accumulated and indexRef to not penalize traders + _fp.updateVolatilityAccumulated(_activeId); + uint256 _fee = _fp.getFeeAmount(_amountInToBin); + _amountInWithFees = _amountInToBin + _fee; + + if (_amountInWithFees + _reserve > type(uint112).max) revert LBRouter__SwapOverflows(_activeId); + amountIn += _amountInWithFees; + feesIn += _fee; + _amountOut -= _amountOutOfBin; + } + + if (_amountOut != 0) { + _activeId = _LBPair.findFirstNonEmptyBinId(uint24(_activeId), _swapForY); + } else { + break; + } + } + if (_amountOut != 0) revert LBRouter__BrokenSwapSafetyCheck(); // Safety check, but should never be false as it would have reverted on transfer + } + + /// @notice Simulate a swap out + /// @param _LBPair The address of the LBPair + /// @param _amountIn The amount of token sent + /// @param _swapForY Whether you swap X for Y (true), or Y for X (false) + /// @return amountOut The amount of token received if _amountIn tokenX are sent + /// @return feesIn The amount of fees paid in token sent + function getSwapOut(ILBPair _LBPair, uint256 _amountIn, bool _swapForY) + external + view + override + returns (uint256 amountOut, uint256 feesIn) + { + (,, uint256 _activeId) = _LBPair.getReservesAndId(); + + FeeHelper.FeeParameters memory _fp = _LBPair.feeParameters(); + _fp.updateVariableFeeParameters(_activeId); + ILBPair.Bin memory _bin; + + // Performs the actual swap, bin per bin + // It uses the findFirstNonEmptyBinId function to make sure the bin we're currently looking at + // has liquidity in it. + while (true) { + { + (uint256 _reserveX, uint256 _reserveY) = _LBPair.getBin(uint24(_activeId)); + _bin = ILBPair.Bin(uint112(_reserveX), uint112(_reserveY), 0, 0); + } + if (_bin.reserveX != 0 || _bin.reserveY != 0) { + (uint256 _amountInToBin, uint256 _amountOutOfBin, FeeHelper.FeesDistribution memory _fees) = + _bin.getAmounts(_fp, _activeId, _swapForY, _amountIn); + + if (_amountInToBin > type(uint112).max) revert LBRouter__BinReserveOverflows(_activeId); + + _amountIn -= _amountInToBin + _fees.total; + feesIn += _fees.total; + amountOut += _amountOutOfBin; + } + + if (_amountIn != 0) { + _activeId = _LBPair.findFirstNonEmptyBinId(uint24(_activeId), _swapForY); + } else { + break; + } + } + if (_amountIn != 0) revert LBRouter__TooMuchTokensIn(_amountIn); + } + + /// @notice Create a liquidity bin LBPair for _tokenX and _tokenY using the factory + /// @param _tokenX The address of the first token + /// @param _tokenY The address of the second token + /// @param _activeId The active id of the pair + /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) + /// @return pair The address of the newly created LBPair + function createLBPair(IERC20 _tokenX, IERC20 _tokenY, uint24 _activeId, uint16 _binStep) + external + override + returns (ILBPair pair) + { + pair = factory.createLBPair(_tokenX, _tokenY, _activeId, _binStep); + } + + /// @notice Add liquidity while performing safety checks + /// @dev This function is compliant with fee on transfer tokens + /// @param _liquidityParameters The liquidity parameters + /// @return depositIds Bin ids where the liquidity was actually deposited + /// @return liquidityMinted Amounts of LBToken minted for each bin + function addLiquidity(LiquidityParameters calldata _liquidityParameters) + external + override + returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) + { + ILBPair _LBPair = _getLBPairInformation( + _liquidityParameters.tokenX, _liquidityParameters.tokenY, _liquidityParameters.binStep + ); + if (_liquidityParameters.tokenX != _LBPair.tokenX()) revert LBRouter__WrongTokenOrder(); + + _liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountX); + _liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountY); + + (depositIds, liquidityMinted) = _addLiquidity(_liquidityParameters, _LBPair); + } + + /// @notice Add liquidity with AVAX while performing safety checks + /// @dev This function is compliant with fee on transfer tokens + /// @param _liquidityParameters The liquidity parameters + /// @return depositIds Bin ids where the liquidity was actually deposited + /// @return liquidityMinted Amounts of LBToken minted for each bin + function addLiquidityAVAX(LiquidityParameters calldata _liquidityParameters) + external + payable + override + returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) + { + ILBPair _LBPair = _getLBPairInformation( + _liquidityParameters.tokenX, _liquidityParameters.tokenY, _liquidityParameters.binStep + ); + if (_liquidityParameters.tokenX != _LBPair.tokenX()) revert LBRouter__WrongTokenOrder(); + + if (_liquidityParameters.tokenX == wavax && _liquidityParameters.amountX == msg.value) { + _wavaxDepositAndTransfer(address(_LBPair), msg.value); + _liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountY); + } else if (_liquidityParameters.tokenY == wavax && _liquidityParameters.amountY == msg.value) { + _liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountX); + _wavaxDepositAndTransfer(address(_LBPair), msg.value); + } else { + revert LBRouter__WrongAvaxLiquidityParameters( + address(_liquidityParameters.tokenX), + address(_liquidityParameters.tokenY), + _liquidityParameters.amountX, + _liquidityParameters.amountY, + msg.value + ); + } + + (depositIds, liquidityMinted) = _addLiquidity(_liquidityParameters, _LBPair); + } + + /// @notice Remove liquidity while performing safety checks + /// @dev This function is compliant with fee on transfer tokens + /// @param _tokenX The address of token X + /// @param _tokenY The address of token Y + /// @param _binStep The bin step of the LBPair + /// @param _amountXMin The min amount to receive of token X + /// @param _amountYMin The min amount to receive of token Y + /// @param _ids The list of ids to burn + /// @param _amounts The list of amounts to burn of each id in `_ids` + /// @param _to The address of the recipient + /// @param _deadline The deadline of the tx + /// @return amountX Amount of token X returned + /// @return amountY Amount of token Y returned + function removeLiquidity( + IERC20 _tokenX, + IERC20 _tokenY, + uint16 _binStep, + uint256 _amountXMin, + uint256 _amountYMin, + uint256[] memory _ids, + uint256[] memory _amounts, + address _to, + uint256 _deadline + ) external override ensure(_deadline) returns (uint256 amountX, uint256 amountY) { + ILBPair _LBPair = _getLBPairInformation(_tokenX, _tokenY, _binStep); + bool _isWrongOrder = _tokenX != _LBPair.tokenX(); + + if (_isWrongOrder) (_amountXMin, _amountYMin) = (_amountYMin, _amountXMin); + + (amountX, amountY) = _removeLiquidity(_LBPair, _amountXMin, _amountYMin, _ids, _amounts, _to); + + if (_isWrongOrder) (amountX, amountY) = (amountY, amountX); + } + + /// @notice Remove AVAX liquidity while performing safety checks + /// @dev This function is **NOT** compliant with fee on transfer tokens. + /// This is wanted as it would make users pays the fee on transfer twice, + /// use the `removeLiquidity` function to remove liquidity with fee on transfer tokens. + /// @param _token The address of token + /// @param _binStep The bin step of the LBPair + /// @param _amountTokenMin The min amount to receive of token + /// @param _amountAVAXMin The min amount to receive of AVAX + /// @param _ids The list of ids to burn + /// @param _amounts The list of amounts to burn of each id in `_ids` + /// @param _to The address of the recipient + /// @param _deadline The deadline of the tx + /// @return amountToken Amount of token returned + /// @return amountAVAX Amount of AVAX returned + function removeLiquidityAVAX( + IERC20 _token, + uint16 _binStep, + uint256 _amountTokenMin, + uint256 _amountAVAXMin, + uint256[] memory _ids, + uint256[] memory _amounts, + address payable _to, + uint256 _deadline + ) external override ensure(_deadline) returns (uint256 amountToken, uint256 amountAVAX) { + ILBPair _LBPair = _getLBPairInformation(_token, IERC20(wavax), _binStep); + + bool _isAVAXTokenY = IERC20(wavax) == _LBPair.tokenY(); + { + if (!_isAVAXTokenY) { + (_amountTokenMin, _amountAVAXMin) = (_amountAVAXMin, _amountTokenMin); + } + + (uint256 _amountX, uint256 _amountY) = + _removeLiquidity(_LBPair, _amountTokenMin, _amountAVAXMin, _ids, _amounts, address(this)); + + (amountToken, amountAVAX) = _isAVAXTokenY ? (_amountX, _amountY) : (_amountY, _amountX); + } + + _token.safeTransfer(_to, amountToken); + + wavax.withdraw(amountAVAX); + _safeTransferAVAX(_to, amountAVAX); + } + + /// @notice Swaps exact tokens for tokens while performing safety checks + /// @param _amountIn The amount of token to send + /// @param _amountOutMin The min amount of token to receive + /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param _to The address of the recipient + /// @param _deadline The deadline of the tx + /// @return amountOut Output amount of the swap + function swapExactTokensForTokens( + uint256 _amountIn, + uint256 _amountOutMin, + uint256[] memory _pairBinSteps, + IERC20[] memory _tokenPath, + address _to, + uint256 _deadline + ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { + address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + + _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + + amountOut = _swapExactTokensForTokens(_amountIn, _pairs, _pairBinSteps, _tokenPath, _to); + + if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); + } + + /// @notice Swaps exact tokens for AVAX while performing safety checks + /// @param _amountIn The amount of token to send + /// @param _amountOutMinAVAX The min amount of AVAX to receive + /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param _to The address of the recipient + /// @param _deadline The deadline of the tx + /// @return amountOut Output amount of the swap + function swapExactTokensForAVAX( + uint256 _amountIn, + uint256 _amountOutMinAVAX, + uint256[] memory _pairBinSteps, + IERC20[] memory _tokenPath, + address payable _to, + uint256 _deadline + ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { + if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { + revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); + } + + address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + + _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + + amountOut = _swapExactTokensForTokens(_amountIn, _pairs, _pairBinSteps, _tokenPath, address(this)); + + if (_amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMinAVAX, amountOut); + + wavax.withdraw(amountOut); + _safeTransferAVAX(_to, amountOut); + } + + /// @notice Swaps exact AVAX for tokens while performing safety checks + /// @param _amountOutMin The min amount of token to receive + /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param _to The address of the recipient + /// @param _deadline The deadline of the tx + /// @return amountOut Output amount of the swap + function swapExactAVAXForTokens( + uint256 _amountOutMin, + uint256[] memory _pairBinSteps, + IERC20[] memory _tokenPath, + address _to, + uint256 _deadline + ) external payable override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { + if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); + + address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + + _wavaxDepositAndTransfer(_pairs[0], msg.value); + + amountOut = _swapExactTokensForTokens(msg.value, _pairs, _pairBinSteps, _tokenPath, _to); + + if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); + } + + /// @notice Swaps tokens for exact tokens while performing safety checks + /// @param _amountOut The amount of token to receive + /// @param _amountInMax The max amount of token to send + /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param _to The address of the recipient + /// @param _deadline The deadline of the tx + /// @return amountsIn Input amounts for every step of the swap + function swapTokensForExactTokens( + uint256 _amountOut, + uint256 _amountInMax, + uint256[] memory _pairBinSteps, + IERC20[] memory _tokenPath, + address _to, + uint256 _deadline + ) + external + override + ensure(_deadline) + verifyInputs(_pairBinSteps, _tokenPath) + returns (uint256[] memory amountsIn) + { + address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountOut); + + if (amountsIn[0] > _amountInMax) revert LBRouter__MaxAmountInExceeded(_amountInMax, amountsIn[0]); + + _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], amountsIn[0]); + + uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, _to); + + if (_amountOutReal < _amountOut) revert LBRouter__InsufficientAmountOut(_amountOut, _amountOutReal); + } + + /// @notice Swaps tokens for exact AVAX while performing safety checks + /// @param _amountAVAXOut The amount of AVAX to receive + /// @param _amountInMax The max amount of token to send + /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param _to The address of the recipient + /// @param _deadline The deadline of the tx + /// @return amountsIn Input amounts for every step of the swap + function swapTokensForExactAVAX( + uint256 _amountAVAXOut, + uint256 _amountInMax, + uint256[] memory _pairBinSteps, + IERC20[] memory _tokenPath, + address payable _to, + uint256 _deadline + ) + external + override + ensure(_deadline) + verifyInputs(_pairBinSteps, _tokenPath) + returns (uint256[] memory amountsIn) + { + if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { + revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); + } + + address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountAVAXOut); + + if (amountsIn[0] > _amountInMax) revert LBRouter__MaxAmountInExceeded(_amountInMax, amountsIn[0]); + + _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], amountsIn[0]); + + uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, address(this)); + + if (_amountOutReal < _amountAVAXOut) revert LBRouter__InsufficientAmountOut(_amountAVAXOut, _amountOutReal); + + wavax.withdraw(_amountOutReal); + _safeTransferAVAX(_to, _amountOutReal); + } + + /// @notice Swaps AVAX for exact tokens while performing safety checks + /// @dev Will refund any AVAX amount sent in excess to `msg.sender` + /// @param _amountOut The amount of tokens to receive + /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param _to The address of the recipient + /// @param _deadline The deadline of the tx + /// @return amountsIn Input amounts for every step of the swap + function swapAVAXForExactTokens( + uint256 _amountOut, + uint256[] memory _pairBinSteps, + IERC20[] memory _tokenPath, + address _to, + uint256 _deadline + ) + external + payable + override + ensure(_deadline) + verifyInputs(_pairBinSteps, _tokenPath) + returns (uint256[] memory amountsIn) + { + if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); + + address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountOut); + + if (amountsIn[0] > msg.value) revert LBRouter__MaxAmountInExceeded(msg.value, amountsIn[0]); + + _wavaxDepositAndTransfer(_pairs[0], amountsIn[0]); + + uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, _to); + + if (_amountOutReal < _amountOut) revert LBRouter__InsufficientAmountOut(_amountOut, _amountOutReal); + + if (msg.value > amountsIn[0]) _safeTransferAVAX(msg.sender, msg.value - amountsIn[0]); + } + + /// @notice Swaps exact tokens for tokens while performing safety checks supporting for fee on transfer tokens + /// @param _amountIn The amount of token to send + /// @param _amountOutMin The min amount of token to receive + /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param _to The address of the recipient + /// @param _deadline The deadline of the tx + /// @return amountOut Output amount of the swap + function swapExactTokensForTokensSupportingFeeOnTransferTokens( + uint256 _amountIn, + uint256 _amountOutMin, + uint256[] memory _pairBinSteps, + IERC20[] memory _tokenPath, + address _to, + uint256 _deadline + ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { + address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + + IERC20 _targetToken = _tokenPath[_pairs.length]; + + uint256 _balanceBefore = _targetToken.balanceOf(_to); + + _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + + _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, _to); + + amountOut = _targetToken.balanceOf(_to) - _balanceBefore; + if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); + } + + /// @notice Swaps exact tokens for AVAX while performing safety checks supporting for fee on transfer tokens + /// @param _amountIn The amount of token to send + /// @param _amountOutMinAVAX The min amount of AVAX to receive + /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param _to The address of the recipient + /// @param _deadline The deadline of the tx + /// @return amountOut Output amount of the swap + function swapExactTokensForAVAXSupportingFeeOnTransferTokens( + uint256 _amountIn, + uint256 _amountOutMinAVAX, + uint256[] memory _pairBinSteps, + IERC20[] memory _tokenPath, + address payable _to, + uint256 _deadline + ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { + if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { + revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); + } + + address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + + uint256 _balanceBefore = wavax.balanceOf(address(this)); + + _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + + _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, address(this)); + + amountOut = wavax.balanceOf(address(this)) - _balanceBefore; + if (_amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMinAVAX, amountOut); + + wavax.withdraw(amountOut); + _safeTransferAVAX(_to, amountOut); + } + + /// @notice Swaps exact AVAX for tokens while performing safety checks supporting for fee on transfer tokens + /// @param _amountOutMin The min amount of token to receive + /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param _to The address of the recipient + /// @param _deadline The deadline of the tx + /// @return amountOut Output amount of the swap + function swapExactAVAXForTokensSupportingFeeOnTransferTokens( + uint256 _amountOutMin, + uint256[] memory _pairBinSteps, + IERC20[] memory _tokenPath, + address _to, + uint256 _deadline + ) external payable override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { + if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); + + address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + + IERC20 _targetToken = _tokenPath[_pairs.length]; + + uint256 _balanceBefore = _targetToken.balanceOf(_to); + + _wavaxDepositAndTransfer(_pairs[0], msg.value); + + _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, _to); + + amountOut = _targetToken.balanceOf(_to) - _balanceBefore; + if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); + } + + /// @notice Unstuck tokens that are sent to this contract by mistake + /// @dev Only callable by the factory owner + /// @param _token The address of the token + /// @param _to The address of the user to send back the tokens + /// @param _amount The amount to send + function sweep(IERC20 _token, address _to, uint256 _amount) external override onlyFactoryOwner { + if (address(_token) == address(0)) { + if (_amount == type(uint256).max) _amount = address(this).balance; + _safeTransferAVAX(_to, _amount); + } else { + if (_amount == type(uint256).max) _amount = _token.balanceOf(address(this)); + _token.safeTransfer(_to, _amount); + } + } + + /// @notice Unstuck LBTokens that are sent to this contract by mistake + /// @dev Only callable by the factory owner + /// @param _lbToken The address of the LBToken + /// @param _to The address of the user to send back the tokens + /// @param _ids The list of token ids + /// @param _amounts The list of amounts to send + function sweepLBToken(ILBToken _lbToken, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) + external + override + onlyFactoryOwner + { + _lbToken.safeBatchTransferFrom(address(this), _to, _ids, _amounts); + } + + /// @notice Helper function to add liquidity + /// @param _liq The liquidity parameter + /// @param _LBPair LBPair where liquidity is deposited + /// @return depositIds Bin ids where the liquidity was actually deposited + /// @return liquidityMinted Amounts of LBToken minted for each bin + function _addLiquidity(LiquidityParameters calldata _liq, ILBPair _LBPair) + private + ensure(_liq.deadline) + returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) + { + unchecked { + if (_liq.deltaIds.length != _liq.distributionX.length && _liq.deltaIds.length != _liq.distributionY.length) + { + revert LBRouter__LengthsMismatch(); + } + + if (_liq.activeIdDesired > type(uint24).max || _liq.idSlippage > type(uint24).max) { + revert LBRouter__IdDesiredOverflows(_liq.activeIdDesired, _liq.idSlippage); + } + + (,, uint256 _activeId) = _LBPair.getReservesAndId(); + if ( + _liq.activeIdDesired + _liq.idSlippage < _activeId || _activeId + _liq.idSlippage < _liq.activeIdDesired + ) revert LBRouter__IdSlippageCaught(_liq.activeIdDesired, _liq.idSlippage, _activeId); + + depositIds = new uint256[](_liq.deltaIds.length); + for (uint256 i; i < depositIds.length; ++i) { + int256 _id = int256(_activeId) + _liq.deltaIds[i]; + if (_id < 0 || uint256(_id) > type(uint24).max) revert LBRouter__IdOverflows(_id); + depositIds[i] = uint256(_id); + } + + uint256 _amountXAdded; + uint256 _amountYAdded; + + (_amountXAdded, _amountYAdded, liquidityMinted) = + _LBPair.mint(depositIds, _liq.distributionX, _liq.distributionY, _liq.to); + + if (_amountXAdded < _liq.amountXMin || _amountYAdded < _liq.amountYMin) { + revert LBRouter__AmountSlippageCaught(_liq.amountXMin, _amountXAdded, _liq.amountYMin, _amountYAdded); + } + } + } + + /// @notice Helper function to return the amounts in + /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param _pairs The list of pairs + /// @param _tokenPath The swap path + /// @param _amountOut The amount out + /// @return amountsIn The list of amounts in + function _getAmountsIn( + uint256[] memory _pairBinSteps, + address[] memory _pairs, + IERC20[] memory _tokenPath, + uint256 _amountOut + ) private view returns (uint256[] memory amountsIn) { + amountsIn = new uint256[](_tokenPath.length); + // Avoid doing -1, as `_pairs.length == _pairBinSteps.length-1` + amountsIn[_pairs.length] = _amountOut; + + for (uint256 i = _pairs.length; i != 0; i--) { + IERC20 _token = _tokenPath[i - 1]; + uint256 _binStep = _pairBinSteps[i - 1]; + + address _pair = _pairs[i - 1]; + + if (_binStep == 0) { + (uint256 _reserveIn, uint256 _reserveOut,) = IJoePair(_pair).getReserves(); + if (_token > _tokenPath[i]) { + (_reserveIn, _reserveOut) = (_reserveOut, _reserveIn); + } + + uint256 amountOut_ = amountsIn[i]; + amountsIn[i - 1] = amountOut_.getAmountIn(_reserveIn, _reserveOut); + } else { + (amountsIn[i - 1],) = getSwapIn(ILBPair(_pair), amountsIn[i], ILBPair(_pair).tokenX() == _token); + } + } + } + + /// @notice Helper function to remove liquidity + /// @param _LBPair The address of the LBPair + /// @param _amountXMin The min amount to receive of token X + /// @param _amountYMin The min amount to receive of token Y + /// @param _ids The list of ids to burn + /// @param _amounts The list of amounts to burn of each id in `_ids` + /// @param _to The address of the recipient + /// @return amountX The amount of token X sent by the pair + /// @return amountY The amount of token Y sent by the pair + function _removeLiquidity( + ILBPair _LBPair, + uint256 _amountXMin, + uint256 _amountYMin, + uint256[] memory _ids, + uint256[] memory _amounts, + address _to + ) private returns (uint256 amountX, uint256 amountY) { + ILBToken(address(_LBPair)).safeBatchTransferFrom(msg.sender, address(_LBPair), _ids, _amounts); + (amountX, amountY) = _LBPair.burn(_ids, _amounts, _to); + if (amountX < _amountXMin || amountY < _amountYMin) { + revert LBRouter__AmountSlippageCaught(_amountXMin, amountX, _amountYMin, amountY); + } + } + + /// @notice Helper function to swap exact tokens for tokens + /// @param _amountIn The amount of token sent + /// @param _pairs The list of pairs + /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param _to The address of the recipient + /// @return amountOut The amount of token sent to `_to` + function _swapExactTokensForTokens( + uint256 _amountIn, + address[] memory _pairs, + uint256[] memory _pairBinSteps, + IERC20[] memory _tokenPath, + address _to + ) private returns (uint256 amountOut) { + IERC20 _token; + uint256 _binStep; + address _recipient; + address _pair; + + IERC20 _tokenNext = _tokenPath[0]; + amountOut = _amountIn; + + unchecked { + for (uint256 i; i < _pairs.length; ++i) { + _pair = _pairs[i]; + _binStep = _pairBinSteps[i]; + + _token = _tokenNext; + _tokenNext = _tokenPath[i + 1]; + + _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; + + if (_binStep == 0) { + (uint256 _reserve0, uint256 _reserve1,) = IJoePair(_pair).getReserves(); + + if (_token < _tokenNext) { + amountOut = amountOut.getAmountOut(_reserve0, _reserve1); + IJoePair(_pair).swap(0, amountOut, _recipient, ""); + } else { + amountOut = amountOut.getAmountOut(_reserve1, _reserve0); + IJoePair(_pair).swap(amountOut, 0, _recipient, ""); + } + } else { + bool _swapForY = _tokenNext == ILBPair(_pair).tokenY(); + + (uint256 _amountXOut, uint256 _amountYOut) = ILBPair(_pair).swap(_swapForY, _recipient); + + if (_swapForY) amountOut = _amountYOut; + else amountOut = _amountXOut; + } + } + } + } + + /// @notice Helper function to swap tokens for exact tokens + /// @param _pairs The array of pairs + /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param _amountsIn The list of amounts in + /// @param _to The address of the recipient + /// @return amountOut The amount of token sent to `_to` + function _swapTokensForExactTokens( + address[] memory _pairs, + uint256[] memory _pairBinSteps, + IERC20[] memory _tokenPath, + uint256[] memory _amountsIn, + address _to + ) private returns (uint256 amountOut) { + IERC20 _token; + uint256 _binStep; + address _recipient; + address _pair; + + IERC20 _tokenNext = _tokenPath[0]; + + unchecked { + for (uint256 i; i < _pairs.length; ++i) { + _pair = _pairs[i]; + _binStep = _pairBinSteps[i]; + + _token = _tokenNext; + _tokenNext = _tokenPath[i + 1]; + + _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; + + if (_binStep == 0) { + amountOut = _amountsIn[i + 1]; + if (_token < _tokenNext) { + IJoePair(_pair).swap(0, amountOut, _recipient, ""); + } else { + IJoePair(_pair).swap(amountOut, 0, _recipient, ""); + } + } else { + bool _swapForY = _tokenNext == ILBPair(_pair).tokenY(); + + (uint256 _amountXOut, uint256 _amountYOut) = ILBPair(_pair).swap(_swapForY, _recipient); + + if (_swapForY) amountOut = _amountYOut; + else amountOut = _amountXOut; + } + } + } + } + + /// @notice Helper function to swap exact tokens supporting for fee on transfer tokens + /// @param _pairs The list of pairs + /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param _to The address of the recipient + function _swapSupportingFeeOnTransferTokens( + address[] memory _pairs, + uint256[] memory _pairBinSteps, + IERC20[] memory _tokenPath, + address _to + ) private { + IERC20 _token; + uint256 _binStep; + address _recipient; + address _pair; + + IERC20 _tokenNext = _tokenPath[0]; + + unchecked { + for (uint256 i; i < _pairs.length; ++i) { + _pair = _pairs[i]; + _binStep = _pairBinSteps[i]; + + _token = _tokenNext; + _tokenNext = _tokenPath[i + 1]; + + _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; + + if (_binStep == 0) { + (uint256 _reserve0, uint256 _reserve1,) = IJoePair(_pair).getReserves(); + if (_token < _tokenNext) { + uint256 _amountIn = _token.balanceOf(_pair) - _reserve0; + uint256 _amountOut = _amountIn.getAmountOut(_reserve0, _reserve1); + + IJoePair(_pair).swap(0, _amountOut, _recipient, ""); + } else { + uint256 _amountIn = _token.balanceOf(_pair) - _reserve1; + uint256 _amountOut = _amountIn.getAmountOut(_reserve1, _reserve0); + + IJoePair(_pair).swap(_amountOut, 0, _recipient, ""); + } + } else { + ILBPair(_pair).swap(_tokenNext == ILBPair(_pair).tokenY(), _recipient); + } + } + } + } + + /// @notice Helper function to return the address of the LBPair + /// @dev Revert if the pair is not created yet + /// @param _tokenX The address of the tokenX + /// @param _tokenY The address of the tokenY + /// @param _binStep The bin step of the LBPair + /// @return The address of the LBPair + function _getLBPairInformation(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep) private view returns (ILBPair) { + ILBPair _LBPair = factory.getLBPairInformation(_tokenX, _tokenY, _binStep).LBPair; + if (address(_LBPair) == address(0)) { + revert LBRouter__PairNotCreated(address(_tokenX), address(_tokenY), _binStep); + } + return _LBPair; + } + + /// @notice Helper function to return the address of the pair (v1 or v2, according to `_binStep`) + /// @dev Revert if the pair is not created yet + /// @param _binStep The bin step of the LBPair, 0 means using V1 pair, any other value will use V2 + /// @param _tokenX The address of the tokenX + /// @param _tokenY The address of the tokenY + /// @return _pair The address of the pair of binStep `_binStep` + function _getPair(uint256 _binStep, IERC20 _tokenX, IERC20 _tokenY) private view returns (address _pair) { + if (_binStep == 0) { + _pair = oldFactory.getPair(address(_tokenX), address(_tokenY)); + if (_pair == address(0)) revert LBRouter__PairNotCreated(address(_tokenX), address(_tokenY), _binStep); + } else { + _pair = address(_getLBPairInformation(_tokenX, _tokenY, _binStep)); + } + } + + function _getPairs(uint256[] memory _pairBinSteps, IERC20[] memory _tokenPath) + private + view + returns (address[] memory pairs) + { + pairs = new address[](_pairBinSteps.length); + + IERC20 _token; + IERC20 _tokenNext = _tokenPath[0]; + unchecked { + for (uint256 i; i < pairs.length; ++i) { + _token = _tokenNext; + _tokenNext = _tokenPath[i + 1]; + + pairs[i] = _getPair(_pairBinSteps[i], _token, _tokenNext); + } + } + } + + /// @notice Helper function to transfer AVAX + /// @param _to The address of the recipient + /// @param _amount The AVAX amount to send + function _safeTransferAVAX(address _to, uint256 _amount) private { + (bool success,) = _to.call{value: _amount}(""); + if (!success) revert LBRouter__FailedToSendAVAX(_to, _amount); + } + + /// @notice Helper function to deposit and transfer wavax + /// @param _to The address of the recipient + /// @param _amount The AVAX amount to wrap + function _wavaxDepositAndTransfer(address _to, uint256 _amount) private { + wavax.deposit{value: _amount}(); + wavax.safeTransfer(_to, _amount); + } +} diff --git a/src/interfaces/ILBFactory.sol b/src/interfaces/ILBFactory.sol index 36b870ab..ef54c033 100644 --- a/src/interfaces/ILBFactory.sol +++ b/src/interfaces/ILBFactory.sol @@ -11,163 +11,159 @@ import "./IPendingOwnable.sol"; /// @author Trader Joe /// @notice Required interface of LBFactory contract interface ILBFactory is IPendingOwnable { - function getProtocolFeeRecipient() external view returns (address); + /// @dev Structure to store the LBPair information, such as: + /// - binStep: The bin step of the LBPair + /// - LBPair: The address of the LBPair + /// - createdByOwner: Whether the pair was created by the owner of the factory + /// - ignoredForRouting: Whether the pair is ignored for routing or not. An ignored pair will not be explored during routes finding + struct LBPairInformation { + uint16 binStep; + ILBPair LBPair; + bool createdByOwner; + bool ignoredForRouting; + } - function getFlashLoanFee() external view returns (uint128); + event LBPairCreated( + IERC20 indexed tokenX, IERC20 indexed tokenY, uint256 indexed binStep, ILBPair LBPair, uint256 pid + ); - // /// @dev Structure to store the LBPair information, such as: - // /// - binStep: The bin step of the LBPair - // /// - LBPair: The address of the LBPair - // /// - createdByOwner: Whether the pair was created by the owner of the factory - // /// - ignoredForRouting: Whether the pair is ignored for routing or not. An ignored pair will not be explored during routes finding - // struct LBPairInformation { - // uint16 binStep; - // ILBPair LBPair; - // bool createdByOwner; - // bool ignoredForRouting; - // } + event FeeRecipientSet(address oldRecipient, address newRecipient); - // event LBPairCreated( - // IERC20 indexed tokenX, IERC20 indexed tokenY, uint256 indexed binStep, ILBPair LBPair, uint256 pid - // ); + event FlashLoanFeeSet(uint256 oldFlashLoanFee, uint256 newFlashLoanFee); - // event FeeRecipientSet(address oldRecipient, address newRecipient); + event FeeParametersSet( + address indexed sender, + ILBPair indexed LBPair, + uint256 binStep, + uint256 baseFactor, + uint256 filterPeriod, + uint256 decayPeriod, + uint256 reductionFactor, + uint256 variableFeeControl, + uint256 protocolShare, + uint256 maxVolatilityAccumulated + ); - // event FlashLoanFeeSet(uint256 oldFlashLoanFee, uint256 newFlashLoanFee); + event FactoryLockedStatusUpdated(bool unlocked); - // event FeeParametersSet( - // address indexed sender, - // ILBPair indexed LBPair, - // uint256 binStep, - // uint256 baseFactor, - // uint256 filterPeriod, - // uint256 decayPeriod, - // uint256 reductionFactor, - // uint256 variableFeeControl, - // uint256 protocolShare, - // uint256 maxVolatilityAccumulated - // ); + event LBPairImplementationSet(address oldLBPairImplementation, address LBPairImplementation); - // event FactoryLockedStatusUpdated(bool unlocked); + event LBPairIgnoredStateChanged(ILBPair indexed LBPair, bool ignored); - // event LBPairImplementationSet(address oldLBPairImplementation, address LBPairImplementation); + event PresetSet( + uint256 indexed binStep, + uint256 baseFactor, + uint256 filterPeriod, + uint256 decayPeriod, + uint256 reductionFactor, + uint256 variableFeeControl, + uint256 protocolShare, + uint256 maxVolatilityAccumulated, + uint256 sampleLifetime + ); - // event LBPairIgnoredStateChanged(ILBPair indexed LBPair, bool ignored); + event PresetRemoved(uint256 indexed binStep); - // event PresetSet( - // uint256 indexed binStep, - // uint256 baseFactor, - // uint256 filterPeriod, - // uint256 decayPeriod, - // uint256 reductionFactor, - // uint256 variableFeeControl, - // uint256 protocolShare, - // uint256 maxVolatilityAccumulated, - // uint256 sampleLifetime - // ); + event QuoteAssetAdded(IERC20 indexed quoteAsset); - // event PresetRemoved(uint256 indexed binStep); + event QuoteAssetRemoved(IERC20 indexed quoteAsset); - // event QuoteAssetAdded(IERC20 indexed quoteAsset); + function MAX_FEE() external pure returns (uint256); - // event QuoteAssetRemoved(IERC20 indexed quoteAsset); + function MIN_BIN_STEP() external pure returns (uint256); - // function MAX_FEE() external pure returns (uint256); + function MAX_BIN_STEP() external pure returns (uint256); - // function MIN_BIN_STEP() external pure returns (uint256); + function MAX_PROTOCOL_SHARE() external pure returns (uint256); - // function MAX_BIN_STEP() external pure returns (uint256); + function LBPairImplementation() external view returns (address); - // function MAX_PROTOCOL_SHARE() external pure returns (uint256); + function getNumberOfQuoteAssets() external view returns (uint256); - // function LBPairImplementation() external view returns (address); + function getQuoteAsset(uint256 index) external view returns (IERC20); - // function getNumberOfQuoteAssets() external view returns (uint256); + function isQuoteAsset(IERC20 token) external view returns (bool); - // function getQuoteAsset(uint256 index) external view returns (IERC20); + function feeRecipient() external view returns (address); - // function isQuoteAsset(IERC20 token) external view returns (bool); + function flashLoanFee() external view returns (uint256); - // function feeRecipient() external view returns (address); + function creationUnlocked() external view returns (bool); - // function flashLoanFee() external view returns (uint256); + function allLBPairs(uint256 id) external returns (ILBPair); - // function creationUnlocked() external view returns (bool); + function getNumberOfLBPairs() external view returns (uint256); - // function allLBPairs(uint256 id) external returns (ILBPair); + function getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep) + external + view + returns (LBPairInformation memory); - // function getNumberOfLBPairs() external view returns (uint256); + function getPreset(uint16 binStep) + external + view + returns ( + uint256 baseFactor, + uint256 filterPeriod, + uint256 decayPeriod, + uint256 reductionFactor, + uint256 variableFeeControl, + uint256 protocolShare, + uint256 maxAccumulator, + uint256 sampleLifetime + ); - // function getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep) - // external - // view - // returns (LBPairInformation memory); + function getAllBinSteps() external view returns (uint256[] memory presetsBinStep); - // function getPreset(uint16 binStep) - // external - // view - // returns ( - // uint256 baseFactor, - // uint256 filterPeriod, - // uint256 decayPeriod, - // uint256 reductionFactor, - // uint256 variableFeeControl, - // uint256 protocolShare, - // uint256 maxAccumulator, - // uint256 sampleLifetime - // ); + function getAllLBPairs(IERC20 tokenX, IERC20 tokenY) + external + view + returns (LBPairInformation[] memory LBPairsBinStep); - // function getAllBinSteps() external view returns (uint256[] memory presetsBinStep); + function setLBPairImplementation(address LBPairImplementation) external; - // function getAllLBPairs(IERC20 tokenX, IERC20 tokenY) - // external - // view - // returns (LBPairInformation[] memory LBPairsBinStep); + function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) + external + returns (ILBPair pair); - // function setLBPairImplementation(address LBPairImplementation) external; + function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) external; - // function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) - // external - // returns (ILBPair pair); + function setPreset( + uint16 binStep, + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated, + uint16 sampleLifetime + ) external; - // function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) external; + function removePreset(uint16 binStep) external; - // function setPreset( - // uint16 binStep, - // uint16 baseFactor, - // uint16 filterPeriod, - // uint16 decayPeriod, - // uint16 reductionFactor, - // uint24 variableFeeControl, - // uint16 protocolShare, - // uint24 maxVolatilityAccumulated, - // uint16 sampleLifetime - // ) external; + function setFeesParametersOnPair( + IERC20 tokenX, + IERC20 tokenY, + uint16 binStep, + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated + ) external; - // function removePreset(uint16 binStep) external; + function setFeeRecipient(address feeRecipient) external; - // function setFeesParametersOnPair( - // IERC20 tokenX, - // IERC20 tokenY, - // uint16 binStep, - // uint16 baseFactor, - // uint16 filterPeriod, - // uint16 decayPeriod, - // uint16 reductionFactor, - // uint24 variableFeeControl, - // uint16 protocolShare, - // uint24 maxVolatilityAccumulated - // ) external; + function setFlashLoanFee(uint256 flashLoanFee) external; - // function setFeeRecipient(address feeRecipient) external; + function setFactoryLockedState(bool locked) external; - // function setFlashLoanFee(uint256 flashLoanFee) external; + function addQuoteAsset(IERC20 quoteAsset) external; - // function setFactoryLockedState(bool locked) external; + function removeQuoteAsset(IERC20 quoteAsset) external; - // function addQuoteAsset(IERC20 quoteAsset) external; - - // function removeQuoteAsset(IERC20 quoteAsset) external; - - // function forceDecay(ILBPair LBPair) external; + function forceDecay(ILBPair LBPair) external; } diff --git a/src/interfaces/ILBRouter.sol b/src/interfaces/ILBRouter.sol index 95f03021..b6a4c833 100644 --- a/src/interfaces/ILBRouter.sol +++ b/src/interfaces/ILBRouter.sol @@ -2,183 +2,183 @@ pragma solidity 0.8.10; -// import "./IJoeFactory.sol"; -// import "./ILBPair.sol"; -// import "./ILBToken.sol"; -// import "./IWAVAX.sol"; - -// /// @title Liquidity Book Router Interface -// /// @author Trader Joe -// /// @notice Required interface of LBRouter contract -// interface ILBRouter { -// /// @dev The liquidity parameters, such as: -// /// - tokenX: The address of token X -// /// - tokenY: The address of token Y -// /// - binStep: The bin step of the pair -// /// - amountX: The amount to send of token X -// /// - amountY: The amount to send of token Y -// /// - amountXMin: The min amount of token X added to liquidity -// /// - amountYMin: The min amount of token Y added to liquidity -// /// - activeIdDesired: The active id that user wants to add liquidity from -// /// - idSlippage: The number of id that are allowed to slip -// /// - deltaIds: The list of delta ids to add liquidity (`deltaId = activeId - desiredId`) -// /// - distributionX: The distribution of tokenX with sum(distributionX) = 100e18 (100%) or 0 (0%) -// /// - distributionY: The distribution of tokenY with sum(distributionY) = 100e18 (100%) or 0 (0%) -// /// - to: The address of the recipient -// /// - deadline: The deadline of the tx -// struct LiquidityParameters { -// IERC20 tokenX; -// IERC20 tokenY; -// uint256 binStep; -// uint256 amountX; -// uint256 amountY; -// uint256 amountXMin; -// uint256 amountYMin; -// uint256 activeIdDesired; -// uint256 idSlippage; -// int256[] deltaIds; -// uint256[] distributionX; -// uint256[] distributionY; -// address to; -// uint256 deadline; -// } - -// function factory() external view returns (ILBFactory); - -// function oldFactory() external view returns (IJoeFactory); - -// function wavax() external view returns (IWAVAX); - -// function getIdFromPrice(ILBPair LBPair, uint256 price) external view returns (uint24); - -// function getPriceFromId(ILBPair LBPair, uint24 id) external view returns (uint256); - -// function getSwapIn(ILBPair LBPair, uint256 amountOut, bool swapForY) -// external -// view -// returns (uint256 amountIn, uint256 feesIn); - -// function getSwapOut(ILBPair LBPair, uint256 amountIn, bool swapForY) -// external -// view -// returns (uint256 amountOut, uint256 feesIn); - -// function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) -// external -// returns (ILBPair pair); - -// function addLiquidity(LiquidityParameters calldata liquidityParameters) -// external -// returns (uint256[] memory depositIds, uint256[] memory liquidityMinted); - -// function addLiquidityAVAX(LiquidityParameters calldata liquidityParameters) -// external -// payable -// returns (uint256[] memory depositIds, uint256[] memory liquidityMinted); - -// function removeLiquidity( -// IERC20 tokenX, -// IERC20 tokenY, -// uint16 binStep, -// uint256 amountXMin, -// uint256 amountYMin, -// uint256[] memory ids, -// uint256[] memory amounts, -// address to, -// uint256 deadline -// ) external returns (uint256 amountX, uint256 amountY); - -// function removeLiquidityAVAX( -// IERC20 token, -// uint16 binStep, -// uint256 amountTokenMin, -// uint256 amountAVAXMin, -// uint256[] memory ids, -// uint256[] memory amounts, -// address payable to, -// uint256 deadline -// ) external returns (uint256 amountToken, uint256 amountAVAX); - -// function swapExactTokensForTokens( -// uint256 amountIn, -// uint256 amountOutMin, -// uint256[] memory pairBinSteps, -// IERC20[] memory tokenPath, -// address to, -// uint256 deadline -// ) external returns (uint256 amountOut); - -// function swapExactTokensForAVAX( -// uint256 amountIn, -// uint256 amountOutMinAVAX, -// uint256[] memory pairBinSteps, -// IERC20[] memory tokenPath, -// address payable to, -// uint256 deadline -// ) external returns (uint256 amountOut); - -// function swapExactAVAXForTokens( -// uint256 amountOutMin, -// uint256[] memory pairBinSteps, -// IERC20[] memory tokenPath, -// address to, -// uint256 deadline -// ) external payable returns (uint256 amountOut); - -// function swapTokensForExactTokens( -// uint256 amountOut, -// uint256 amountInMax, -// uint256[] memory pairBinSteps, -// IERC20[] memory tokenPath, -// address to, -// uint256 deadline -// ) external returns (uint256[] memory amountsIn); - -// function swapTokensForExactAVAX( -// uint256 amountOut, -// uint256 amountInMax, -// uint256[] memory pairBinSteps, -// IERC20[] memory tokenPath, -// address payable to, -// uint256 deadline -// ) external returns (uint256[] memory amountsIn); - -// function swapAVAXForExactTokens( -// uint256 amountOut, -// uint256[] memory pairBinSteps, -// IERC20[] memory tokenPath, -// address to, -// uint256 deadline -// ) external payable returns (uint256[] memory amountsIn); - -// function swapExactTokensForTokensSupportingFeeOnTransferTokens( -// uint256 amountIn, -// uint256 amountOutMin, -// uint256[] memory pairBinSteps, -// IERC20[] memory tokenPath, -// address to, -// uint256 deadline -// ) external returns (uint256 amountOut); - -// function swapExactTokensForAVAXSupportingFeeOnTransferTokens( -// uint256 amountIn, -// uint256 amountOutMinAVAX, -// uint256[] memory pairBinSteps, -// IERC20[] memory tokenPath, -// address payable to, -// uint256 deadline -// ) external returns (uint256 amountOut); - -// function swapExactAVAXForTokensSupportingFeeOnTransferTokens( -// uint256 amountOutMin, -// uint256[] memory pairBinSteps, -// IERC20[] memory tokenPath, -// address to, -// uint256 deadline -// ) external payable returns (uint256 amountOut); - -// function sweep(IERC20 token, address to, uint256 amount) external; - -// function sweepLBToken(ILBToken _lbToken, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) -// external; -// } +import "./IJoeFactory.sol"; +import "./ILBPair.sol"; +import "./ILBToken.sol"; +import "./IWAVAX.sol"; + +/// @title Liquidity Book Router Interface +/// @author Trader Joe +/// @notice Required interface of LBRouter contract +interface ILBRouter { + /// @dev The liquidity parameters, such as: + /// - tokenX: The address of token X + /// - tokenY: The address of token Y + /// - binStep: The bin step of the pair + /// - amountX: The amount to send of token X + /// - amountY: The amount to send of token Y + /// - amountXMin: The min amount of token X added to liquidity + /// - amountYMin: The min amount of token Y added to liquidity + /// - activeIdDesired: The active id that user wants to add liquidity from + /// - idSlippage: The number of id that are allowed to slip + /// - deltaIds: The list of delta ids to add liquidity (`deltaId = activeId - desiredId`) + /// - distributionX: The distribution of tokenX with sum(distributionX) = 100e18 (100%) or 0 (0%) + /// - distributionY: The distribution of tokenY with sum(distributionY) = 100e18 (100%) or 0 (0%) + /// - to: The address of the recipient + /// - deadline: The deadline of the tx + struct LiquidityParameters { + IERC20 tokenX; + IERC20 tokenY; + uint256 binStep; + uint256 amountX; + uint256 amountY; + uint256 amountXMin; + uint256 amountYMin; + uint256 activeIdDesired; + uint256 idSlippage; + int256[] deltaIds; + uint256[] distributionX; + uint256[] distributionY; + address to; + uint256 deadline; + } + + function factory() external view returns (ILBFactory); + + function oldFactory() external view returns (IJoeFactory); + + function wavax() external view returns (IWAVAX); + + function getIdFromPrice(ILBPair LBPair, uint256 price) external view returns (uint24); + + function getPriceFromId(ILBPair LBPair, uint24 id) external view returns (uint256); + + function getSwapIn(ILBPair LBPair, uint256 amountOut, bool swapForY) + external + view + returns (uint256 amountIn, uint256 feesIn); + + function getSwapOut(ILBPair LBPair, uint256 amountIn, bool swapForY) + external + view + returns (uint256 amountOut, uint256 feesIn); + + function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) + external + returns (ILBPair pair); + + function addLiquidity(LiquidityParameters calldata liquidityParameters) + external + returns (uint256[] memory depositIds, uint256[] memory liquidityMinted); + + function addLiquidityAVAX(LiquidityParameters calldata liquidityParameters) + external + payable + returns (uint256[] memory depositIds, uint256[] memory liquidityMinted); + + function removeLiquidity( + IERC20 tokenX, + IERC20 tokenY, + uint16 binStep, + uint256 amountXMin, + uint256 amountYMin, + uint256[] memory ids, + uint256[] memory amounts, + address to, + uint256 deadline + ) external returns (uint256 amountX, uint256 amountY); + + function removeLiquidityAVAX( + IERC20 token, + uint16 binStep, + uint256 amountTokenMin, + uint256 amountAVAXMin, + uint256[] memory ids, + uint256[] memory amounts, + address payable to, + uint256 deadline + ) external returns (uint256 amountToken, uint256 amountAVAX); + + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address to, + uint256 deadline + ) external returns (uint256 amountOut); + + function swapExactTokensForAVAX( + uint256 amountIn, + uint256 amountOutMinAVAX, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address payable to, + uint256 deadline + ) external returns (uint256 amountOut); + + function swapExactAVAXForTokens( + uint256 amountOutMin, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address to, + uint256 deadline + ) external payable returns (uint256 amountOut); + + function swapTokensForExactTokens( + uint256 amountOut, + uint256 amountInMax, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address to, + uint256 deadline + ) external returns (uint256[] memory amountsIn); + + function swapTokensForExactAVAX( + uint256 amountOut, + uint256 amountInMax, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address payable to, + uint256 deadline + ) external returns (uint256[] memory amountsIn); + + function swapAVAXForExactTokens( + uint256 amountOut, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address to, + uint256 deadline + ) external payable returns (uint256[] memory amountsIn); + + function swapExactTokensForTokensSupportingFeeOnTransferTokens( + uint256 amountIn, + uint256 amountOutMin, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address to, + uint256 deadline + ) external returns (uint256 amountOut); + + function swapExactTokensForAVAXSupportingFeeOnTransferTokens( + uint256 amountIn, + uint256 amountOutMinAVAX, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address payable to, + uint256 deadline + ) external returns (uint256 amountOut); + + function swapExactAVAXForTokensSupportingFeeOnTransferTokens( + uint256 amountOutMin, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address to, + uint256 deadline + ) external payable returns (uint256 amountOut); + + function sweep(IERC20 token, address to, uint256 amount) external; + + function sweepLBToken(ILBToken _lbToken, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) + external; +} diff --git a/test/BinHelper.T.sol b/test/BinHelper.T.sol index ea6fc5f1..45c4aa96 100644 --- a/test/BinHelper.T.sol +++ b/test/BinHelper.T.sol @@ -2,20 +2,20 @@ pragma solidity 0.8.10; -// import "./helpers/TestHelper.sol"; +import "./helpers/TestHelper.sol"; -// contract BinHelperTest is TestHelper { -// function testInversePriceForOppositeBins() public { -// assertApproxEqAbs( -// (getPriceFromId(ID_ONE + 10) * getPriceFromId(ID_ONE - 10)) / Constants.SCALE, Constants.SCALE, 1 -// ); +contract BinHelperTest is TestHelper { + function testInversePriceForOppositeBins() public { + assertApproxEqAbs( + (getPriceFromId(ID_ONE + 10) * getPriceFromId(ID_ONE - 10)) / Constants.SCALE, Constants.SCALE, 1 + ); -// assertApproxEqAbs( -// (getPriceFromId(ID_ONE + 1_000) * getPriceFromId(ID_ONE - 1_000)) / Constants.SCALE, Constants.SCALE, 1 -// ); + assertApproxEqAbs( + (getPriceFromId(ID_ONE + 1_000) * getPriceFromId(ID_ONE - 1_000)) / Constants.SCALE, Constants.SCALE, 1 + ); -// assertApproxEqAbs( -// (getPriceFromId(ID_ONE + 10_000) * getPriceFromId(ID_ONE - 10_000)) / Constants.SCALE, Constants.SCALE, 1 -// ); -// } -// } + assertApproxEqAbs( + (getPriceFromId(ID_ONE + 10_000) * getPriceFromId(ID_ONE - 10_000)) / Constants.SCALE, Constants.SCALE, 1 + ); + } +} diff --git a/test/helpers/TestHelper.sol b/test/helpers/TestHelper.sol index 2cc65b25..408e1242 100644 --- a/test/helpers/TestHelper.sol +++ b/test/helpers/TestHelper.sol @@ -2,278 +2,278 @@ pragma solidity 0.8.10; -// import "forge-std/Test.sol"; - -// import "src/LBFactory.sol"; -// import "src/LBPair.sol"; -// import "src/LBRouter.sol"; -// import "src/LBQuoter.sol"; -// import "src/interfaces/ILBRouter.sol"; -// import "src/interfaces/IJoeRouter02.sol"; -// import "src/LBToken.sol"; -// import "src/libraries/math/Uint256x256Math.sol"; -// import "src/libraries/Constants.sol"; - -// import "test/mocks/WAVAX.sol"; -// import "test/mocks/ERC20.sol"; -// import "test/mocks/FlashloanBorrower.sol"; -// import "test/mocks/ERC20TransferTax.sol"; - -// abstract contract TestHelper is Test, IERC165 { -// using Uint256x256Math for uint256; - -// uint24 internal constant ID_ONE = 2 ** 23; -// uint256 internal constant BASIS_POINT_MAX = 10_000; - -// // Avalanche market config for 10bps -// uint16 internal constant DEFAULT_BIN_STEP = 10; -// uint16 internal constant DEFAULT_BASE_FACTOR = 1000; -// uint16 internal constant DEFAULT_FILTER_PERIOD = 30; -// uint16 internal constant DEFAULT_DECAY_PERIOD = 600; -// uint16 internal constant DEFAULT_REDUCTION_FACTOR = 5_000; -// uint24 internal constant DEFAULT_VARIABLE_FEE_CONTROL = 40_000; -// uint16 internal constant DEFAULT_PROTOCOL_SHARE = 1_000; -// uint24 internal constant DEFAULT_MAX_VOLATILITY_ACCUMULATED = 350_000; -// uint16 internal constant DEFAULT_SAMPLE_LIFETIME = 120; -// uint256 internal constant DEFAULT_FLASHLOAN_FEE = 8e14; - -// address payable immutable DEV = payable(address(this)); -// address payable immutable ALICE = payable(makeAddr("alice")); -// address payable immutable BOB = payable(makeAddr("bob")); - -// // Wrapped Native -// WAVAX internal wavax; - -// // 6 decimals -// ERC20Mock internal usdc; -// ERC20Mock internal usdt; - -// // 8 decimals -// ERC20Mock internal wbtc; - -// // 18 decimals -// ERC20Mock internal link; -// ERC20Mock internal bnb; -// ERC20Mock internal weth; - -// // Tax tokens (18 decimals) -// ERC20TransferTaxMock internal taxToken; - -// LBFactory internal factory; -// LBRouter internal router; -// LBPair internal pair; -// LBPair internal pairWavax; -// LBQuoter internal quoter; - -// function setUp() public virtual { -// // Create mocks -// wavax = new WAVAX(); -// usdc = new ERC20Mock(6); -// usdt = new ERC20Mock(6); -// wbtc = new ERC20Mock(8); -// weth = new ERC20Mock(18); -// link = new ERC20Mock(18); -// bnb = new ERC20Mock(18); -// taxToken = new ERC20TransferTaxMock(); - -// // Label mocks -// vm.label(address(wavax), "wavax"); -// vm.label(address(usdc), "usdc"); -// vm.label(address(usdt), "usdt"); -// vm.label(address(wbtc), "wbtc"); -// vm.label(address(weth), "weth"); -// vm.label(address(link), "link"); -// vm.label(address(bnb), "bnb"); -// vm.label(address(taxToken), "taxToken"); - -// // Create factory -// factory = new LBFactory(DEV, DEFAULT_FLASHLOAN_FEE); -// ILBPair LBPairImplementation = new LBPair(factory); - -// // Setup factory -// factory.setLBPairImplementation(address(LBPairImplementation)); -// addAllAssetsToQuoteWhitelist(); -// setDefaultFactoryPresets(DEFAULT_BIN_STEP); - -// // Create router -// router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(0))); - -// // Label deployed contracts -// vm.label(address(factory), "factory"); -// vm.label(address(router), "router"); -// vm.label(address(LBPairImplementation), "LBPairImplementation"); -// } - -// function supportsInterface(bytes4 interfaceId) external view virtual returns (bool) { -// return interfaceId == type(ILBToken).interfaceId; -// } - -// function getPriceFromId(uint24 id) internal pure returns (uint256 price) { -// price = BinHelper.getPriceFromId(id, DEFAULT_BIN_STEP); -// } - -// function getIdFromPrice(uint256 price) internal pure returns (uint24 id) { -// id = BinHelper.getIdFromPrice(price, DEFAULT_BIN_STEP); -// } - -// function createLBPair(IERC20 tokenX, IERC20 tokenY) internal returns (LBPair newPair) { -// newPair = createLBPairFromStartId(tokenX, tokenY, ID_ONE); -// } - -// function setDefaultFactoryPresets(uint16 binStep) internal { -// factory.setPreset( -// binStep, -// DEFAULT_BASE_FACTOR, -// DEFAULT_FILTER_PERIOD, -// DEFAULT_DECAY_PERIOD, -// DEFAULT_REDUCTION_FACTOR, -// DEFAULT_VARIABLE_FEE_CONTROL, -// DEFAULT_PROTOCOL_SHARE, -// DEFAULT_MAX_VOLATILITY_ACCUMULATED, -// DEFAULT_SAMPLE_LIFETIME -// ); -// } - -// function createLBPairFromStartId(IERC20 tokenX, IERC20 tokenY, uint24 startId) internal returns (LBPair newPair) { -// newPair = createLBPairFromStartIdAndBinStep(tokenX, tokenY, startId, DEFAULT_BIN_STEP); -// } - -// function createLBPairFromStartIdAndBinStep(IERC20 tokenX, IERC20 tokenY, uint24 startId, uint16 binStep) -// internal -// returns (LBPair newPair) -// { -// newPair = LBPair(address(factory.createLBPair(tokenX, tokenY, startId, binStep))); -// } - -// function convertRelativeIdsToAbsolute(int256[] memory relativeIds, uint24 startId) -// internal -// pure -// returns (uint256[] memory absoluteIds) -// { -// absoluteIds = new uint256[](relativeIds.length); -// for (uint256 i = 0; i < relativeIds.length; i++) { -// int256 id = int256(uint256(startId)) + relativeIds[i]; -// require(id >= 0, "Id conversion: id must be positive"); -// absoluteIds[i] = uint256(id); -// } -// } - -// function convertAbsoluteIdsToRelative(uint256[] memory absoluteIds, uint24 startId) -// internal -// pure -// returns (int256[] memory relativeIds) -// { -// relativeIds = new int256[](absoluteIds.length); -// for (uint256 i = 0; i < absoluteIds.length; i++) { -// relativeIds[i] = int256(absoluteIds[i]) - int256(uint256(startId)); -// } -// } - -// function addLiquidityAndReturnAbsoluteIds( -// ERC20Mock tokenX, -// ERC20Mock tokenY, -// uint256 amountYIn, -// uint24 startId, -// uint24 numberBins, -// uint24 gap -// ) -// internal -// returns ( -// uint256[] memory ids, -// uint256[] memory distributionX, -// uint256[] memory distributionY, -// uint256 amountXIn -// ) -// { -// (ids, distributionX, distributionY, amountXIn) = -// addLiquidity(tokenX, tokenY, amountYIn, startId, numberBins, gap); -// } - -// function addLiquidityAndReturnRelativeIds( -// ERC20Mock tokenX, -// ERC20Mock tokenY, -// uint256 amountYIn, -// uint24 startId, -// uint24 numberBins, -// uint24 gap -// ) -// internal -// returns (int256[] memory ids, uint256[] memory distributionX, uint256[] memory distributionY, uint256 amountXIn) -// { -// uint256[] memory absoluteIds; -// (absoluteIds, distributionX, distributionY, amountXIn) = -// addLiquidity(tokenX, tokenY, amountYIn, startId, numberBins, gap); -// ids = convertAbsoluteIdsToRelative(absoluteIds, startId); -// } - -// function addLiquidity( -// ERC20Mock tokenX, -// ERC20Mock tokenY, -// uint256 amountYIn, -// uint24 startId, -// uint24 numberBins, -// uint24 gap -// ) -// internal -// returns ( -// uint256[] memory ids, -// uint256[] memory distributionX, -// uint256[] memory distributionY, -// uint256 amountXIn -// ) -// { -// (ids, distributionX, distributionY, amountXIn) = spreadLiquidity(amountYIn, startId, numberBins, gap); - -// tokenX.mint(address(pair), amountXIn); -// tokenY.mint(address(pair), amountYIn); - -// pair.mint(ids, distributionX, distributionY, DEV); -// } - -// function spreadLiquidity(uint256 amountYIn, uint24 startId, uint24 numberBins, uint24 gap) -// internal -// pure -// returns ( -// uint256[] memory ids, -// uint256[] memory distributionX, -// uint256[] memory distributionY, -// uint256 amountXIn -// ) -// { -// if (numberBins % 2 == 0) { -// revert("Pls put an uneven number of bins"); -// } - -// uint24 spread = numberBins / 2; -// ids = new uint256[](numberBins); - -// distributionX = new uint256[](numberBins); -// distributionY = new uint256[](numberBins); -// uint256 binDistribution = Constants.PRECISION / (spread + 1); -// uint256 binLiquidity = amountYIn / (spread + 1); - -// for (uint256 i; i < numberBins; i++) { -// ids[i] = startId - spread * (1 + gap) + i * (1 + gap); - -// if (i <= spread) { -// distributionY[i] = binDistribution; -// } -// if (i >= spread) { -// distributionX[i] = binDistribution; -// amountXIn += -// binLiquidity > 0 ? (binLiquidity * Constants.SCALE - 1) / getPriceFromId(uint24(ids[i])) + 1 : 0; -// } -// } -// } - -// function addAllAssetsToQuoteWhitelist() internal { -// if (address(wavax) != address(0)) factory.addQuoteAsset(wavax); -// if (address(usdc) != address(0)) factory.addQuoteAsset(usdc); -// if (address(usdt) != address(0)) factory.addQuoteAsset(usdt); -// if (address(wbtc) != address(0)) factory.addQuoteAsset(wbtc); -// if (address(weth) != address(0)) factory.addQuoteAsset(weth); -// if (address(link) != address(0)) factory.addQuoteAsset(link); -// if (address(bnb) != address(0)) factory.addQuoteAsset(bnb); -// if (address(taxToken) != address(0)) factory.addQuoteAsset(taxToken); -// } -// } +import "forge-std/Test.sol"; + +import "src/LBFactory.sol"; +import "src/LBPair.sol"; +import "src/LBRouter.sol"; +import "src/LBQuoter.sol"; +import "src/interfaces/ILBRouter.sol"; +import "src/interfaces/IJoeRouter02.sol"; +import "src/LBToken.sol"; +import "src/libraries/math/Uint256x256Math.sol"; +import "src/libraries/Constants.sol"; + +import "test/mocks/WAVAX.sol"; +import "test/mocks/ERC20.sol"; +import "test/mocks/FlashloanBorrower.sol"; +import "test/mocks/ERC20TransferTax.sol"; + +abstract contract TestHelper is Test, IERC165 { + using Uint256x256Math for uint256; + + uint24 internal constant ID_ONE = 2 ** 23; + uint256 internal constant BASIS_POINT_MAX = 10_000; + + // Avalanche market config for 10bps + uint16 internal constant DEFAULT_BIN_STEP = 10; + uint16 internal constant DEFAULT_BASE_FACTOR = 1000; + uint16 internal constant DEFAULT_FILTER_PERIOD = 30; + uint16 internal constant DEFAULT_DECAY_PERIOD = 600; + uint16 internal constant DEFAULT_REDUCTION_FACTOR = 5_000; + uint24 internal constant DEFAULT_VARIABLE_FEE_CONTROL = 40_000; + uint16 internal constant DEFAULT_PROTOCOL_SHARE = 1_000; + uint24 internal constant DEFAULT_MAX_VOLATILITY_ACCUMULATED = 350_000; + uint16 internal constant DEFAULT_SAMPLE_LIFETIME = 120; + uint256 internal constant DEFAULT_FLASHLOAN_FEE = 8e14; + + address payable immutable DEV = payable(address(this)); + address payable immutable ALICE = payable(makeAddr("alice")); + address payable immutable BOB = payable(makeAddr("bob")); + + // Wrapped Native + WAVAX internal wavax; + + // 6 decimals + ERC20Mock internal usdc; + ERC20Mock internal usdt; + + // 8 decimals + ERC20Mock internal wbtc; + + // 18 decimals + ERC20Mock internal link; + ERC20Mock internal bnb; + ERC20Mock internal weth; + + // Tax tokens (18 decimals) + ERC20TransferTaxMock internal taxToken; + + LBFactory internal factory; + LBRouter internal router; + LBPair internal pair; + LBPair internal pairWavax; + LBQuoter internal quoter; + + function setUp() public virtual { + // Create mocks + wavax = new WAVAX(); + usdc = new ERC20Mock(6); + usdt = new ERC20Mock(6); + wbtc = new ERC20Mock(8); + weth = new ERC20Mock(18); + link = new ERC20Mock(18); + bnb = new ERC20Mock(18); + taxToken = new ERC20TransferTaxMock(); + + // Label mocks + vm.label(address(wavax), "wavax"); + vm.label(address(usdc), "usdc"); + vm.label(address(usdt), "usdt"); + vm.label(address(wbtc), "wbtc"); + vm.label(address(weth), "weth"); + vm.label(address(link), "link"); + vm.label(address(bnb), "bnb"); + vm.label(address(taxToken), "taxToken"); + + // Create factory + factory = new LBFactory(DEV, DEFAULT_FLASHLOAN_FEE); + ILBPair LBPairImplementation = new LBPair(factory); + + // Setup factory + factory.setLBPairImplementation(address(LBPairImplementation)); + addAllAssetsToQuoteWhitelist(); + setDefaultFactoryPresets(DEFAULT_BIN_STEP); + + // Create router + router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(0))); + + // Label deployed contracts + vm.label(address(factory), "factory"); + vm.label(address(router), "router"); + vm.label(address(LBPairImplementation), "LBPairImplementation"); + } + + function supportsInterface(bytes4 interfaceId) external view virtual returns (bool) { + return interfaceId == type(ILBToken).interfaceId; + } + + function getPriceFromId(uint24 id) internal pure returns (uint256 price) { + price = BinHelper.getPriceFromId(id, DEFAULT_BIN_STEP); + } + + function getIdFromPrice(uint256 price) internal pure returns (uint24 id) { + id = BinHelper.getIdFromPrice(price, DEFAULT_BIN_STEP); + } + + function createLBPair(IERC20 tokenX, IERC20 tokenY) internal returns (LBPair newPair) { + newPair = createLBPairFromStartId(tokenX, tokenY, ID_ONE); + } + + function setDefaultFactoryPresets(uint16 binStep) internal { + factory.setPreset( + binStep, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATED, + DEFAULT_SAMPLE_LIFETIME + ); + } + + function createLBPairFromStartId(IERC20 tokenX, IERC20 tokenY, uint24 startId) internal returns (LBPair newPair) { + newPair = createLBPairFromStartIdAndBinStep(tokenX, tokenY, startId, DEFAULT_BIN_STEP); + } + + function createLBPairFromStartIdAndBinStep(IERC20 tokenX, IERC20 tokenY, uint24 startId, uint16 binStep) + internal + returns (LBPair newPair) + { + newPair = LBPair(address(factory.createLBPair(tokenX, tokenY, startId, binStep))); + } + + function convertRelativeIdsToAbsolute(int256[] memory relativeIds, uint24 startId) + internal + pure + returns (uint256[] memory absoluteIds) + { + absoluteIds = new uint256[](relativeIds.length); + for (uint256 i = 0; i < relativeIds.length; i++) { + int256 id = int256(uint256(startId)) + relativeIds[i]; + require(id >= 0, "Id conversion: id must be positive"); + absoluteIds[i] = uint256(id); + } + } + + function convertAbsoluteIdsToRelative(uint256[] memory absoluteIds, uint24 startId) + internal + pure + returns (int256[] memory relativeIds) + { + relativeIds = new int256[](absoluteIds.length); + for (uint256 i = 0; i < absoluteIds.length; i++) { + relativeIds[i] = int256(absoluteIds[i]) - int256(uint256(startId)); + } + } + + function addLiquidityAndReturnAbsoluteIds( + ERC20Mock tokenX, + ERC20Mock tokenY, + uint256 amountYIn, + uint24 startId, + uint24 numberBins, + uint24 gap + ) + internal + returns ( + uint256[] memory ids, + uint256[] memory distributionX, + uint256[] memory distributionY, + uint256 amountXIn + ) + { + (ids, distributionX, distributionY, amountXIn) = + addLiquidity(tokenX, tokenY, amountYIn, startId, numberBins, gap); + } + + function addLiquidityAndReturnRelativeIds( + ERC20Mock tokenX, + ERC20Mock tokenY, + uint256 amountYIn, + uint24 startId, + uint24 numberBins, + uint24 gap + ) + internal + returns (int256[] memory ids, uint256[] memory distributionX, uint256[] memory distributionY, uint256 amountXIn) + { + uint256[] memory absoluteIds; + (absoluteIds, distributionX, distributionY, amountXIn) = + addLiquidity(tokenX, tokenY, amountYIn, startId, numberBins, gap); + ids = convertAbsoluteIdsToRelative(absoluteIds, startId); + } + + function addLiquidity( + ERC20Mock tokenX, + ERC20Mock tokenY, + uint256 amountYIn, + uint24 startId, + uint24 numberBins, + uint24 gap + ) + internal + returns ( + uint256[] memory ids, + uint256[] memory distributionX, + uint256[] memory distributionY, + uint256 amountXIn + ) + { + (ids, distributionX, distributionY, amountXIn) = spreadLiquidity(amountYIn, startId, numberBins, gap); + + tokenX.mint(address(pair), amountXIn); + tokenY.mint(address(pair), amountYIn); + + pair.mint(ids, distributionX, distributionY, DEV); + } + + function spreadLiquidity(uint256 amountYIn, uint24 startId, uint24 numberBins, uint24 gap) + internal + pure + returns ( + uint256[] memory ids, + uint256[] memory distributionX, + uint256[] memory distributionY, + uint256 amountXIn + ) + { + if (numberBins % 2 == 0) { + revert("Pls put an uneven number of bins"); + } + + uint24 spread = numberBins / 2; + ids = new uint256[](numberBins); + + distributionX = new uint256[](numberBins); + distributionY = new uint256[](numberBins); + uint256 binDistribution = Constants.PRECISION / (spread + 1); + uint256 binLiquidity = amountYIn / (spread + 1); + + for (uint256 i; i < numberBins; i++) { + ids[i] = startId - spread * (1 + gap) + i * (1 + gap); + + if (i <= spread) { + distributionY[i] = binDistribution; + } + if (i >= spread) { + distributionX[i] = binDistribution; + amountXIn += + binLiquidity > 0 ? (binLiquidity * Constants.SCALE - 1) / getPriceFromId(uint24(ids[i])) + 1 : 0; + } + } + } + + function addAllAssetsToQuoteWhitelist() internal { + if (address(wavax) != address(0)) factory.addQuoteAsset(wavax); + if (address(usdc) != address(0)) factory.addQuoteAsset(usdc); + if (address(usdt) != address(0)) factory.addQuoteAsset(usdt); + if (address(wbtc) != address(0)) factory.addQuoteAsset(wbtc); + if (address(weth) != address(0)) factory.addQuoteAsset(weth); + if (address(link) != address(0)) factory.addQuoteAsset(link); + if (address(bnb) != address(0)) factory.addQuoteAsset(bnb); + if (address(taxToken) != address(0)) factory.addQuoteAsset(taxToken); + } +} From 3870bb09493855ebcf97d344a4943f357fe882b6 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 25 Jan 2023 19:28:31 +0100 Subject: [PATCH 06/47] add getter to the pair and missing libraries, some clean up --- src/LBPair.sol | 137 ++++++++++++++++++ src/interfaces/ILBPair.sol | 14 ++ src/libraries/BinHelper.sol | 2 +- src/libraries/FeeHelper.sol | 9 +- src/libraries/PairParameterHelper.sol | 4 +- src/libraries/math/Decoder.sol | 24 +++ .../math/LiquidityConfigurations.sol | 21 ++- src/libraries/math/SampleMath.sol | 58 ++++---- 8 files changed, 223 insertions(+), 46 deletions(-) create mode 100644 src/libraries/math/Decoder.sol diff --git a/src/LBPair.sol b/src/LBPair.sol index 2acf3b0d..b2abdb80 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -7,6 +7,7 @@ import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {BinHelper} from "./libraries/BinHelper.sol"; import {Clone} from "./libraries/Clone.sol"; import {Constants} from "./libraries/Constants.sol"; +import {FeeHelper} from "./libraries/FeeHelper.sol"; import {LiquidityConfigurations} from "./libraries/math/LiquidityConfigurations.sol"; import {ILBFactory} from "./interfaces/ILBFactory.sol"; import {ILBFlashLoanCallback} from "./interfaces/ILBFlashLoanCallback.sol"; @@ -24,6 +25,7 @@ import {Uint256x256Math} from "./libraries/math/Uint256x256Math.sol"; contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { using BinHelper for bytes32; + using FeeHelper for uint128; using LiquidityConfigurations for bytes32; using OracleHelper for OracleHelper.Oracle; using PackedUint128Math for bytes32; @@ -278,6 +280,141 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { } } + /** + * @notice Returns the price corresponding to the given id, as a 128.128-binary fixed-point number + * @dev This is the trusted source of price information, always trust this rather than getIdFromPrice + * @param id The id of the bin + * @return price The price corresponding to this id + */ + function getPriceFromId(uint24 id) external view override returns (uint256 price) { + price = id.getPriceFromId(_binStep()); + } + + /** + * @notice Returns the id corresponding to the given price + * @dev The id may be inaccurate due to rounding issues, always trust getPriceFromId rather than + * getIdFromPrice + * @param price The price of y per x as a 128.128-binary fixed-point number + * @return id The id of the bin corresponding to this price + */ + function getIdFromPrice(uint256 price) external view override returns (uint24 id) { + id = price.getIdFromPrice(_binStep()); + } + + /** + * @notice Simulates a swap in. + * @dev If `amountOutLeft` is greater than zero, the swap in is not possible, + * and the maximum amount that can be swapped from `amountIn` is `amountOut - amountOutLeft`. + * @param amountOut The amount of token X or Y to swap in + * @param swapForY Whether the swap is for token Y (true) or token X (false) + * @return amountIn The amount of token X or Y that can be swapped in + * @return amountOutLeft The amount of token Y or X that cannot be swapped out + * @return fee The fee of the swap + */ + function getSwapIn(uint128 amountOut, bool swapForY) + external + view + override + returns (uint128 amountIn, uint128 amountOutLeft, uint128 fee) + { + amountOutLeft = amountOut; + + bytes32 parameters = _parameters; + uint8 binStep = _binStep(); + + uint24 id = parameters.getActiveId(); + + parameters = parameters.updateReferences(); + + while (true) { + uint128 binReserves = _bins[id].decode(swapForY); + if (binReserves > 0) { + uint256 price = id.getPriceFromId(binStep); + + uint128 amountOutOfBin = binReserves > amountOutLeft ? amountOutLeft : binReserves; + + parameters.updateVolatilityParameters(id); + + uint256 amountInToBin = swapForY + ? uint256(amountOutOfBin).shiftDivRoundUp(Constants.SCALE_OFFSET, price) + : uint256(amountOutOfBin).mulShiftRoundUp(price, Constants.SCALE_OFFSET); + + uint128 totalFee = parameters.getTotalFee(binStep); + uint128 feeAmount = amountOutOfBin.getFeeAmount(totalFee); + + amountIn += amountInToBin + feeAmount; + amountOutLeft -= amountOutOfBin; + + fee += feeAmount; + } + + if (amountOutLeft == 0) { + break; + } else { + uint24 nextId = _getNextNonEmptyBin(swapForY, id); + + if (nextId == 0 || nextId == type(uint24).max) break; + + id = nextId; + } + } + } + + /** + * @notice Simulates a swap out. + * @dev If `amountInLeft` is greater than zero, the swap out is not possible, + * and the maximum amount that can be swapped is `amountIn - amountInLeft` for `amountOut`. + * @param amountIn The amount of token X or Y to swap in + * @param swapForY Whether the swap is for token Y (true) or token X (false) + * @return amountInLeft The amount of token X or Y that cannot be swapped in + * @return amountOut The amount of token Y or X that can be swapped out + * @return fee The fee of the swap + */ + function getSwapOut(uint128 amountIn, bool swapForY) + external + view + override + returns (uint128 amountInLeft, uint128 amountOut, uint128 fee) + { + (bytes32 amountsIn, bytes32 amountsOut, bytes32 fees) = (amountIn.encode(swapForY), 0, 0); + + bytes32 parameters = _parameters; + uint8 binStep = _binStep(); + + uint24 id = parameters.getActiveId(); + + parameters = parameters.updateReferences(); + + while (true) { + bytes32 binReserves = _bins[id]; + if (!binReserves.isEmpty(swapForY)) { + parameters = parameters.updateVolatilityAccumulated(id); + + (bytes32 amountsInToBin, bytes32 amountsOutOfBin, bytes32 totalFees) = + binReserves.getAmounts(parameters, binStep, swapForY, id, amountsIn); + + if (amountsInToBin > 0) { + amountsIn = amountsIn.sub(amountsInToBin.add(totalFees)); + fees = fees.add(totalFees); + amountsOut = amountsOut.add(amountsOutOfBin); + } + } + + if (amountsIn == 0) { + break; + } else { + uint24 nextId = _getNextNonEmptyBin(swapForY, id); + + if (nextId == 0 || nextId == type(uint24).max) break; + + id = nextId; + } + } + + (amountInLeft, amountOut, fee) = + (amountsIn.decode(swapForY), amountsOut.decode(swapForY), fees.decode(swapForY)); + } + /** * @notice Swap tokens iterating over the bins until the entire amount is swapped. * Token X will be swapped for token Y if `swapForY` is true, and token Y for token X if `swapForY` is false. diff --git a/src/interfaces/ILBPair.sol b/src/interfaces/ILBPair.sol index f755695b..48c5bdb1 100644 --- a/src/interfaces/ILBPair.sol +++ b/src/interfaces/ILBPair.sol @@ -119,6 +119,20 @@ interface ILBPair is ILBToken { view returns (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed); + function getPriceFromId(uint24 id) external view returns (uint256 price); + + function getIdFromPrice(uint256 price) external view returns (uint24 id); + + function getSwapIn(uint128 amountOut, bool swapForY) + external + view + returns (uint128 amountIn, uint128 amountOutLeft, uint128 fee); + + function getSwapOut(uint128 amountIn, bool swapForY) + external + view + returns (uint128 amountInLeft, uint128 amountOut, uint128 fee); + function swap(bool swapForY, address to) external returns (bytes32 amountsOut); function flashLoan(ILBFlashLoanCallback receiver, bytes32 amounts, bytes calldata data) external; diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index 8488948d..fdee4dc5 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -161,7 +161,7 @@ library BinHelper { ? uint256(binReserveOut).shiftDivRoundUp(Constants.SCALE_OFFSET, price).safe128() : uint256(binReserveOut).mulShiftRoundUp(price, Constants.SCALE_OFFSET).safe128(); - uint256 totalFee = parameters.getTotalFee(binStep); + uint128 totalFee = parameters.getTotalFee(binStep); uint128 maxFee = maxAmountIn.getFeeAmount(totalFee); uint128 fee128; diff --git a/src/libraries/FeeHelper.sol b/src/libraries/FeeHelper.sol index 73081a22..101c3620 100644 --- a/src/libraries/FeeHelper.sol +++ b/src/libraries/FeeHelper.sol @@ -19,7 +19,7 @@ library FeeHelper { * @param totalFee The total fee * @return feeAmount The fee amount */ - function getFeeAmountFrom(uint128 amounWithFees, uint256 totalFee) internal pure returns (uint128) { + function getFeeAmountFrom(uint128 amounWithFees, uint128 totalFee) internal pure returns (uint128) { unchecked { return ((uint256(amounWithFees) * totalFee + Constants.PRECISION - 1) / Constants.PRECISION).safe128(); } @@ -31,7 +31,7 @@ library FeeHelper { * @param totalFee The total fee * @return feeAmount The fee amount */ - function getFeeAmount(uint128 amount, uint256 totalFee) internal pure returns (uint128) { + function getFeeAmount(uint128 amount, uint128 totalFee) internal pure returns (uint128) { unchecked { uint256 denominator = Constants.PRECISION - totalFee; return ((uint256(amount) * totalFee + denominator - 1) / denominator).safe128(); @@ -44,10 +44,11 @@ library FeeHelper { * @param totalFee The total fee * @return The amount with fees */ - function getCompositionFee(uint128 amountWithFees, uint256 totalFee) internal pure returns (uint128) { + function getCompositionFee(uint128 amountWithFees, uint128 totalFee) internal pure returns (uint128) { unchecked { uint256 denominator = Constants.PRECISION * Constants.PRECISION; - return (uint256(amountWithFees) * totalFee * (totalFee + Constants.PRECISION) / denominator).safe128(); + return + (uint256(amountWithFees) * totalFee * (uint256(totalFee) + Constants.PRECISION) / denominator).safe128(); } } diff --git a/src/libraries/PairParameterHelper.sol b/src/libraries/PairParameterHelper.sol index 69f38fe5..cb44d2d0 100644 --- a/src/libraries/PairParameterHelper.sol +++ b/src/libraries/PairParameterHelper.sol @@ -381,9 +381,9 @@ library PairParameterHelper { * @param binStep The bin step * @return totalFee The total fee */ - function getTotalFee(bytes32 params, uint8 binStep) internal pure returns (uint256) { + function getTotalFee(bytes32 params, uint8 binStep) internal pure returns (uint128) { unchecked { - return getBaseFee(params, binStep) + getVariableFee(params, binStep); + return (getBaseFee(params, binStep) + getVariableFee(params, binStep)).safe128(); } } diff --git a/src/libraries/math/Decoder.sol b/src/libraries/math/Decoder.sol new file mode 100644 index 00000000..b6119629 --- /dev/null +++ b/src/libraries/math/Decoder.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +/** + * @title Liquidity Book Decoder Library + * @author Trader Joe + * @notice Helper contract used for decoding bytes32 sample + */ +library Decoder { + /** + * @notice Internal function to decode a bytes32 sample using a mask and offset + * @dev This function can overflow + * @param encoded The encoded value + * @param mask The mask + * @param offset The offset + * @return value The decoded value + */ + function decode(bytes32 encoded, uint256 mask, uint256 offset) internal pure returns (uint256 value) { + assembly { + value := and(shr(offset, encoded), mask) + } + } +} diff --git a/src/libraries/math/LiquidityConfigurations.sol b/src/libraries/math/LiquidityConfigurations.sol index d2055221..cac7254f 100644 --- a/src/libraries/math/LiquidityConfigurations.sol +++ b/src/libraries/math/LiquidityConfigurations.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.10; import "./PackedUint128Math.sol"; +import "./Decoder.sol"; /** * @title Liquidity Book Liquidity Configurations Library @@ -10,12 +11,16 @@ import "./PackedUint128Math.sol"; * @notice This library contains functions to encode and decode the config of a pool and interact with the encoded bytes32. */ library LiquidityConfigurations { + using Decoder for bytes32; using PackedUint128Math for bytes32; using PackedUint128Math for uint128; error LiquidityConfigurations__InvalidConfig(); - uint256 private constant PRECISION = 1e18; + uint256 private constant _OFFSET_DISTRIBUTION_Y = 24; + uint256 private constant _OFFSET_DISTRIBUTION_X = 88; + + uint256 private constant _PRECISION = 1e18; /** * @dev Encode the distributionX, distributionY and id into a single bytes32 @@ -34,7 +39,7 @@ library LiquidityConfigurations { returns (bytes32 config) { assembly { - config := or(shl(88, distributionX), or(shl(24, distributionY), id)) + config := or(shl(_OFFSET_DISTRIBUTION_Y, distributionX), or(shl(_OFFSET_DISTRIBUTION_X, distributionY), id)) } } @@ -55,12 +60,12 @@ library LiquidityConfigurations { returns (uint64 distributionX, uint64 distributionY, uint24 id) { assembly { - distributionX := shr(88, config) - distributionY := and(shr(24, config), 0xffffffffffffffff) - id := and(config, 0xffffff) + distributionX := shr(_OFFSET_DISTRIBUTION_Y, config) + distributionY := shr(_OFFSET_DISTRIBUTION_X, config) + id := config } - if (uint256(config) > type(uint152).max || distributionX > PRECISION || distributionY > PRECISION) { + if (uint256(config) > type(uint152).max || distributionX > _PRECISION || distributionY > _PRECISION) { revert LiquidityConfigurations__InvalidConfig(); } } @@ -86,8 +91,8 @@ library LiquidityConfigurations { (uint128 x1, uint128 x2) = amountsIn.decode(); assembly { - x1 := div(mul(x1, distributionX), PRECISION) - x2 := div(mul(x2, distributionY), PRECISION) + x1 := div(mul(x1, distributionX), _PRECISION) + x2 := div(mul(x2, distributionY), _PRECISION) } return (x1.encode(x2), id); diff --git a/src/libraries/math/SampleMath.sol b/src/libraries/math/SampleMath.sol index 2147a25f..4a9920c1 100644 --- a/src/libraries/math/SampleMath.sol +++ b/src/libraries/math/SampleMath.sol @@ -16,16 +16,39 @@ pragma solidity 0.8.10; * 216 - 256: sample creation timestamp (40 bits) */ library SampleMath { - uint256 internal constant _MASK_ORACLE_LENGTH = 0xffff; - uint256 internal constant _MASK_CUMULATIVE = 0xffffffffffff; - uint256 internal constant _MASK_SAMPLE_LIFETIME = 0xff; - uint256 internal constant _SHIFT_CUMULATIVE_ID = 16; uint256 internal constant _SHIFT_CUMULATIVE_VOLATILITY = 80; uint256 internal constant _SHIFT_CUMULATIVE_BIN_CROSSED = 144; uint256 internal constant _SHIFT_SAMPLE_LIFETIME = 208; uint256 internal constant _SHIFT_SAMPLE_CREATION = 216; + /** + * @dev Encodes a sample + * @param oracleLength The oracle length + * @param cumulativeId The cumulative id + * @param cumulativeVolatility The cumulative volatility + * @param cumulativeBinCrossed The cumulative bin crossed + * @param sampleLifetime The sample lifetime + * @param createdAt The sample creation timestamp + * @return sample The encoded sample + */ + function encode( + uint16 oracleLength, + uint64 cumulativeId, + uint64 cumulativeVolatility, + uint64 cumulativeBinCrossed, + uint8 sampleLifetime, + uint40 createdAt + ) internal pure returns (bytes32 sample) { + assembly { + sample := or(oracleLength, shl(_SHIFT_CUMULATIVE_ID, cumulativeId)) + sample := or(sample, shl(_SHIFT_CUMULATIVE_VOLATILITY, cumulativeVolatility)) + sample := or(sample, shl(_SHIFT_CUMULATIVE_BIN_CROSSED, cumulativeBinCrossed)) + sample := or(sample, shl(_SHIFT_SAMPLE_LIFETIME, sampleLifetime)) + sample := or(sample, shl(_SHIFT_SAMPLE_CREATION, createdAt)) + } + } + /** * @dev Decodes an encoded sample and return all the values * @param sample The encoded sample @@ -56,33 +79,6 @@ library SampleMath { createdAt = getSampleCreation(sample); } - /** - * @dev Encodes a sample - * @param oracleLength The oracle length - * @param cumulativeId The cumulative id - * @param cumulativeVolatility The cumulative volatility - * @param cumulativeBinCrossed The cumulative bin crossed - * @param sampleLifetime The sample lifetime - * @param createdAt The sample creation timestamp - * @return sample The encoded sample - */ - function encode( - uint16 oracleLength, - uint64 cumulativeId, - uint64 cumulativeVolatility, - uint64 cumulativeBinCrossed, - uint8 sampleLifetime, - uint40 createdAt - ) internal pure returns (bytes32 sample) { - assembly { - sample := or(oracleLength, shl(_SHIFT_CUMULATIVE_ID, cumulativeId)) - sample := or(sample, shl(_SHIFT_CUMULATIVE_VOLATILITY, cumulativeVolatility)) - sample := or(sample, shl(_SHIFT_CUMULATIVE_BIN_CROSSED, cumulativeBinCrossed)) - sample := or(sample, shl(_SHIFT_SAMPLE_LIFETIME, sampleLifetime)) - sample := or(sample, shl(_SHIFT_SAMPLE_CREATION, createdAt)) - } - } - /** * @dev Gets the oracle length from an encoded sample * @param sample The encoded sample From 85754f1043a8ab34b113f14eb8a4f52b9ebb06ca Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 25 Jan 2023 20:20:02 +0100 Subject: [PATCH 07/47] missing natspec for binhelper --- src/libraries/BinHelper.sol | 176 +++++++++++++++++++++++++++++------- 1 file changed, 143 insertions(+), 33 deletions(-) diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index fdee4dc5..54a2ab29 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -30,39 +30,13 @@ library BinHelper { error BinMath__CompositionFactorFlawed(uint24 id); - function received(bytes32 reserves, IERC20 tokenX, IERC20 tokenY) internal view returns (bytes32 amounts) { - amounts = _balanceOf(tokenX).encode(_balanceOf(tokenY)).sub(reserves); - } - - function receivedX(bytes32 reserves, IERC20 tokenX) internal view returns (bytes32) { - uint128 reserveX = reserves.decodeFirst(); - return (_balanceOf(tokenX) - reserveX).encodeFirst(); - } - - function receivedY(bytes32 reserves, IERC20 tokenY) internal view returns (bytes32) { - uint128 reserveY = reserves.decodeSecond(); - return (_balanceOf(tokenY) - reserveY).encodeSecond(); - } - - function transfer(bytes32 amounts, IERC20 tokenX, IERC20 tokenY, address recipient) internal { - (uint128 amountX, uint128 amountY) = amounts.decode(); - - if (amountX > 0) tokenX.safeTransfer(recipient, amountX); - if (amountY > 0) tokenY.safeTransfer(recipient, amountY); - } - - function transferX(bytes32 amounts, IERC20 tokenX, address recipient) internal { - uint128 amountX = amounts.decodeFirst(); - - if (amountX > 0) tokenX.safeTransfer(recipient, amountX); - } - - function transferY(bytes32 amounts, IERC20 tokenY, address recipient) internal { - uint128 amountY = amounts.decodeSecond(); - - if (amountY > 0) tokenY.safeTransfer(recipient, amountY); - } - + /** + * @dev Returns the amount of tokens that will be received when burning the given amount of liquidity + * @param binReserves The reserves of the bin + * @param amountToBurn The amount of liquidity to burn + * @param totalSupply The total supply of the liquidity book + * @return amountsOut The encoded amount of tokens that will be received + */ function getAmountOutOfBin(bytes32 binReserves, uint256 amountToBurn, uint256 totalSupply) internal pure @@ -84,6 +58,17 @@ library BinHelper { amountsOut = amountXOutFromBin.encode(amountYOutFromBin); } + /** + * @dev Returns the share and the effective amounts in when adding liquidity + * @param binReserves The reserves of the bin + * @param amountsIn The amounts of tokens to add + * @param price The price of the bin + * @param totalSupply The total supply of the liquidity book + * @return shares The share of the liquidity book that the user will receive + * @return effectiveAmountsIn The encoded effective amounts of tokens that the user will add. + * This is the amount of tokens that the user will actually add to the liquidity book, + * and will always be less than or equal to the amountsIn. + */ function getShareAndEffectiveAmountsIn(bytes32 binReserves, bytes32 amountsIn, uint256 price, uint256 totalSupply) internal pure @@ -101,6 +86,12 @@ library BinHelper { effectiveAmountsIn = amountsIn.scalarMulShift128RoundUp(ratioLiquidity.safe128()); } + /** + * @dev Returns the amount of liquidity following the constant sum formula `L = price * x + y` + * @param amounts The amounts of tokens + * @param price The price of the bin + * @return liquidity The amount of liquidity + */ function getLiquidity(bytes32 amounts, uint256 price) internal pure returns (uint256 liquidity) { (uint128 x, uint128 y) = amounts.decode(); if (x > 0) { @@ -111,6 +102,12 @@ library BinHelper { } } + /** + * @dev Verify that the amounts are correct and that the composition factor is not flawed + * @param amounts The amounts of tokens + * @param activeId The id of the active bin + * @param id The id of the bin + */ function verifyAmounts(bytes32 amounts, uint24 activeId, uint24 id) internal pure { if ( uint256(amounts) <= type(uint128).max && id < activeId @@ -118,6 +115,17 @@ library BinHelper { ) revert BinMath__CompositionFactorFlawed(id); } + /** + * @dev Returns the composition fees when adding liquidity to the active bin with a different + * composition factor than the bin's one, as it does an implicit swap + * @param binReserves The reserves of the bin + * @param parameters The parameters of the liquidity book + * @param binStep The step of the bin + * @param amountsIn The amounts of tokens to add + * @param totalSupply The total supply of the liquidity book + * @param shares The share of the liquidity book that the user will receive + * @return fees The encoded fees that will be charged + */ function getCompositionFees( bytes32 binReserves, bytes32 parameters, @@ -141,10 +149,29 @@ library BinHelper { } } + /** + * @dev Returns whether the bin is empty (true) or not (false) + * @param binReserves The reserves of the bin + * @param isX Whether the reserve to check is the X reserve (true) or the Y reserve (false) + * @return Whether the bin is empty (true) or not (false) + */ function isEmpty(bytes32 binReserves, bool isX) internal pure returns (bool) { return isX ? binReserves.decodeFirst() == 0 : binReserves.decodeSecond() == 0; } + /** + * @dev Returns the amounts of tokens that will be added and removed from the bin during a swap + * along with the fees that will be charged + * @param binReserves The reserves of the bin + * @param parameters The parameters of the liquidity book + * @param binStep The step of the bin + * @param swapForY Whether the swap is for Y (true) or for X (false) + * @param activeId The id of the active bin + * @param amountsLeft The amounts of tokens left to swap + * @return amountsInToBin The encoded amounts of tokens that will be added to the bin + * @return amountsOutOfBin The encoded amounts of tokens that will be removed from the bin + * @return totalFees The encoded fees that will be charged + */ function getAmounts( bytes32 binReserves, bytes32 parameters, @@ -193,6 +220,89 @@ library BinHelper { : (amountIn128.encodeSecond(), amountOut128.encodeFirst(), fee128.encodeSecond()); } + /** + * @dev Returns the encoded amounts that was transferred to the contract + * @param reserves The reserves + * @param tokenX The token X + * @param tokenY The token Y + * @return amounts The amounts, encoded as follows: + * [0 - 128[: amountX + * [128 - 256[: amountY + */ + function received(bytes32 reserves, IERC20 tokenX, IERC20 tokenY) internal view returns (bytes32 amounts) { + amounts = _balanceOf(tokenX).encode(_balanceOf(tokenY)).sub(reserves); + } + + /** + * @dev Returns the encoded amounts that was transferred to the contract, only for token X + * @param reserves The reserves + * @param tokenX The token X + * @return amounts The amounts, encoded as follows: + * [0 - 128[: amountX + * [128 - 256[: empty + */ + function receivedX(bytes32 reserves, IERC20 tokenX) internal view returns (bytes32) { + uint128 reserveX = reserves.decodeFirst(); + return (_balanceOf(tokenX) - reserveX).encodeFirst(); + } + + /** + * @dev Returns the encoded amounts that was transferred to the contract, only for token Y + * @param reserves The reserves + * @param tokenY The token Y + * @return amounts The amounts, encoded as follows: + * [0 - 128[: empty + * [128 - 256[: amountY + */ + function receivedY(bytes32 reserves, IERC20 tokenY) internal view returns (bytes32) { + uint128 reserveY = reserves.decodeSecond(); + return (_balanceOf(tokenY) - reserveY).encodeSecond(); + } + + /** + * @dev Transfers the encoded amounts to the recipient + * @param amounts The amounts, encoded as follows: + * [0 - 128[: amountX + * [128 - 256[: amountY + * @param tokenX The token X + * @param tokenY The token Y + * @param recipient The recipient + */ + function transfer(bytes32 amounts, IERC20 tokenX, IERC20 tokenY, address recipient) internal { + (uint128 amountX, uint128 amountY) = amounts.decode(); + + if (amountX > 0) tokenX.safeTransfer(recipient, amountX); + if (amountY > 0) tokenY.safeTransfer(recipient, amountY); + } + + /** + * @dev Transfers the encoded amounts to the recipient, only for token X + * @param amounts The amounts, encoded as follows: + * [0 - 128[: amountX + * [128 - 256[: empty + * @param tokenX The token X + * @param recipient The recipient + */ + function transferX(bytes32 amounts, IERC20 tokenX, address recipient) internal { + uint128 amountX = amounts.decodeFirst(); + + if (amountX > 0) tokenX.safeTransfer(recipient, amountX); + } + + /** + * @dev Transfers the encoded amounts to the recipient, only for token Y + * @param amounts The amounts, encoded as follows: + * [0 - 128[: empty + * [128 - 256[: amountY + * @param tokenY The token Y + * @param recipient The recipient + */ + function transferY(bytes32 amounts, IERC20 tokenY, address recipient) internal { + uint128 amountY = amounts.decodeSecond(); + + if (amountY > 0) tokenY.safeTransfer(recipient, amountY); + } + function _balanceOf(IERC20 token) private view returns (uint128) { return token.balanceOf(address(this)).safe128(); } From 2def5da19cf97b390cec1abe75e2dd909bea6ae0 Mon Sep 17 00:00:00 2001 From: Mathieu <85969303+Mathieu-Be@users.noreply.github.com> Date: Thu, 26 Jan 2023 16:17:30 +0100 Subject: [PATCH 08/47] Add pair revisions (#73) * add revisions in factory, router and quoter * add factory tests * test helpers improvements * support revisions on router and quoter * adding tests for revisions * merge v2.1 into add-pair-revisions * fix merge issues * fix stack too deep error * small fix for swapForY * fix stack too deep error Co-authored-by: 0x0Louis --- script/deploy-core.s.sol | 122 ++-- src/LBFactory.sol | 255 ++++++-- src/LBPair.sol | 29 +- src/LBQuoter.sol | 209 +++--- src/LBRouter.sol | 469 ++++++-------- src/interfaces/ILBFactory.sol | 38 +- src/interfaces/ILBLegacyFactory.sol | 179 ++++++ src/interfaces/ILBLegacyRouter.sol | 184 ++++++ src/interfaces/ILBRouter.sol | 94 ++- src/libraries/BinHelper.sol | 4 +- test/LBFactory.t.sol | 962 ++++++++++++++++++++++++++++ test/LBRouter.Liquidity.t.sol | 57 ++ test/helpers/TestHelper.sol | 224 ++++--- test/helpers/Utils.sol | 55 ++ test/integration/Addresses.sol | 16 +- test/integration/LBQuoter.t.sol | 129 ++++ test_old/LBFactory.t.sol | 475 -------------- 17 files changed, 2400 insertions(+), 1101 deletions(-) create mode 100644 src/interfaces/ILBLegacyFactory.sol create mode 100644 src/interfaces/ILBLegacyRouter.sol create mode 100644 test/LBFactory.t.sol create mode 100644 test/LBRouter.Liquidity.t.sol create mode 100644 test/helpers/Utils.sol create mode 100644 test/integration/LBQuoter.t.sol delete mode 100644 test_old/LBFactory.t.sol diff --git a/script/deploy-core.s.sol b/script/deploy-core.s.sol index 008bc7ae..0ba4acf0 100644 --- a/script/deploy-core.s.sol +++ b/script/deploy-core.s.sol @@ -12,65 +12,65 @@ import "src/LBQuoter.sol"; import "./config/bips-config.sol"; contract CoreDeployer is Script { - // address private constant WAVAX_AVALANCHE = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7; - // address private constant WAVAX_FUJI = 0xd00ae08403B9bbb9124bB305C09058E32C39A48c; - - // address private constant FACTORY_V1_AVALANCHE = 0x9Ad6C38BE94206cA50bb0d90783181662f0Cfa10; - // address private constant FACTORY_V1_FUJI = 0xF5c7d9733e5f53abCC1695820c4818C59B457C2C; - - // address private wavax; - // address private factoryV1; - - // uint256 private constant FLASHLOAN_FEE = 5e12; - - // function run() external { - // if (block.chainid == 43114) { - // wavax = WAVAX_AVALANCHE; - // factoryV1 = FACTORY_V1_AVALANCHE; - // } else { - // wavax = WAVAX_FUJI; - // factoryV1 = FACTORY_V1_FUJI; - // } - - // vm.broadcast(); - // LBFactory factory = new LBFactory(msg.sender, FLASHLOAN_FEE); - // console.log("LBFactory deployed -->", address(factory)); - - // vm.broadcast(); - // LBPair pairImplementation = new LBPair(factory); - // console.log("LBPair implementation deployed -->", address(pairImplementation)); - - // vm.broadcast(); - // LBRouter router = new LBRouter(factory, IJoeFactory(factoryV1), IWAVAX(wavax)); - // console.log("LBRouter deployed -->", address(router)); - - // vm.startBroadcast(); - // LBQuoter quoter = new LBQuoter(address(router), address(factoryV1), address(factory)); - // console.log("LBQuoter deployed -->", address(quoter)); - - // factory.setLBPairImplementation(address(pairImplementation)); - // console.log("LBPair implementation set on factory"); - - // factory.addQuoteAsset(IERC20(wavax)); - // console.log("Wavax whitelisted as quote asset"); - // vm.stopBroadcast(); - - // vm.startBroadcast(); - // uint256[] memory presetList = BipsConfig.getPresetList(); - // for (uint256 i; i < presetList.length; i++) { - // BipsConfig.FactoryPreset memory preset = BipsConfig.getPreset(presetList[i]); - // factory.setPreset( - // preset.binStep, - // preset.baseFactor, - // preset.filterPeriod, - // preset.decayPeriod, - // preset.reductionFactor, - // preset.variableFeeControl, - // preset.protocolShare, - // preset.maxVolatilityAccumulated, - // preset.sampleLifetime - // ); - // } - // vm.stopBroadcast(); - // } +// address private constant WAVAX_AVALANCHE = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7; +// address private constant WAVAX_FUJI = 0xd00ae08403B9bbb9124bB305C09058E32C39A48c; + +// address private constant FACTORY_V1_AVALANCHE = 0x9Ad6C38BE94206cA50bb0d90783181662f0Cfa10; +// address private constant FACTORY_V1_FUJI = 0xF5c7d9733e5f53abCC1695820c4818C59B457C2C; + +// address private wavax; +// address private factoryV1; + +// uint256 private constant FLASHLOAN_FEE = 5e12; + +// function run() external { +// if (block.chainid == 43114) { +// wavax = WAVAX_AVALANCHE; +// factoryV1 = FACTORY_V1_AVALANCHE; +// } else { +// wavax = WAVAX_FUJI; +// factoryV1 = FACTORY_V1_FUJI; +// } + +// vm.broadcast(); +// LBFactory factory = new LBFactory(msg.sender, FLASHLOAN_FEE); +// console.log("LBFactory deployed -->", address(factory)); + +// vm.broadcast(); +// LBPair pairImplementation = new LBPair(factory); +// console.log("LBPair implementation deployed -->", address(pairImplementation)); + +// vm.broadcast(); +// LBRouter router = new LBRouter(factory, IJoeFactory(factoryV1), IWAVAX(wavax)); +// console.log("LBRouter deployed -->", address(router)); + +// vm.startBroadcast(); +// LBQuoter quoter = new LBQuoter(address(router), address(factoryV1), address(factory)); +// console.log("LBQuoter deployed -->", address(quoter)); + +// factory.setLBPairImplementation(address(pairImplementation)); +// console.log("LBPair implementation set on factory"); + +// factory.addQuoteAsset(IERC20(wavax)); +// console.log("Wavax whitelisted as quote asset"); +// vm.stopBroadcast(); + +// vm.startBroadcast(); +// uint256[] memory presetList = BipsConfig.getPresetList(); +// for (uint256 i; i < presetList.length; i++) { +// BipsConfig.FactoryPreset memory preset = BipsConfig.getPreset(presetList[i]); +// factory.setPreset( +// preset.binStep, +// preset.baseFactor, +// preset.filterPeriod, +// preset.decayPeriod, +// preset.reductionFactor, +// preset.variableFeeControl, +// preset.protocolShare, +// preset.maxVolatilityAccumulated, +// preset.sampleLifetime +// ); +// } +// vm.stopBroadcast(); +// } } diff --git a/src/LBFactory.sol b/src/LBFactory.sol index bd001aef..663db300 100644 --- a/src/LBFactory.sol +++ b/src/LBFactory.sol @@ -5,13 +5,13 @@ pragma solidity 0.8.10; import "openzeppelin/proxy/Clones.sol"; import "openzeppelin/utils/structs/EnumerableSet.sol"; -import "./LBErrors.sol"; import "./libraries/BinHelper.sol"; import "./libraries/Constants.sol"; -import "./libraries/Decoder.sol"; +import "./libraries/math/Decoder.sol"; import "./libraries/PendingOwnable.sol"; import "./libraries/math/SafeCast.sol"; import "./interfaces/ILBFactory.sol"; +import "./libraries/ImmutableClone.sol"; /// @title Liquidity Book Factory /// @author Trader Joe @@ -22,6 +22,7 @@ contract LBFactory is PendingOwnable, ILBFactory { using SafeCast for uint256; using Decoder for bytes32; using EnumerableSet for EnumerableSet.AddressSet; + using PairParameterHelper for bytes32; uint256 public constant override MAX_FEE = 0.1e18; // 10% @@ -43,7 +44,7 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @dev Mapping from a (tokenA, tokenB, binStep) to a LBPair. The tokens are ordered to save gas, but they can be /// in the reverse order in the actual pair. Always query one of the 2 tokens of the pair to assert the order of the 2 tokens - mapping(IERC20 => mapping(IERC20 => mapping(uint256 => LBPairInformation))) private _LBPairsInfo; + mapping(IERC20 => mapping(IERC20 => mapping(uint256 => LBPairInformation[]))) private _LBPairsInfos; /// @dev Whether a preset was set or not, if the bit at `index` is 1, it means that the binStep `index` was set /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas @@ -58,6 +59,8 @@ contract LBFactory is PendingOwnable, ILBFactory { /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas mapping(IERC20 => mapping(IERC20 => bytes32)) private _availableLBPairBinSteps; + uint256 private constant _REVISION_START_INDEX = 1; + /// @notice Constructor /// @param _feeRecipient The address of the fee recipient /// @param _flashLoanFee The value of the fee for flash loan @@ -96,19 +99,34 @@ contract LBFactory is PendingOwnable, ILBFactory { return _quoteAssetWhitelist.contains(address(_token)); } + /// @notice View function to return the number of revisions of a LBPair + /// @param _tokenA The address of the first token of the pair. The order doesn't matter + /// @param _tokenB The address of the second token of the pair + /// @param _binStep The bin step of the LBPair + /// @return The number of revisions + function getNumberOfRevisions(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep) + external + view + override + returns (uint256) + { + (_tokenA, _tokenB) = _sortTokens(_tokenA, _tokenB); + + return _LBPairsInfos[_tokenA][_tokenB][_binStep].length; + } + /// @notice Returns the LBPairInformation if it exists, /// if not, then the address 0 is returned. The order doesn't matter /// @param _tokenA The address of the first token of the pair /// @param _tokenB The address of the second token of the pair /// @param _binStep The bin step of the LBPair /// @return The LBPairInformation - function getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep) + function getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep, uint256 _revision) external view - override returns (LBPairInformation memory) { - return _getLBPairInformation(_tokenA, _tokenB, _binStep); + return _getLBPairInformation(_tokenA, _tokenB, _binStep, _revision); } /// @notice View function to return the different parameters of the preset @@ -190,23 +208,42 @@ contract LBFactory is PendingOwnable, ILBFactory { (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); bytes32 _avLBPairBinSteps = _availableLBPairBinSteps[_tokenA][_tokenB]; - uint256 _nbAvailable = _avLBPairBinSteps.decode(type(uint8).max, 248); + uint256 _nbExistingBinSteps = _avLBPairBinSteps.decode(type(uint8).max, 248); - if (_nbAvailable > 0) { - LBPairsAvailable = new LBPairInformation[](_nbAvailable); + uint256 totalPairs; + if (_nbExistingBinSteps > 0) { uint256 _index; + // Loops a first time to know how many pairs are available + for (uint256 i = MIN_BIN_STEP; i <= MAX_BIN_STEP; ++i) { + if (_avLBPairBinSteps.decode(1, i) == 1) { + totalPairs += _LBPairsInfos[_tokenA][_tokenB][i].length; + + if (++_index == _nbExistingBinSteps) break; + } + } + + LBPairsAvailable = new LBPairInformation[](totalPairs); + + _index = 0; + // Loops a second time to fill the array for (uint256 i = MIN_BIN_STEP; i <= MAX_BIN_STEP; ++i) { if (_avLBPairBinSteps.decode(1, i) == 1) { - LBPairInformation memory _LBPairInformation = _LBPairsInfo[_tokenA][_tokenB][i]; - - LBPairsAvailable[_index] = LBPairInformation({ - binStep: i.safe16(), - LBPair: _LBPairInformation.LBPair, - createdByOwner: _LBPairInformation.createdByOwner, - ignoredForRouting: _LBPairInformation.ignoredForRouting - }); - if (++_index == _nbAvailable) break; + uint256 revisionNumber = _LBPairsInfos[_tokenA][_tokenB][i].length; + for (uint256 j = 0; j < revisionNumber; ++j) { + LBPairInformation memory _LBPairInformation = _LBPairsInfos[_tokenA][_tokenB][i][j]; + + LBPairsAvailable[_index++] = LBPairInformation({ + binStep: i.safe16(), + LBPair: _LBPairInformation.LBPair, + createdByOwner: _LBPairInformation.createdByOwner, + ignoredForRouting: _LBPairInformation.ignoredForRouting, + revisionIndex: _LBPairInformation.revisionIndex, + implementation: _LBPairInformation.implementation + }); + } + + if (_index == totalPairs) break; } } } @@ -217,7 +254,7 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @dev Needs to be called by the owner /// @param _LBPairImplementation The address of the implementation function setLBPairImplementation(address _LBPairImplementation) external override onlyOwner { - if (ILBPair(_LBPairImplementation).factory() != this) { + if (ILBPair(_LBPairImplementation).getFactory() != this) { revert LBFactory__LBPairSafetyCheckFailed(_LBPairImplementation); } @@ -242,8 +279,8 @@ contract LBFactory is PendingOwnable, ILBFactory { override returns (ILBPair _LBPair) { - address _owner = owner(); - if (!creationUnlocked && msg.sender != _owner) revert LBFactory__FunctionIsLockedForUsers(msg.sender); + // address _owner = owner(); + if (!creationUnlocked && msg.sender != owner()) revert LBFactory__FunctionIsLockedForUsers(msg.sender); address _LBPairImplementation = LBPairImplementation; @@ -254,34 +291,54 @@ contract LBFactory is PendingOwnable, ILBFactory { if (_tokenX == _tokenY) revert LBFactory__IdenticalAddresses(_tokenX); // safety check, making sure that the price can be calculated - BinHelper.getPriceFromId(_activeId, _binStep); + // BinHelper.getPriceFromId(_activeId, _binStep); - TODO - check if necessary // We sort token for storage efficiency, only one input needs to be stored because they are sorted (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); // single check is sufficient if (address(_tokenA) == address(0)) revert LBFactory__AddressZero(); - if (address(_LBPairsInfo[_tokenA][_tokenB][_binStep].LBPair) != address(0)) { + if (_LBPairsInfos[_tokenA][_tokenB][_binStep].length != 0) { revert LBFactory__LBPairAlreadyExists(_tokenX, _tokenY, _binStep); } - bytes32 _preset = _presets[_binStep]; - if (_preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); - - uint256 _sampleLifetime = _preset.decode(type(uint16).max, 240); + // uint256 _sampleLifetime = _preset.decode(type(uint16).max, 240); // We remove the bits that are not part of the feeParameters - _preset &= bytes32(uint256(type(uint144).max)); - - bytes32 _salt = keccak256(abi.encode(_tokenA, _tokenB, _binStep)); - _LBPair = ILBPair(Clones.cloneDeterministic(_LBPairImplementation, _salt)); + // _preset &= bytes32(uint256(type(uint144).max)); + { + bytes32 _salt = keccak256(abi.encode(_tokenA, _tokenB, _binStep, _REVISION_START_INDEX)); + _LBPair = ILBPair( + ImmutableClone.cloneDeterministic( + _LBPairImplementation, abi.encodePacked(_tokenX, _tokenY, _binStep), _salt + ) + ); + } - _LBPair.initialize(_tokenX, _tokenY, _activeId, uint16(_sampleLifetime), _preset); + { + bytes32 _preset = _presets[_binStep]; + if (_preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); + + _LBPair.initialize( + _preset.getBaseFactor(), + _preset.getFilterPeriod(), + _preset.getDecayPeriod(), + _preset.getReductionFactor(), + _preset.getVariableFeeControl(), + _preset.getProtocolShare(), + _preset.getMaxVolatilityAccumulated(), + _activeId + ); + } - _LBPairsInfo[_tokenA][_tokenB][_binStep] = LBPairInformation({ - binStep: _binStep, - LBPair: _LBPair, - createdByOwner: msg.sender == _owner, - ignoredForRouting: false - }); + _LBPairsInfos[_tokenA][_tokenB][_binStep].push( + LBPairInformation({ + binStep: _binStep, + LBPair: _LBPair, + createdByOwner: msg.sender == owner(), + ignoredForRouting: false, + revisionIndex: uint16(_REVISION_START_INDEX), + implementation: LBPairImplementation + }) + ); allLBPairs.push(_LBPair); @@ -299,6 +356,93 @@ contract LBFactory is PendingOwnable, ILBFactory { emit LBPairCreated(_tokenX, _tokenY, _binStep, _LBPair, allLBPairs.length - 1); + { + bytes32 _preset = _presets[_binStep]; + emit FeeParametersSet( + msg.sender, + _LBPair, + _binStep, + _preset.decode(type(uint16).max, 16), + _preset.decode(type(uint16).max, 32), + _preset.decode(type(uint16).max, 48), + _preset.decode(type(uint16).max, 64), + _preset.decode(type(uint24).max, 80), + _preset.decode(type(uint16).max, 104), + _preset.decode(type(uint24).max, 120) + ); + } + } + + /// @notice Function to create a new revision of a pair + /// Restricted to the owner + /// @param _tokenX The first token of the pair + /// @param _tokenY The second token of the pair + /// @param _binStep The binStep of the pair + /// @return _LBPair The new LBPair + function createLBPairRevision(IERC20 _tokenX, IERC20 _tokenY, uint16 _binStep) + external + override + onlyOwner + returns (ILBPair _LBPair) + { + (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); + + uint256 currentVersionNumber = _LBPairsInfos[_tokenA][_tokenB][_binStep].length; + if (currentVersionNumber == 0) revert LBFactory__LBPairDoesNotExists(_tokenX, _tokenY, _binStep); + + address _LBPairImplementation = LBPairImplementation; + + // Get latest version + LBPairInformation memory _LBPairInformation = + _LBPairsInfos[_tokenA][_tokenB][_binStep][currentVersionNumber - _REVISION_START_INDEX]; + + if (_LBPairInformation.implementation == _LBPairImplementation) { + revert LBFactory__SameImplementation(_LBPairImplementation); + } + + ILBPair _oldLBPair = _LBPairInformation.LBPair; + + bytes32 _preset = _presets[_binStep]; + + bytes32 _salt = keccak256(abi.encode(_tokenA, _tokenB, _binStep, ++currentVersionNumber)); + _LBPair = ILBPair( + ImmutableClone.cloneDeterministic( + _LBPairImplementation, abi.encodePacked(_tokenX, _tokenY, _binStep), _salt + ) + ); + + { + // (uint256 oracleSampleLifetime,,,,,,) = _oldLBPair.getOracleParameters(); + + // (,, uint256 activeId) = _oldLBPair.getReservesAndId(); + + _LBPair.initialize( + _preset.getBaseFactor(), + _preset.getFilterPeriod(), + _preset.getDecayPeriod(), + _preset.getReductionFactor(), + _preset.getVariableFeeControl(), + _preset.getProtocolShare(), + _preset.getMaxVolatilityAccumulated(), + _oldLBPair.getActiveId() + ); + + _LBPairsInfos[_tokenA][_tokenB][_binStep].push( + LBPairInformation({ + binStep: _binStep, + LBPair: _LBPair, + createdByOwner: true, + ignoredForRouting: false, + revisionIndex: uint16(currentVersionNumber), + implementation: LBPairImplementation + }) + ); + } + + allLBPairs.push(_LBPair); + + emit LBPairCreated(_tokenX, _tokenY, _binStep, _LBPair, allLBPairs.length - 1); + emit FeeParametersSet( msg.sender, _LBPair, @@ -317,20 +461,26 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @param _tokenX The address of the first token of the pair /// @param _tokenY The address of the second token of the pair /// @param _binStep The bin step in basis point of the pair + /// @param _revision The revision of the pair /// @param _ignored Whether to ignore (true) or not (false) the pair for routing - function setLBPairIgnored(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep, bool _ignored) + function setLBPairIgnored(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep, uint256 _revision, bool _ignored) external override onlyOwner { (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); - LBPairInformation memory _LBPairInformation = _LBPairsInfo[_tokenA][_tokenB][_binStep]; - if (address(_LBPairInformation.LBPair) == address(0)) revert LBFactory__AddressZero(); + uint256 revisionAmount = _LBPairsInfos[_tokenA][_tokenB][_binStep].length; + if (revisionAmount == 0 || _revision > revisionAmount) { + revert LBFactory__AddressZero(); + } + + LBPairInformation memory _LBPairInformation = + _LBPairsInfos[_tokenA][_tokenB][_binStep][_revision - _REVISION_START_INDEX]; if (_LBPairInformation.ignoredForRouting == _ignored) revert LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); - _LBPairsInfo[_tokenA][_tokenB][_binStep].ignoredForRouting = _ignored; + _LBPairsInfos[_tokenA][_tokenB][_binStep][_revision - _REVISION_START_INDEX].ignoredForRouting = _ignored; emit LBPairIgnoredStateChanged(_LBPairInformation.LBPair, _ignored); } @@ -420,6 +570,7 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @param _tokenX The address of the first token /// @param _tokenY The address of the second token /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) + /// @param _revision The revision of the LBPair /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam /// @param _decayPeriod The period where the accumulator value is halved @@ -431,6 +582,7 @@ contract LBFactory is PendingOwnable, ILBFactory { IERC20 _tokenX, IERC20 _tokenY, uint16 _binStep, + uint256 _revision, uint16 _baseFactor, uint16 _filterPeriod, uint16 _decayPeriod, @@ -439,12 +591,9 @@ contract LBFactory is PendingOwnable, ILBFactory { uint16 _protocolShare, uint24 _maxVolatilityAccumulated ) external override onlyOwner { - ILBPair _LBPair = _getLBPairInformation(_tokenX, _tokenY, _binStep).LBPair; - - if (address(_LBPair) == address(0)) revert LBFactory__LBPairNotCreated(_tokenX, _tokenY, _binStep); + ILBPair _LBPair = _getLBPairInformation(_tokenX, _tokenY, _binStep, _revision).LBPair; - bytes32 _packedFeeParameters = _getPackedFeeParameters( - _binStep, + _LBPair.setStaticFeeParameters( _baseFactor, _filterPeriod, _decayPeriod, @@ -454,8 +603,6 @@ contract LBFactory is PendingOwnable, ILBFactory { _maxVolatilityAccumulated ); - _LBPair.setFeesParameters(_packedFeeParameters); - emit FeeParametersSet( msg.sender, _LBPair, @@ -598,14 +745,20 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @param _tokenA The address of the first token of the pair /// @param _tokenB The address of the second token of the pair /// @param _binStep The bin step of the LBPair + /// @param _revision The revision of the LBPair /// @return The LBPairInformation - function _getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep) + function _getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep, uint256 _revision) private view returns (LBPairInformation memory) { (_tokenA, _tokenB) = _sortTokens(_tokenA, _tokenB); - return _LBPairsInfo[_tokenA][_tokenB][_binStep]; + + if (_LBPairsInfos[_tokenA][_tokenB][_binStep].length == 0) { + revert LBFactory__LBPairNotCreated(_tokenA, _tokenB, _binStep); + } + + return _LBPairsInfos[_tokenA][_tokenB][_binStep][_revision - _REVISION_START_INDEX]; } /// @notice Private view function to sort 2 tokens in ascending order diff --git a/src/LBPair.sol b/src/LBPair.sol index b2abdb80..83dea7c8 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -31,6 +31,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { using PackedUint128Math for bytes32; using PackedUint128Math for uint128; using PairParameterHelper for bytes32; + using PriceHelper for uint256; using PriceHelper for uint24; using SafeCast for uint256; using TreeMath for TreeMath.TreeUint24; @@ -42,7 +43,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { } modifier onlyProtocolFeeReceiver() { - if (msg.sender != _factory.getProtocolFeeRecipient()) revert LBPair__OnlyProtocolFeeReceiver(); + if (msg.sender != _factory.feeRecipient()) revert LBPair__OnlyProtocolFeeReceiver(); _; } @@ -335,9 +336,11 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { parameters.updateVolatilityParameters(id); - uint256 amountInToBin = swapForY - ? uint256(amountOutOfBin).shiftDivRoundUp(Constants.SCALE_OFFSET, price) - : uint256(amountOutOfBin).mulShiftRoundUp(price, Constants.SCALE_OFFSET); + uint128 amountInToBin = uint128( + swapForY + ? uint256(amountOutOfBin).shiftDivRoundUp(Constants.SCALE_OFFSET, price) + : uint256(amountOutOfBin).mulShiftRoundUp(price, Constants.SCALE_OFFSET) + ); uint128 totalFee = parameters.getTotalFee(binStep); uint128 feeAmount = amountOutOfBin.getFeeAmount(totalFee); @@ -376,7 +379,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { override returns (uint128 amountInLeft, uint128 amountOut, uint128 fee) { - (bytes32 amountsIn, bytes32 amountsOut, bytes32 fees) = (amountIn.encode(swapForY), 0, 0); + bytes32 amountsInLeft = amountIn.encode(!swapForY); bytes32 parameters = _parameters; uint8 binStep = _binStep(); @@ -391,16 +394,17 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { parameters = parameters.updateVolatilityAccumulated(id); (bytes32 amountsInToBin, bytes32 amountsOutOfBin, bytes32 totalFees) = - binReserves.getAmounts(parameters, binStep, swapForY, id, amountsIn); + binReserves.getAmounts(parameters, binStep, swapForY, id, amountsInLeft); if (amountsInToBin > 0) { - amountsIn = amountsIn.sub(amountsInToBin.add(totalFees)); - fees = fees.add(totalFees); - amountsOut = amountsOut.add(amountsOutOfBin); + amountsInLeft = amountsInLeft.sub(amountsInToBin.add(totalFees)); + amountOut += amountsOutOfBin.decode(swapForY); + + fee += totalFees.decode(!swapForY); } } - if (amountsIn == 0) { + if (amountsInLeft == 0) { break; } else { uint24 nextId = _getNextNonEmptyBin(swapForY, id); @@ -411,8 +415,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { } } - (amountInLeft, amountOut, fee) = - (amountsIn.decode(swapForY), amountsOut.decode(swapForY), fees.decode(swapForY)); + amountInLeft = amountsInLeft.decode(!swapForY); } /** @@ -782,7 +785,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { * @return The encoded fees amounts */ function _getFlashLoanFees(bytes32 amounts) private view returns (bytes32) { - uint128 fee = _factory.getFlashLoanFee(); + uint128 fee = uint128(_factory.flashLoanFee()); (uint128 x, uint128 y) = amounts.decode(); unchecked { diff --git a/src/LBQuoter.sol b/src/LBQuoter.sol index 387b1a0a..b492e49e 100644 --- a/src/LBQuoter.sol +++ b/src/LBQuoter.sol @@ -2,52 +2,61 @@ pragma solidity 0.8.10; -import "./LBErrors.sol"; import "./libraries/BinHelper.sol"; import "./libraries/JoeLibrary.sol"; -import "./libraries/Math512Bits.sol"; +import "./libraries/math/Uint256x256Math.sol"; import "./interfaces/IJoeFactory.sol"; import "./interfaces/IJoePair.sol"; import "./interfaces/ILBFactory.sol"; +import "./interfaces/ILBLegacyFactory.sol"; import "./interfaces/ILBRouter.sol"; +import {PriceHelper} from "./libraries/PriceHelper.sol"; + /// @title Liquidity Book Quoter /// @author Trader Joe /// @notice Helper contract to determine best path through multiple markets contract LBQuoter { - using Math512Bits for uint256; + using Uint256x256Math for uint256; + + error LBQuoter_InvalidLength(); /// @notice Dex V2 router address address public immutable routerV2; /// @notice Dex V1 factory address address public immutable factoryV1; /// @notice Dex V2 factory address + address public immutable legacyFactoryV2; + /// @notice Dex V2.1 factory address address public immutable factoryV2; struct Quote { address[] route; address[] pairs; - uint256[] binSteps; - uint256[] amounts; - uint256[] virtualAmountsWithoutSlippage; - uint256[] fees; + uint8[] binSteps; + uint256[] revisions; + uint128[] amounts; + uint128[] virtualAmountsWithoutSlippage; + uint128[] fees; } /// @notice Constructor /// @param _routerV2 Dex V2 router address /// @param _factoryV1 Dex V1 factory address - /// @param _factoryV2 Dex V2 factory address - constructor(address _routerV2, address _factoryV1, address _factoryV2) { - routerV2 = _routerV2; + /// @param _legacyFactoryV2 Dex V2 factory address + /// @param _factoryV2 Dex V2.1 factory address + constructor(address _factoryV1, address _legacyFactoryV2, address _factoryV2, address _routerV2) { factoryV1 = _factoryV1; + legacyFactoryV2 = _legacyFactoryV2; factoryV2 = _factoryV2; + routerV2 = _routerV2; } /// @notice Finds the best path given a list of tokens and the input amount wanted from the swap /// @param _route List of the tokens to go through /// @param _amountIn Swap amount in /// @return quote The Quote structure containing the necessary element to perform the swap - function findBestPathFromAmountIn(address[] calldata _route, uint256 _amountIn) + function findBestPathFromAmountIn(address[] calldata _route, uint128 _amountIn) public view returns (Quote memory quote) @@ -60,10 +69,11 @@ contract LBQuoter { uint256 swapLength = _route.length - 1; quote.pairs = new address[](swapLength); - quote.binSteps = new uint256[](swapLength); - quote.fees = new uint256[](swapLength); - quote.amounts = new uint256[](_route.length); - quote.virtualAmountsWithoutSlippage = new uint256[](_route.length); + quote.binSteps = new uint8[](swapLength); + quote.revisions = new uint256[](swapLength); + quote.fees = new uint128[](swapLength); + quote.amounts = new uint128[](_route.length); + quote.virtualAmountsWithoutSlippage = new uint128[](_route.length); quote.amounts[0] = _amountIn; quote.virtualAmountsWithoutSlippage[0] = _amountIn; @@ -76,38 +86,65 @@ contract LBQuoter { (uint256 reserveIn, uint256 reserveOut) = _getReserves(quote.pairs[i], _route[i], _route[i + 1]); if (reserveIn > 0 && reserveOut > 0) { - quote.amounts[i + 1] = JoeLibrary.getAmountOut(quote.amounts[i], reserveIn, reserveOut); - quote.virtualAmountsWithoutSlippage[i + 1] = - JoeLibrary.quote(quote.virtualAmountsWithoutSlippage[i] * 997, reserveIn * 1000, reserveOut); + quote.amounts[i + 1] = uint128(JoeLibrary.getAmountOut(quote.amounts[i], reserveIn, reserveOut)); + quote.virtualAmountsWithoutSlippage[i + 1] = uint128( + JoeLibrary.quote(quote.virtualAmountsWithoutSlippage[i] * 997, reserveIn * 1000, reserveOut) + ); quote.fees[i] = 0.003e18; // 0.3% } } // Fetch swaps for V2 - ILBFactory.LBPairInformation[] memory LBPairsAvailable = - ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i]), IERC20(_route[i + 1])); - - if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { - for (uint256 j; j < LBPairsAvailable.length; j++) { - if (!LBPairsAvailable[j].ignoredForRouting) { - bool swapForY = address(LBPairsAvailable[j].LBPair.tokenY()) == _route[i + 1]; - - try ILBRouter(routerV2).getSwapOut(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) - returns (uint256 swapAmountOut, uint256 fees) { - if (swapAmountOut > quote.amounts[i + 1]) { - quote.amounts[i + 1] = swapAmountOut; - quote.pairs[i] = address(LBPairsAvailable[j].LBPair); - quote.binSteps[i] = LBPairsAvailable[j].binStep; - - // Getting current price - (,, uint256 activeId) = LBPairsAvailable[j].LBPair.getReservesAndId(); - quote.virtualAmountsWithoutSlippage[i + 1] = _getV2Quote( - quote.virtualAmountsWithoutSlippage[i] - fees, activeId, quote.binSteps[i], swapForY - ); - - quote.fees[i] = (fees * 1e18) / quote.amounts[i]; // fee percentage in amountIn - } - } catch {} + ILBFactory.LBPairInformation[] memory LBPairsAvailable; + + for (uint256 k = 0; k < 2; k++) { + if (k == 0) { + LBPairsAvailable = ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i]), IERC20(_route[i + 1])); + } else { + ILBLegacyFactory.LBPairInformation[] memory LBPairsAvailableLegacy = + ILBLegacyFactory(legacyFactoryV2).getAllLBPairs(IERC20(_route[i]), IERC20(_route[i + 1])); + + LBPairsAvailable = new ILBFactory.LBPairInformation[](LBPairsAvailableLegacy.length); + for (uint256 l = 0; l < LBPairsAvailableLegacy.length; l++) { + LBPairsAvailable[l] = ILBFactory.LBPairInformation( + LBPairsAvailableLegacy[l].binStep, + ILBPair(address(LBPairsAvailableLegacy[l].LBPair)), + LBPairsAvailableLegacy[l].createdByOwner, + LBPairsAvailableLegacy[l].ignoredForRouting, + 0, + address(0) + ); + } + } + + if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { + for (uint256 j; j < LBPairsAvailable.length; j++) { + if (!LBPairsAvailable[j].ignoredForRouting) { + bool swapForY = address(LBPairsAvailable[j].LBPair.getTokenY()) == _route[i + 1]; + + try ILBRouter(routerV2).getSwapOut(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) + returns (uint128 amountInLeft, uint128 swapAmountOut, uint128 fees) { + if (swapAmountOut > quote.amounts[i + 1]) { + quote.amounts[i + 1] = swapAmountOut; + quote.pairs[i] = address(LBPairsAvailable[j].LBPair); + quote.binSteps[i] = uint8(LBPairsAvailable[j].binStep); + quote.revisions[i] = LBPairsAvailable[j].revisionIndex; + + // Getting current price + uint24 activeId = LBPairsAvailable[j].LBPair.getActiveId(); + quote.virtualAmountsWithoutSlippage[i + 1] = uint128( + _getV2Quote( + quote.virtualAmountsWithoutSlippage[i] - fees, + activeId, + quote.binSteps[i], + swapForY + ) + ); + + quote.fees[i] = (fees * 1e18) / quote.amounts[i]; // fee percentage in amountIn + } + } catch {} + } } } } @@ -118,7 +155,7 @@ contract LBQuoter { /// @param _route List of the tokens to go through /// @param _amountOut Swap amount out /// @return quote The Quote structure containing the necessary element to perform the swap - function findBestPathFromAmountOut(address[] calldata _route, uint256 _amountOut) + function findBestPathFromAmountOut(address[] calldata _route, uint128 _amountOut) public view returns (Quote memory quote) @@ -130,10 +167,11 @@ contract LBQuoter { uint256 swapLength = _route.length - 1; quote.pairs = new address[](swapLength); - quote.binSteps = new uint256[](swapLength); - quote.fees = new uint256[](swapLength); - quote.amounts = new uint256[](_route.length); - quote.virtualAmountsWithoutSlippage = new uint256[](_route.length); + quote.binSteps = new uint8[](swapLength); + quote.revisions = new uint256[](swapLength); + quote.fees = new uint128[](swapLength); + quote.amounts = new uint128[](_route.length); + quote.virtualAmountsWithoutSlippage = new uint128[](_route.length); quote.amounts[swapLength] = _amountOut; quote.virtualAmountsWithoutSlippage[swapLength] = _amountOut; @@ -145,39 +183,56 @@ contract LBQuoter { (uint256 reserveIn, uint256 reserveOut) = _getReserves(quote.pairs[i - 1], _route[i - 1], _route[i]); if (reserveIn > 0 && reserveOut > quote.amounts[i]) { - quote.amounts[i - 1] = JoeLibrary.getAmountIn(quote.amounts[i], reserveIn, reserveOut); - quote.virtualAmountsWithoutSlippage[i - 1] = - JoeLibrary.quote(quote.virtualAmountsWithoutSlippage[i] * 1000, reserveOut * 997, reserveIn) + 1; + quote.amounts[i - 1] = uint128(JoeLibrary.getAmountIn(quote.amounts[i], reserveIn, reserveOut)); + quote.virtualAmountsWithoutSlippage[i - 1] = uint128( + JoeLibrary.quote(quote.virtualAmountsWithoutSlippage[i] * 1000, reserveOut * 997, reserveIn) + 1 + ); quote.fees[i - 1] = 0.003e18; // 0.3% } } // Fetch swaps for V2 - ILBFactory.LBPairInformation[] memory LBPairsAvailable = - ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i - 1]), IERC20(_route[i])); - - if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { - for (uint256 j; j < LBPairsAvailable.length; j++) { - if (!LBPairsAvailable[j].ignoredForRouting) { - bool swapForY = address(LBPairsAvailable[j].LBPair.tokenY()) == _route[i]; - try ILBRouter(routerV2).getSwapIn(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) - returns (uint256 swapAmountIn, uint256 fees) { - if (swapAmountIn != 0 && (swapAmountIn < quote.amounts[i - 1] || quote.amounts[i - 1] == 0)) - { - quote.amounts[i - 1] = swapAmountIn; - quote.pairs[i - 1] = address(LBPairsAvailable[j].LBPair); - quote.binSteps[i - 1] = LBPairsAvailable[j].binStep; - - // Getting current price - (,, uint256 activeId) = LBPairsAvailable[j].LBPair.getReservesAndId(); - quote.virtualAmountsWithoutSlippage[i - 1] = _getV2Quote( - quote.virtualAmountsWithoutSlippage[i], activeId, quote.binSteps[i - 1], !swapForY - ) + fees; - - quote.fees[i - 1] = (fees * 1e18) / quote.amounts[i - 1]; // fee percentage in amountIn - } - } catch {} + ILBFactory.LBPairInformation[] memory LBPairsAvailable; + + for (uint256 k = 0; k < 2; k++) { + if (k == 0) { + LBPairsAvailable = ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i - 1]), IERC20(_route[i])); + } else { + LBPairsAvailable = + ILBFactory(legacyFactoryV2).getAllLBPairs(IERC20(_route[i - 1]), IERC20(_route[i])); + } + + if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { + for (uint256 j; j < LBPairsAvailable.length; j++) { + if (!LBPairsAvailable[j].ignoredForRouting) { + bool swapForY = address(LBPairsAvailable[j].LBPair.getTokenY()) == _route[i]; + try ILBRouter(routerV2).getSwapIn(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) + returns (uint128 swapAmountIn, uint128, uint128 fees) { + if ( + swapAmountIn != 0 + && (swapAmountIn < quote.amounts[i - 1] || quote.amounts[i - 1] == 0) + ) { + quote.amounts[i - 1] = swapAmountIn; + quote.pairs[i - 1] = address(LBPairsAvailable[j].LBPair); + quote.binSteps[i - 1] = uint8(LBPairsAvailable[j].binStep); + quote.revisions[i - 1] = LBPairsAvailable[j].revisionIndex; + + // Getting current price + uint24 activeId = LBPairsAvailable[j].LBPair.getActiveId(); + quote.virtualAmountsWithoutSlippage[i - 1] = uint128( + _getV2Quote( + quote.virtualAmountsWithoutSlippage[i], + activeId, + quote.binSteps[i - 1], + !swapForY + ) + ) + fees; + + quote.fees[i - 1] = (fees * 1e18) / quote.amounts[i - 1]; // fee percentage in amountIn + } + } catch {} + } } } } @@ -207,15 +262,15 @@ contract LBQuoter { /// @param _binStep Bin step of the considered pair /// @param _swapForY Boolean describing if we are swapping from X to Y or the opposite /// @return quote Amount Out if _amount was swapped with no slippage and no fees - function _getV2Quote(uint256 _amount, uint256 _activeId, uint256 _binStep, bool _swapForY) + function _getV2Quote(uint256 _amount, uint24 _activeId, uint8 _binStep, bool _swapForY) internal pure returns (uint256 quote) { if (_swapForY) { - quote = BinHelper.getPriceFromId(_activeId, _binStep).mulShiftRoundDown(_amount, Constants.SCALE_OFFSET); + quote = PriceHelper.getPriceFromId(_activeId, _binStep).mulShiftRoundDown(_amount, Constants.SCALE_OFFSET); } else { - quote = _amount.shiftDivRoundDown(Constants.SCALE_OFFSET, BinHelper.getPriceFromId(_activeId, _binStep)); + quote = _amount.shiftDivRoundDown(Constants.SCALE_OFFSET, PriceHelper.getPriceFromId(_activeId, _binStep)); } } } diff --git a/src/LBRouter.sol b/src/LBRouter.sol index d784e147..6ccb175b 100644 --- a/src/LBRouter.sol +++ b/src/LBRouter.sol @@ -4,17 +4,19 @@ pragma solidity 0.8.10; import "openzeppelin/token/ERC20/IERC20.sol"; -import "./LBErrors.sol"; import "./libraries/BinHelper.sol"; import "./libraries/Constants.sol"; import "./libraries/FeeHelper.sol"; import "./libraries/JoeLibrary.sol"; -import "./libraries/Math512Bits.sol"; -import "./libraries/SwapHelper.sol"; +import "./libraries/math/Uint256x256Math.sol"; +import "./libraries/math/PackedUint128Math.sol"; import "./libraries/TokenHelper.sol"; import "./interfaces/IJoePair.sol"; import "./interfaces/ILBToken.sol"; import "./interfaces/ILBRouter.sol"; +import "./interfaces/ILBLegacyFactory.sol"; + +import {LiquidityConfigurations} from "./libraries/math/LiquidityConfigurations.sol"; /// @title Liquidity Book Router /// @author Trader Joe @@ -22,12 +24,14 @@ import "./interfaces/ILBRouter.sol"; contract LBRouter is ILBRouter { using TokenHelper for IERC20; using TokenHelper for IWAVAX; - using FeeHelper for FeeHelper.FeeParameters; - using Math512Bits for uint256; - using SwapHelper for ILBPair.Bin; + using Uint256x256Math for uint256; + using LiquidityConfigurations for bytes32; + // using SwapHelper for ILBPair.Bin; using JoeLibrary for uint256; + using PackedUint128Math for bytes32; ILBFactory public immutable override factory; + ILBLegacyFactory public immutable legacyFactory; IJoeFactory public immutable override oldFactory; IWAVAX public immutable override wavax; @@ -41,10 +45,11 @@ contract LBRouter is ILBRouter { _; } - modifier verifyInputs(uint256[] memory _pairBinSteps, IERC20[] memory _tokenPath) { - if (_pairBinSteps.length == 0 || _pairBinSteps.length + 1 != _tokenPath.length) { - revert LBRouter__LengthsMismatch(); - } + modifier verifyPathValidity(Path memory _path) { + if ( + _path.pairBinSteps.length == 0 || _path.revisions.length != _path.pairBinSteps.length + || _path.pairBinSteps.length + 1 != _path.tokenPath.length + ) revert LBRouter__LengthsMismatch(); _; } @@ -52,8 +57,9 @@ contract LBRouter is ILBRouter { /// @param _factory LBFactory address /// @param _oldFactory Address of old factory (Joe V1) /// @param _wavax Address of WAVAX - constructor(ILBFactory _factory, IJoeFactory _oldFactory, IWAVAX _wavax) { + constructor(ILBFactory _factory, ILBLegacyFactory _legacyFactory, IJoeFactory _oldFactory, IWAVAX _wavax) { factory = _factory; + legacyFactory = _legacyFactory; oldFactory = _oldFactory; wavax = _wavax; } @@ -69,7 +75,7 @@ contract LBRouter is ILBRouter { /// @param _price The price of y per x (multiplied by 1e36) /// @return The id corresponding to this price function getIdFromPrice(ILBPair _LBPair, uint256 _price) external view override returns (uint24) { - return BinHelper.getIdFromPrice(_price, _LBPair.feeParameters().binStep); + return _LBPair.getIdFromPrice(_price); } /// @notice Returns the price corresponding to the inputted id @@ -77,7 +83,7 @@ contract LBRouter is ILBRouter { /// @param _id The id /// @return The price corresponding to this id function getPriceFromId(ILBPair _LBPair, uint24 _id) external view override returns (uint256) { - return BinHelper.getPriceFromId(_id, _LBPair.feeParameters().binStep); + return _LBPair.getPriceFromId(_id); } /// @notice Simulate a swap in @@ -85,104 +91,31 @@ contract LBRouter is ILBRouter { /// @param _amountOut The amount of token to receive /// @param _swapForY Whether you swap X for Y (true), or Y for X (false) /// @return amountIn The amount of token to send in order to receive _amountOut token - /// @return feesIn The amount of fees paid in token sent - function getSwapIn(ILBPair _LBPair, uint256 _amountOut, bool _swapForY) + /// @return amountOutLeft The amount of token Out that can't be returned due to a lack of liquidity + /// @return fee The amount of fees paid in token sent + function getSwapIn(ILBPair _LBPair, uint128 _amountOut, bool _swapForY) public view override - returns (uint256 amountIn, uint256 feesIn) + returns (uint128 amountIn, uint128 amountOutLeft, uint128 fee) { - (uint256 _pairReserveX, uint256 _pairReserveY, uint256 _activeId) = _LBPair.getReservesAndId(); - - if (_amountOut == 0 || (_swapForY ? _amountOut > _pairReserveY : _amountOut > _pairReserveX)) { - revert LBRouter__WrongAmounts(_amountOut, _swapForY ? _pairReserveY : _pairReserveX); - } // If this is wrong, then we're sure the amounts sent are wrong - - FeeHelper.FeeParameters memory _fp = _LBPair.feeParameters(); - _fp.updateVariableFeeParameters(_activeId); - - uint256 _amountOutOfBin; - uint256 _amountInWithFees; - uint256 _reserve; - // Performs the actual swap, bin per bin - // It uses the findFirstNonEmptyBinId function to make sure the bin we're currently looking at - // has liquidity in it. - while (true) { - { - (uint256 _reserveX, uint256 _reserveY) = _LBPair.getBin(uint24(_activeId)); - _reserve = _swapForY ? _reserveY : _reserveX; - } - uint256 _price = BinHelper.getPriceFromId(_activeId, _fp.binStep); - if (_reserve != 0) { - _amountOutOfBin = _amountOut >= _reserve ? _reserve : _amountOut; - uint256 _amountInToBin = _swapForY - ? _amountOutOfBin.shiftDivRoundUp(Constants.SCALE_OFFSET, _price) - : _price.mulShiftRoundUp(_amountOutOfBin, Constants.SCALE_OFFSET); - - // We update the fee, but we don't store the new volatility reference, volatility accumulated and indexRef to not penalize traders - _fp.updateVolatilityAccumulated(_activeId); - uint256 _fee = _fp.getFeeAmount(_amountInToBin); - _amountInWithFees = _amountInToBin + _fee; - - if (_amountInWithFees + _reserve > type(uint112).max) revert LBRouter__SwapOverflows(_activeId); - amountIn += _amountInWithFees; - feesIn += _fee; - _amountOut -= _amountOutOfBin; - } - - if (_amountOut != 0) { - _activeId = _LBPair.findFirstNonEmptyBinId(uint24(_activeId), _swapForY); - } else { - break; - } - } - if (_amountOut != 0) revert LBRouter__BrokenSwapSafetyCheck(); // Safety check, but should never be false as it would have reverted on transfer + (amountIn, amountOutLeft, fee) = _LBPair.getSwapIn(_amountOut, _swapForY); } /// @notice Simulate a swap out /// @param _LBPair The address of the LBPair /// @param _amountIn The amount of token sent /// @param _swapForY Whether you swap X for Y (true), or Y for X (false) + /// @return amountInLeft The amount of token In that can't be swapped due to a lack of liquidity /// @return amountOut The amount of token received if _amountIn tokenX are sent - /// @return feesIn The amount of fees paid in token sent - function getSwapOut(ILBPair _LBPair, uint256 _amountIn, bool _swapForY) + /// @return fee The amount of fees paid in token sent + function getSwapOut(ILBPair _LBPair, uint128 _amountIn, bool _swapForY) external view override - returns (uint256 amountOut, uint256 feesIn) + returns (uint128 amountInLeft, uint128 amountOut, uint128 fee) { - (,, uint256 _activeId) = _LBPair.getReservesAndId(); - - FeeHelper.FeeParameters memory _fp = _LBPair.feeParameters(); - _fp.updateVariableFeeParameters(_activeId); - ILBPair.Bin memory _bin; - - // Performs the actual swap, bin per bin - // It uses the findFirstNonEmptyBinId function to make sure the bin we're currently looking at - // has liquidity in it. - while (true) { - { - (uint256 _reserveX, uint256 _reserveY) = _LBPair.getBin(uint24(_activeId)); - _bin = ILBPair.Bin(uint112(_reserveX), uint112(_reserveY), 0, 0); - } - if (_bin.reserveX != 0 || _bin.reserveY != 0) { - (uint256 _amountInToBin, uint256 _amountOutOfBin, FeeHelper.FeesDistribution memory _fees) = - _bin.getAmounts(_fp, _activeId, _swapForY, _amountIn); - - if (_amountInToBin > type(uint112).max) revert LBRouter__BinReserveOverflows(_activeId); - - _amountIn -= _amountInToBin + _fees.total; - feesIn += _fees.total; - amountOut += _amountOutOfBin; - } - - if (_amountIn != 0) { - _activeId = _LBPair.findFirstNonEmptyBinId(uint24(_activeId), _swapForY); - } else { - break; - } - } - if (_amountIn != 0) revert LBRouter__TooMuchTokensIn(_amountIn); + (amountInLeft, amountOut, fee) = _LBPair.getSwapOut(_amountIn, _swapForY); } /// @notice Create a liquidity bin LBPair for _tokenX and _tokenY using the factory @@ -207,12 +140,15 @@ contract LBRouter is ILBRouter { function addLiquidity(LiquidityParameters calldata _liquidityParameters) external override - returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) + returns (bytes32[] memory depositIds, uint256[] memory liquidityMinted) { ILBPair _LBPair = _getLBPairInformation( - _liquidityParameters.tokenX, _liquidityParameters.tokenY, _liquidityParameters.binStep + _liquidityParameters.tokenX, + _liquidityParameters.tokenY, + _liquidityParameters.binStep, + _liquidityParameters.revision ); - if (_liquidityParameters.tokenX != _LBPair.tokenX()) revert LBRouter__WrongTokenOrder(); + if (_liquidityParameters.tokenX != _LBPair.getTokenX()) revert LBRouter__WrongTokenOrder(); _liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountX); _liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountY); @@ -229,12 +165,15 @@ contract LBRouter is ILBRouter { external payable override - returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) + returns (bytes32[] memory depositIds, uint256[] memory liquidityMinted) { ILBPair _LBPair = _getLBPairInformation( - _liquidityParameters.tokenX, _liquidityParameters.tokenY, _liquidityParameters.binStep + _liquidityParameters.tokenX, + _liquidityParameters.tokenY, + _liquidityParameters.binStep, + _liquidityParameters.revision ); - if (_liquidityParameters.tokenX != _LBPair.tokenX()) revert LBRouter__WrongTokenOrder(); + if (_liquidityParameters.tokenX != _LBPair.getTokenX()) revert LBRouter__WrongTokenOrder(); if (_liquidityParameters.tokenX == wavax && _liquidityParameters.amountX == msg.value) { _wavaxDepositAndTransfer(address(_LBPair), msg.value); @@ -272,6 +211,7 @@ contract LBRouter is ILBRouter { IERC20 _tokenX, IERC20 _tokenY, uint16 _binStep, + uint256 _revision, uint256 _amountXMin, uint256 _amountYMin, uint256[] memory _ids, @@ -279,8 +219,8 @@ contract LBRouter is ILBRouter { address _to, uint256 _deadline ) external override ensure(_deadline) returns (uint256 amountX, uint256 amountY) { - ILBPair _LBPair = _getLBPairInformation(_tokenX, _tokenY, _binStep); - bool _isWrongOrder = _tokenX != _LBPair.tokenX(); + ILBPair _LBPair = _getLBPairInformation(_tokenX, _tokenY, _binStep, _revision); + bool _isWrongOrder = _tokenX != _LBPair.getTokenX(); if (_isWrongOrder) (_amountXMin, _amountYMin) = (_amountYMin, _amountXMin); @@ -306,6 +246,7 @@ contract LBRouter is ILBRouter { function removeLiquidityAVAX( IERC20 _token, uint16 _binStep, + uint256 _revision, uint256 _amountTokenMin, uint256 _amountAVAXMin, uint256[] memory _ids, @@ -313,10 +254,11 @@ contract LBRouter is ILBRouter { address payable _to, uint256 _deadline ) external override ensure(_deadline) returns (uint256 amountToken, uint256 amountAVAX) { - ILBPair _LBPair = _getLBPairInformation(_token, IERC20(wavax), _binStep); + ILBPair _LBPair = _getLBPairInformation(_token, IERC20(wavax), _binStep, _revision); - bool _isAVAXTokenY = IERC20(wavax) == _LBPair.tokenY(); { + bool _isAVAXTokenY = IERC20(wavax) == _LBPair.getTokenY(); + if (!_isAVAXTokenY) { (_amountTokenMin, _amountAVAXMin) = (_amountAVAXMin, _amountTokenMin); } @@ -336,24 +278,21 @@ contract LBRouter is ILBRouter { /// @notice Swaps exact tokens for tokens while performing safety checks /// @param _amountIn The amount of token to send /// @param _amountOutMin The min amount of token to receive - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` /// @param _to The address of the recipient /// @param _deadline The deadline of the tx /// @return amountOut Output amount of the swap function swapExactTokensForTokens( uint256 _amountIn, uint256 _amountOutMin, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, + Path memory _path, address _to, uint256 _deadline - ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + ) external override ensure(_deadline) verifyPathValidity(_path) returns (uint256 amountOut) { + address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); - _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + _path.tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); - amountOut = _swapExactTokensForTokens(_amountIn, _pairs, _pairBinSteps, _tokenPath, _to); + amountOut = _swapExactTokensForTokens(_amountIn, _pairs, _path.pairBinSteps, _path.tokenPath, _to); if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); } @@ -361,28 +300,25 @@ contract LBRouter is ILBRouter { /// @notice Swaps exact tokens for AVAX while performing safety checks /// @param _amountIn The amount of token to send /// @param _amountOutMinAVAX The min amount of AVAX to receive - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` /// @param _to The address of the recipient /// @param _deadline The deadline of the tx /// @return amountOut Output amount of the swap function swapExactTokensForAVAX( uint256 _amountIn, uint256 _amountOutMinAVAX, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, + Path memory _path, address payable _to, uint256 _deadline - ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { - if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { - revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); + ) external override ensure(_deadline) verifyPathValidity(_path) returns (uint256 amountOut) { + if (_path.tokenPath[_path.pairBinSteps.length] != IERC20(wavax)) { + revert LBRouter__InvalidTokenPath(address(_path.tokenPath[_path.pairBinSteps.length])); } - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); - _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + _path.tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); - amountOut = _swapExactTokensForTokens(_amountIn, _pairs, _pairBinSteps, _tokenPath, address(this)); + amountOut = _swapExactTokensForTokens(_amountIn, _pairs, _path.pairBinSteps, _path.tokenPath, address(this)); if (_amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMinAVAX, amountOut); @@ -392,97 +328,78 @@ contract LBRouter is ILBRouter { /// @notice Swaps exact AVAX for tokens while performing safety checks /// @param _amountOutMin The min amount of token to receive - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` /// @param _to The address of the recipient /// @param _deadline The deadline of the tx /// @return amountOut Output amount of the swap - function swapExactAVAXForTokens( - uint256 _amountOutMin, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, - address _to, - uint256 _deadline - ) external payable override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { - if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); + function swapExactAVAXForTokens(uint256 _amountOutMin, Path memory _path, address _to, uint256 _deadline) + external + payable + override + ensure(_deadline) + verifyPathValidity(_path) + returns (uint256 amountOut) + { + if (_path.tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_path.tokenPath[0])); - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); _wavaxDepositAndTransfer(_pairs[0], msg.value); - amountOut = _swapExactTokensForTokens(msg.value, _pairs, _pairBinSteps, _tokenPath, _to); + amountOut = _swapExactTokensForTokens(msg.value, _pairs, _path.pairBinSteps, _path.tokenPath, _to); if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); } /// @notice Swaps tokens for exact tokens while performing safety checks - /// @param _amountOut The amount of token to receive - /// @param _amountInMax The max amount of token to send - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx - /// @return amountsIn Input amounts for every step of the swap function swapTokensForExactTokens( uint256 _amountOut, uint256 _amountInMax, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, + Path memory _path, address _to, uint256 _deadline - ) - external - override - ensure(_deadline) - verifyInputs(_pairBinSteps, _tokenPath) - returns (uint256[] memory amountsIn) - { - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountOut); + ) external override ensure(_deadline) verifyPathValidity(_path) returns (uint256[] memory amountsIn) { + address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); - if (amountsIn[0] > _amountInMax) revert LBRouter__MaxAmountInExceeded(_amountInMax, amountsIn[0]); + { + amountsIn = _getAmountsIn(_path.pairBinSteps, _pairs, _path.tokenPath, _amountOut); - _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], amountsIn[0]); + if (amountsIn[0] > _amountInMax) revert LBRouter__MaxAmountInExceeded(_amountInMax, amountsIn[0]); - uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, _to); + _path.tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], amountsIn[0]); - if (_amountOutReal < _amountOut) revert LBRouter__InsufficientAmountOut(_amountOut, _amountOutReal); + uint256 _amountOutReal = + _swapTokensForExactTokens(_pairs, _path.pairBinSteps, _path.tokenPath, amountsIn, _to); + + if (_amountOutReal < _amountOut) revert LBRouter__InsufficientAmountOut(_amountOut, _amountOutReal); + } } /// @notice Swaps tokens for exact AVAX while performing safety checks /// @param _amountAVAXOut The amount of AVAX to receive /// @param _amountInMax The max amount of token to send - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` /// @param _to The address of the recipient /// @param _deadline The deadline of the tx - /// @return amountsIn Input amounts for every step of the swap + /// @return amountsIn _path amounts for every step of the swap function swapTokensForExactAVAX( uint256 _amountAVAXOut, uint256 _amountInMax, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, + Path memory _path, address payable _to, uint256 _deadline - ) - external - override - ensure(_deadline) - verifyInputs(_pairBinSteps, _tokenPath) - returns (uint256[] memory amountsIn) - { - if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { - revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); + ) external override ensure(_deadline) verifyPathValidity(_path) returns (uint256[] memory amountsIn) { + if (_path.tokenPath[_path.pairBinSteps.length] != IERC20(wavax)) { + revert LBRouter__InvalidTokenPath(address(_path.tokenPath[_path.pairBinSteps.length])); } - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountAVAXOut); + address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); + amountsIn = _getAmountsIn(_path.pairBinSteps, _pairs, _path.tokenPath, _amountAVAXOut); if (amountsIn[0] > _amountInMax) revert LBRouter__MaxAmountInExceeded(_amountInMax, amountsIn[0]); - _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], amountsIn[0]); + _path.tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], amountsIn[0]); - uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, address(this)); + uint256 _amountOutReal = + _swapTokensForExactTokens(_pairs, _path.pairBinSteps, _path.tokenPath, amountsIn, address(this)); if (_amountOutReal < _amountAVAXOut) revert LBRouter__InsufficientAmountOut(_amountAVAXOut, _amountOutReal); @@ -493,35 +410,27 @@ contract LBRouter is ILBRouter { /// @notice Swaps AVAX for exact tokens while performing safety checks /// @dev Will refund any AVAX amount sent in excess to `msg.sender` /// @param _amountOut The amount of tokens to receive - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` /// @param _to The address of the recipient /// @param _deadline The deadline of the tx - /// @return amountsIn Input amounts for every step of the swap - function swapAVAXForExactTokens( - uint256 _amountOut, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, - address _to, - uint256 _deadline - ) + /// @return amountsIn _path amounts for every step of the swap + function swapAVAXForExactTokens(uint256 _amountOut, Path memory _path, address _to, uint256 _deadline) external payable override ensure(_deadline) - verifyInputs(_pairBinSteps, _tokenPath) + verifyPathValidity(_path) returns (uint256[] memory amountsIn) { - if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); + if (_path.tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_path.tokenPath[0])); - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); - amountsIn = _getAmountsIn(_pairBinSteps, _pairs, _tokenPath, _amountOut); + address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); + amountsIn = _getAmountsIn(_path.pairBinSteps, _pairs, _path.tokenPath, _amountOut); if (amountsIn[0] > msg.value) revert LBRouter__MaxAmountInExceeded(msg.value, amountsIn[0]); _wavaxDepositAndTransfer(_pairs[0], amountsIn[0]); - uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _pairBinSteps, _tokenPath, amountsIn, _to); + uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _path.pairBinSteps, _path.tokenPath, amountsIn, _to); if (_amountOutReal < _amountOut) revert LBRouter__InsufficientAmountOut(_amountOut, _amountOutReal); @@ -531,28 +440,25 @@ contract LBRouter is ILBRouter { /// @notice Swaps exact tokens for tokens while performing safety checks supporting for fee on transfer tokens /// @param _amountIn The amount of token to send /// @param _amountOutMin The min amount of token to receive - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` /// @param _to The address of the recipient /// @param _deadline The deadline of the tx /// @return amountOut Output amount of the swap function swapExactTokensForTokensSupportingFeeOnTransferTokens( uint256 _amountIn, uint256 _amountOutMin, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, + Path memory _path, address _to, uint256 _deadline - ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + ) external override ensure(_deadline) verifyPathValidity(_path) returns (uint256 amountOut) { + address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); - IERC20 _targetToken = _tokenPath[_pairs.length]; + IERC20 _targetToken = _path.tokenPath[_pairs.length]; uint256 _balanceBefore = _targetToken.balanceOf(_to); - _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + _path.tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); - _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, _to); + _swapSupportingFeeOnTransferTokens(_pairs, _path.pairBinSteps, _path.tokenPath, _to); amountOut = _targetToken.balanceOf(_to) - _balanceBefore; if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); @@ -561,30 +467,27 @@ contract LBRouter is ILBRouter { /// @notice Swaps exact tokens for AVAX while performing safety checks supporting for fee on transfer tokens /// @param _amountIn The amount of token to send /// @param _amountOutMinAVAX The min amount of AVAX to receive - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` /// @param _to The address of the recipient /// @param _deadline The deadline of the tx /// @return amountOut Output amount of the swap function swapExactTokensForAVAXSupportingFeeOnTransferTokens( uint256 _amountIn, uint256 _amountOutMinAVAX, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, + Path memory _path, address payable _to, uint256 _deadline - ) external override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { - if (_tokenPath[_pairBinSteps.length] != IERC20(wavax)) { - revert LBRouter__InvalidTokenPath(address(_tokenPath[_pairBinSteps.length])); + ) external override ensure(_deadline) verifyPathValidity(_path) returns (uint256 amountOut) { + if (_path.tokenPath[_path.pairBinSteps.length] != IERC20(wavax)) { + revert LBRouter__InvalidTokenPath(address(_path.tokenPath[_path.pairBinSteps.length])); } - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); uint256 _balanceBefore = wavax.balanceOf(address(this)); - _tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + _path.tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); - _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, address(this)); + _swapSupportingFeeOnTransferTokens(_pairs, _path.pairBinSteps, _path.tokenPath, address(this)); amountOut = wavax.balanceOf(address(this)) - _balanceBefore; if (_amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMinAVAX, amountOut); @@ -595,29 +498,26 @@ contract LBRouter is ILBRouter { /// @notice Swaps exact AVAX for tokens while performing safety checks supporting for fee on transfer tokens /// @param _amountOutMin The min amount of token to receive - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` /// @param _to The address of the recipient /// @param _deadline The deadline of the tx /// @return amountOut Output amount of the swap function swapExactAVAXForTokensSupportingFeeOnTransferTokens( uint256 _amountOutMin, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, + Path memory _path, address _to, uint256 _deadline - ) external payable override ensure(_deadline) verifyInputs(_pairBinSteps, _tokenPath) returns (uint256 amountOut) { - if (_tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_tokenPath[0])); + ) external payable override ensure(_deadline) verifyPathValidity(_path) returns (uint256 amountOut) { + if (_path.tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_path.tokenPath[0])); - address[] memory _pairs = _getPairs(_pairBinSteps, _tokenPath); + address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); - IERC20 _targetToken = _tokenPath[_pairs.length]; + IERC20 _targetToken = _path.tokenPath[_pairs.length]; uint256 _balanceBefore = _targetToken.balanceOf(_to); _wavaxDepositAndTransfer(_pairs[0], msg.value); - _swapSupportingFeeOnTransferTokens(_pairs, _pairBinSteps, _tokenPath, _to); + _swapSupportingFeeOnTransferTokens(_pairs, _path.pairBinSteps, _path.tokenPath, _to); amountOut = _targetToken.balanceOf(_to) - _balanceBefore; if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); @@ -649,18 +549,18 @@ contract LBRouter is ILBRouter { override onlyFactoryOwner { - _lbToken.safeBatchTransferFrom(address(this), _to, _ids, _amounts); + _lbToken.batchTransferFrom(address(this), _to, _ids, _amounts); } /// @notice Helper function to add liquidity /// @param _liq The liquidity parameter /// @param _LBPair LBPair where liquidity is deposited - /// @return depositIds Bin ids where the liquidity was actually deposited + /// @return liquidityConfigs Bin ids where the liquidity was actually deposited /// @return liquidityMinted Amounts of LBToken minted for each bin function _addLiquidity(LiquidityParameters calldata _liq, ILBPair _LBPair) private ensure(_liq.deadline) - returns (uint256[] memory depositIds, uint256[] memory liquidityMinted) + returns (bytes32[] memory liquidityConfigs, uint256[] memory liquidityMinted) { unchecked { if (_liq.deltaIds.length != _liq.distributionX.length && _liq.deltaIds.length != _liq.distributionY.length) @@ -672,23 +572,25 @@ contract LBRouter is ILBRouter { revert LBRouter__IdDesiredOverflows(_liq.activeIdDesired, _liq.idSlippage); } - (,, uint256 _activeId) = _LBPair.getReservesAndId(); + uint256 _activeId = _LBPair.getActiveId(); if ( _liq.activeIdDesired + _liq.idSlippage < _activeId || _activeId + _liq.idSlippage < _liq.activeIdDesired ) revert LBRouter__IdSlippageCaught(_liq.activeIdDesired, _liq.idSlippage, _activeId); - depositIds = new uint256[](_liq.deltaIds.length); - for (uint256 i; i < depositIds.length; ++i) { + liquidityConfigs = new bytes32[](_liq.deltaIds.length); + for (uint256 i; i < liquidityConfigs.length; ++i) { int256 _id = int256(_activeId) + _liq.deltaIds[i]; if (_id < 0 || uint256(_id) > type(uint24).max) revert LBRouter__IdOverflows(_id); - depositIds[i] = uint256(_id); + liquidityConfigs[i] = LiquidityConfigurations.encodeParams( + uint64(_liq.distributionX[i]), uint64(_liq.distributionY[i]), uint24(uint256(_id)) + ); } uint256 _amountXAdded; uint256 _amountYAdded; - (_amountXAdded, _amountYAdded, liquidityMinted) = - _LBPair.mint(depositIds, _liq.distributionX, _liq.distributionY, _liq.to); + (bytes32 amountsReceived, bytes32 amountsLeft, uint256[] memory liquidityMinted) = + _LBPair.mint(msg.sender, liquidityConfigs, _liq.to); if (_amountXAdded < _liq.amountXMin || _amountYAdded < _liq.amountYMin) { revert LBRouter__AmountSlippageCaught(_liq.amountXMin, _amountXAdded, _liq.amountYMin, _amountYAdded); @@ -697,37 +599,38 @@ contract LBRouter is ILBRouter { } /// @notice Helper function to return the amounts in - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param pairBinSteps The bin step of the pairs (0: V1, other values will use V2) /// @param _pairs The list of pairs - /// @param _tokenPath The swap path + /// @param tokenPath The swap path /// @param _amountOut The amount out /// @return amountsIn The list of amounts in function _getAmountsIn( - uint256[] memory _pairBinSteps, + uint256[] memory pairBinSteps, address[] memory _pairs, - IERC20[] memory _tokenPath, + IERC20[] memory tokenPath, uint256 _amountOut ) private view returns (uint256[] memory amountsIn) { - amountsIn = new uint256[](_tokenPath.length); - // Avoid doing -1, as `_pairs.length == _pairBinSteps.length-1` + amountsIn = new uint256[](tokenPath.length); + // Avoid doing -1, as `_pairs.length == pairBinSteps.length-1` amountsIn[_pairs.length] = _amountOut; for (uint256 i = _pairs.length; i != 0; i--) { - IERC20 _token = _tokenPath[i - 1]; - uint256 _binStep = _pairBinSteps[i - 1]; + IERC20 _token = tokenPath[i - 1]; + uint256 _binStep = pairBinSteps[i - 1]; address _pair = _pairs[i - 1]; if (_binStep == 0) { (uint256 _reserveIn, uint256 _reserveOut,) = IJoePair(_pair).getReserves(); - if (_token > _tokenPath[i]) { + if (_token > tokenPath[i]) { (_reserveIn, _reserveOut) = (_reserveOut, _reserveIn); } uint256 amountOut_ = amountsIn[i]; - amountsIn[i - 1] = amountOut_.getAmountIn(_reserveIn, _reserveOut); + amountsIn[i - 1] = uint128(uint256(amountOut_).getAmountIn(_reserveIn, _reserveOut)); } else { - (amountsIn[i - 1],) = getSwapIn(ILBPair(_pair), amountsIn[i], ILBPair(_pair).tokenX() == _token); + (amountsIn[i - 1],,) = + getSwapIn(ILBPair(_pair), uint128(amountsIn[i]), ILBPair(_pair).getTokenX() == _token); } } } @@ -749,8 +652,8 @@ contract LBRouter is ILBRouter { uint256[] memory _amounts, address _to ) private returns (uint256 amountX, uint256 amountY) { - ILBToken(address(_LBPair)).safeBatchTransferFrom(msg.sender, address(_LBPair), _ids, _amounts); - (amountX, amountY) = _LBPair.burn(_ids, _amounts, _to); + ILBToken(address(_LBPair)).batchTransferFrom(msg.sender, address(_LBPair), _ids, _amounts); + (bytes32[] memory amounts) = _LBPair.burn(msg.sender, _to, _ids, _amounts); if (amountX < _amountXMin || amountY < _amountYMin) { revert LBRouter__AmountSlippageCaught(_amountXMin, amountX, _amountYMin, amountY); } @@ -759,15 +662,15 @@ contract LBRouter is ILBRouter { /// @notice Helper function to swap exact tokens for tokens /// @param _amountIn The amount of token sent /// @param _pairs The list of pairs - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param tokenPath The swap path using the binSteps following `pairBinSteps` /// @param _to The address of the recipient /// @return amountOut The amount of token sent to `_to` function _swapExactTokensForTokens( uint256 _amountIn, address[] memory _pairs, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, address _to ) private returns (uint256 amountOut) { IERC20 _token; @@ -775,16 +678,16 @@ contract LBRouter is ILBRouter { address _recipient; address _pair; - IERC20 _tokenNext = _tokenPath[0]; + IERC20 _tokenNext = tokenPath[0]; amountOut = _amountIn; unchecked { for (uint256 i; i < _pairs.length; ++i) { _pair = _pairs[i]; - _binStep = _pairBinSteps[i]; + _binStep = pairBinSteps[i]; _token = _tokenNext; - _tokenNext = _tokenPath[i + 1]; + _tokenNext = tokenPath[i + 1]; _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; @@ -799,9 +702,9 @@ contract LBRouter is ILBRouter { IJoePair(_pair).swap(amountOut, 0, _recipient, ""); } } else { - bool _swapForY = _tokenNext == ILBPair(_pair).tokenY(); + bool _swapForY = _tokenNext == ILBPair(_pair).getTokenY(); - (uint256 _amountXOut, uint256 _amountYOut) = ILBPair(_pair).swap(_swapForY, _recipient); + (uint256 _amountXOut, uint256 _amountYOut) = ILBPair(_pair).swap(_swapForY, _recipient).decode(); if (_swapForY) amountOut = _amountYOut; else amountOut = _amountXOut; @@ -812,15 +715,15 @@ contract LBRouter is ILBRouter { /// @notice Helper function to swap tokens for exact tokens /// @param _pairs The array of pairs - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param tokenPath The swap path using the binSteps following `pairBinSteps` /// @param _amountsIn The list of amounts in /// @param _to The address of the recipient /// @return amountOut The amount of token sent to `_to` function _swapTokensForExactTokens( address[] memory _pairs, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, uint256[] memory _amountsIn, address _to ) private returns (uint256 amountOut) { @@ -829,15 +732,15 @@ contract LBRouter is ILBRouter { address _recipient; address _pair; - IERC20 _tokenNext = _tokenPath[0]; + IERC20 _tokenNext = tokenPath[0]; unchecked { for (uint256 i; i < _pairs.length; ++i) { _pair = _pairs[i]; - _binStep = _pairBinSteps[i]; + _binStep = pairBinSteps[i]; _token = _tokenNext; - _tokenNext = _tokenPath[i + 1]; + _tokenNext = tokenPath[i + 1]; _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; @@ -849,9 +752,9 @@ contract LBRouter is ILBRouter { IJoePair(_pair).swap(amountOut, 0, _recipient, ""); } } else { - bool _swapForY = _tokenNext == ILBPair(_pair).tokenY(); + bool _swapForY = _tokenNext == ILBPair(_pair).getTokenY(); - (uint256 _amountXOut, uint256 _amountYOut) = ILBPair(_pair).swap(_swapForY, _recipient); + (uint256 _amountXOut, uint256 _amountYOut) = ILBPair(_pair).swap(_swapForY, _recipient).decode(); if (_swapForY) amountOut = _amountYOut; else amountOut = _amountXOut; @@ -862,13 +765,13 @@ contract LBRouter is ILBRouter { /// @notice Helper function to swap exact tokens supporting for fee on transfer tokens /// @param _pairs The list of pairs - /// @param _pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _tokenPath The swap path using the binSteps following `_pairBinSteps` + /// @param pairBinSteps The bin step of the pairs (0: V1, other values will use V2) + /// @param tokenPath The swap path using the binSteps following `pairBinSteps` /// @param _to The address of the recipient function _swapSupportingFeeOnTransferTokens( address[] memory _pairs, - uint256[] memory _pairBinSteps, - IERC20[] memory _tokenPath, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, address _to ) private { IERC20 _token; @@ -876,15 +779,15 @@ contract LBRouter is ILBRouter { address _recipient; address _pair; - IERC20 _tokenNext = _tokenPath[0]; + IERC20 _tokenNext = tokenPath[0]; unchecked { for (uint256 i; i < _pairs.length; ++i) { _pair = _pairs[i]; - _binStep = _pairBinSteps[i]; + _binStep = pairBinSteps[i]; _token = _tokenNext; - _tokenNext = _tokenPath[i + 1]; + _tokenNext = tokenPath[i + 1]; _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; @@ -902,7 +805,7 @@ contract LBRouter is ILBRouter { IJoePair(_pair).swap(_amountOut, 0, _recipient, ""); } } else { - ILBPair(_pair).swap(_tokenNext == ILBPair(_pair).tokenY(), _recipient); + ILBPair(_pair).swap(_tokenNext == ILBPair(_pair).getTokenY(), _recipient); } } } @@ -914,8 +817,18 @@ contract LBRouter is ILBRouter { /// @param _tokenY The address of the tokenY /// @param _binStep The bin step of the LBPair /// @return The address of the LBPair - function _getLBPairInformation(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep) private view returns (ILBPair) { - ILBPair _LBPair = factory.getLBPairInformation(_tokenX, _tokenY, _binStep).LBPair; + function _getLBPairInformation(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep, uint256 _revision) + private + view + returns (ILBPair) + { + ILBPair _LBPair; + if (_revision == 0) { + _LBPair = legacyFactory.getLBPairInformation(_tokenX, _tokenY, _binStep).LBPair; + } else { + _LBPair = factory.getLBPairInformation(_tokenX, _tokenY, _binStep, _revision).LBPair; + } + if (address(_LBPair) == address(0)) { revert LBRouter__PairNotCreated(address(_tokenX), address(_tokenY), _binStep); } @@ -928,30 +841,34 @@ contract LBRouter is ILBRouter { /// @param _tokenX The address of the tokenX /// @param _tokenY The address of the tokenY /// @return _pair The address of the pair of binStep `_binStep` - function _getPair(uint256 _binStep, IERC20 _tokenX, IERC20 _tokenY) private view returns (address _pair) { + function _getPair(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep, uint256 _revision) + private + view + returns (address _pair) + { if (_binStep == 0) { _pair = oldFactory.getPair(address(_tokenX), address(_tokenY)); if (_pair == address(0)) revert LBRouter__PairNotCreated(address(_tokenX), address(_tokenY), _binStep); } else { - _pair = address(_getLBPairInformation(_tokenX, _tokenY, _binStep)); + _pair = address(_getLBPairInformation(_tokenX, _tokenY, _binStep, _revision)); } } - function _getPairs(uint256[] memory _pairBinSteps, IERC20[] memory _tokenPath) + function _getPairs(uint256[] memory pairBinSteps, uint256[] memory revisions, IERC20[] memory tokenPath) private view returns (address[] memory pairs) { - pairs = new address[](_pairBinSteps.length); + pairs = new address[](pairBinSteps.length); IERC20 _token; - IERC20 _tokenNext = _tokenPath[0]; + IERC20 _tokenNext = tokenPath[0]; unchecked { for (uint256 i; i < pairs.length; ++i) { _token = _tokenNext; - _tokenNext = _tokenPath[i + 1]; + _tokenNext = tokenPath[i + 1]; - pairs[i] = _getPair(_pairBinSteps[i], _token, _tokenNext); + pairs[i] = _getPair(_token, _tokenNext, pairBinSteps[i], revisions[i]); } } } diff --git a/src/interfaces/ILBFactory.sol b/src/interfaces/ILBFactory.sol index ef54c033..e320ae27 100644 --- a/src/interfaces/ILBFactory.sol +++ b/src/interfaces/ILBFactory.sol @@ -11,16 +11,45 @@ import "./IPendingOwnable.sol"; /// @author Trader Joe /// @notice Required interface of LBFactory contract interface ILBFactory is IPendingOwnable { + error LBFactory__IdenticalAddresses(IERC20 token); + error LBFactory__QuoteAssetNotWhitelisted(IERC20 quoteAsset); + error LBFactory__QuoteAssetAlreadyWhitelisted(IERC20 quoteAsset); + error LBFactory__AddressZero(); + error LBFactory__LBPairAlreadyExists(IERC20 tokenX, IERC20 tokenY, uint256 _binStep); + error LBFactory__LBPairDoesNotExists(IERC20 tokenX, IERC20 tokenY, uint256 _binStep); + error LBFactory__LBPairNotCreated(IERC20 tokenX, IERC20 tokenY, uint256 binStep); + error LBFactory__DecreasingPeriods(uint16 filterPeriod, uint16 decayPeriod); + error LBFactory__ReductionFactorOverflows(uint16 reductionFactor, uint256 max); + error LBFactory__VariableFeeControlOverflows(uint16 variableFeeControl, uint256 max); + error LBFactory__BaseFeesBelowMin(uint256 baseFees, uint256 minBaseFees); + error LBFactory__FeesAboveMax(uint256 fees, uint256 maxFees); + error LBFactory__FlashLoanFeeAboveMax(uint256 fees, uint256 maxFees); + error LBFactory__BinStepRequirementsBreached(uint256 lowerBound, uint16 binStep, uint256 higherBound); + error LBFactory__ProtocolShareOverflows(uint16 protocolShare, uint256 max); + error LBFactory__FunctionIsLockedForUsers(address user); + error LBFactory__FactoryLockIsAlreadyInTheSameState(); + error LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); + error LBFactory__BinStepHasNoPreset(uint256 binStep); + error LBFactory__SameFeeRecipient(address feeRecipient); + error LBFactory__SameFlashLoanFee(uint256 flashLoanFee); + error LBFactory__LBPairSafetyCheckFailed(address LBPairImplementation); + error LBFactory__SameImplementation(address LBPairImplementation); + error LBFactory__ImplementationNotSet(); + /// @dev Structure to store the LBPair information, such as: /// - binStep: The bin step of the LBPair /// - LBPair: The address of the LBPair /// - createdByOwner: Whether the pair was created by the owner of the factory /// - ignoredForRouting: Whether the pair is ignored for routing or not. An ignored pair will not be explored during routes finding + /// - revisionIndex: The revision index of the LBPair + /// - implementation: The implementation address of the LBPair struct LBPairInformation { uint16 binStep; ILBPair LBPair; bool createdByOwner; bool ignoredForRouting; + uint16 revisionIndex; + address implementation; } event LBPairCreated( @@ -94,7 +123,9 @@ interface ILBFactory is IPendingOwnable { function getNumberOfLBPairs() external view returns (uint256); - function getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep) + function getNumberOfRevisions(IERC20 tokenX, IERC20 tokenY, uint256 binStep) external view returns (uint256); + + function getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep, uint256 revision) external view returns (LBPairInformation memory); @@ -126,7 +157,9 @@ interface ILBFactory is IPendingOwnable { external returns (ILBPair pair); - function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) external; + function createLBPairRevision(IERC20 _tokenX, IERC20 _tokenY, uint16 _binStep) external returns (ILBPair pair); + + function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, uint256 revision, bool ignored) external; function setPreset( uint16 binStep, @@ -146,6 +179,7 @@ interface ILBFactory is IPendingOwnable { IERC20 tokenX, IERC20 tokenY, uint16 binStep, + uint256 revision, uint16 baseFactor, uint16 filterPeriod, uint16 decayPeriod, diff --git a/src/interfaces/ILBLegacyFactory.sol b/src/interfaces/ILBLegacyFactory.sol new file mode 100644 index 00000000..515a97d6 --- /dev/null +++ b/src/interfaces/ILBLegacyFactory.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "openzeppelin/token/ERC20/IERC20.sol"; + +import "./ILBPair.sol"; +import "./IPendingOwnable.sol"; + +/// @title Liquidity Book Factory Interface +/// @author Trader Joe +/// @notice Required interface of LBFactory contract +interface ILBLegacyFactory is IPendingOwnable { + /// @dev Structure to store the LBPair information, such as: + /// - binStep: The bin step of the LBPair + /// - LBPair: The address of the LBPair + /// - createdByOwner: Whether the pair was created by the owner of the factory + /// - ignoredForRouting: Whether the pair is ignored for routing or not. An ignored pair will not be explored during routes finding + struct LBPairInformation { + uint16 binStep; + ILBPair LBPair; + bool createdByOwner; + bool ignoredForRouting; + } + + event LBPairCreated( + IERC20 indexed tokenX, + IERC20 indexed tokenY, + uint256 indexed binStep, + ILBPair LBPair, + uint256 pid + ); + + event FeeRecipientSet(address oldRecipient, address newRecipient); + + event FlashLoanFeeSet(uint256 oldFlashLoanFee, uint256 newFlashLoanFee); + + event FeeParametersSet( + address indexed sender, + ILBPair indexed LBPair, + uint256 binStep, + uint256 baseFactor, + uint256 filterPeriod, + uint256 decayPeriod, + uint256 reductionFactor, + uint256 variableFeeControl, + uint256 protocolShare, + uint256 maxVolatilityAccumulated + ); + + event FactoryLockedStatusUpdated(bool unlocked); + + event LBPairImplementationSet(address oldLBPairImplementation, address LBPairImplementation); + + event LBPairIgnoredStateChanged(ILBPair indexed LBPair, bool ignored); + + event PresetSet( + uint256 indexed binStep, + uint256 baseFactor, + uint256 filterPeriod, + uint256 decayPeriod, + uint256 reductionFactor, + uint256 variableFeeControl, + uint256 protocolShare, + uint256 maxVolatilityAccumulated, + uint256 sampleLifetime + ); + + event PresetRemoved(uint256 indexed binStep); + + event QuoteAssetAdded(IERC20 indexed quoteAsset); + + event QuoteAssetRemoved(IERC20 indexed quoteAsset); + + function MAX_FEE() external pure returns (uint256); + + function MIN_BIN_STEP() external pure returns (uint256); + + function MAX_BIN_STEP() external pure returns (uint256); + + function MAX_PROTOCOL_SHARE() external pure returns (uint256); + + function LBPairImplementation() external view returns (address); + + function getNumberOfQuoteAssets() external view returns (uint256); + + function getQuoteAsset(uint256 index) external view returns (IERC20); + + function isQuoteAsset(IERC20 token) external view returns (bool); + + function feeRecipient() external view returns (address); + + function flashLoanFee() external view returns (uint256); + + function creationUnlocked() external view returns (bool); + + function allLBPairs(uint256 id) external returns (ILBPair); + + function getNumberOfLBPairs() external view returns (uint256); + + function getLBPairInformation( + IERC20 tokenX, + IERC20 tokenY, + uint256 binStep + ) external view returns (LBPairInformation memory); + + function getPreset( + uint16 binStep + ) + external + view + returns ( + uint256 baseFactor, + uint256 filterPeriod, + uint256 decayPeriod, + uint256 reductionFactor, + uint256 variableFeeControl, + uint256 protocolShare, + uint256 maxAccumulator, + uint256 sampleLifetime + ); + + function getAllBinSteps() external view returns (uint256[] memory presetsBinStep); + + function getAllLBPairs( + IERC20 tokenX, + IERC20 tokenY + ) external view returns (LBPairInformation[] memory LBPairsBinStep); + + function setLBPairImplementation(address LBPairImplementation) external; + + function createLBPair( + IERC20 tokenX, + IERC20 tokenY, + uint24 activeId, + uint16 binStep + ) external returns (ILBPair pair); + + function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) external; + + function setPreset( + uint16 binStep, + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated, + uint16 sampleLifetime + ) external; + + function removePreset(uint16 binStep) external; + + function setFeesParametersOnPair( + IERC20 tokenX, + IERC20 tokenY, + uint16 binStep, + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated + ) external; + + function setFeeRecipient(address feeRecipient) external; + + function setFlashLoanFee(uint256 flashLoanFee) external; + + function setFactoryLockedState(bool locked) external; + + function addQuoteAsset(IERC20 quoteAsset) external; + + function removeQuoteAsset(IERC20 quoteAsset) external; + + function forceDecay(ILBPair LBPair) external; +} diff --git a/src/interfaces/ILBLegacyRouter.sol b/src/interfaces/ILBLegacyRouter.sol new file mode 100644 index 00000000..78590901 --- /dev/null +++ b/src/interfaces/ILBLegacyRouter.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "./IJoeFactory.sol"; +import "./ILBPair.sol"; +import "./ILBToken.sol"; +import "./IWAVAX.sol"; + +/// @title Liquidity Book Router Interface +/// @author Trader Joe +/// @notice Required interface of LBRouter contract +interface ILBLegacyRouter { + /// @dev The liquidity parameters, such as: + /// - tokenX: The address of token X + /// - tokenY: The address of token Y + /// - binStep: The bin step of the pair + /// - amountX: The amount to send of token X + /// - amountY: The amount to send of token Y + /// - amountXMin: The min amount of token X added to liquidity + /// - amountYMin: The min amount of token Y added to liquidity + /// - activeIdDesired: The active id that user wants to add liquidity from + /// - idSlippage: The number of id that are allowed to slip + /// - deltaIds: The list of delta ids to add liquidity (`deltaId = activeId - desiredId`) + /// - distributionX: The distribution of tokenX with sum(distributionX) = 100e18 (100%) or 0 (0%) + /// - distributionY: The distribution of tokenY with sum(distributionY) = 100e18 (100%) or 0 (0%) + /// - to: The address of the recipient + /// - deadline: The deadline of the tx + struct LiquidityParameters { + IERC20 tokenX; + IERC20 tokenY; + uint256 binStep; + uint256 amountX; + uint256 amountY; + uint256 amountXMin; + uint256 amountYMin; + uint256 activeIdDesired; + uint256 idSlippage; + int256[] deltaIds; + uint256[] distributionX; + uint256[] distributionY; + address to; + uint256 deadline; + } + + function factory() external view returns (ILBFactory); + + function oldFactory() external view returns (IJoeFactory); + + function wavax() external view returns (IWAVAX); + + function getIdFromPrice(ILBPair LBPair, uint256 price) external view returns (uint24); + + function getPriceFromId(ILBPair LBPair, uint24 id) external view returns (uint256); + + function getSwapIn(ILBPair LBPair, uint256 amountOut, bool swapForY) + external + view + returns (uint256 amountIn, uint256 feesIn); + + function getSwapOut(ILBPair LBPair, uint256 amountIn, bool swapForY) + external + view + returns (uint256 amountOut, uint256 feesIn); + + function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) + external + returns (ILBPair pair); + + function addLiquidity(LiquidityParameters calldata liquidityParameters) + external + returns (uint256[] memory depositIds, uint256[] memory liquidityMinted); + + function addLiquidityAVAX(LiquidityParameters calldata liquidityParameters) + external + payable + returns (uint256[] memory depositIds, uint256[] memory liquidityMinted); + + function removeLiquidity( + IERC20 tokenX, + IERC20 tokenY, + uint16 binStep, + uint256 amountXMin, + uint256 amountYMin, + uint256[] memory ids, + uint256[] memory amounts, + address to, + uint256 deadline + ) external returns (uint256 amountX, uint256 amountY); + + function removeLiquidityAVAX( + IERC20 token, + uint16 binStep, + uint256 amountTokenMin, + uint256 amountAVAXMin, + uint256[] memory ids, + uint256[] memory amounts, + address payable to, + uint256 deadline + ) external returns (uint256 amountToken, uint256 amountAVAX); + + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address to, + uint256 deadline + ) external returns (uint256 amountOut); + + function swapExactTokensForAVAX( + uint256 amountIn, + uint256 amountOutMinAVAX, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address payable to, + uint256 deadline + ) external returns (uint256 amountOut); + + function swapExactAVAXForTokens( + uint256 amountOutMin, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address to, + uint256 deadline + ) external payable returns (uint256 amountOut); + + function swapTokensForExactTokens( + uint256 amountOut, + uint256 amountInMax, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address to, + uint256 deadline + ) external returns (uint256[] memory amountsIn); + + function swapTokensForExactAVAX( + uint256 amountOut, + uint256 amountInMax, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address payable to, + uint256 deadline + ) external returns (uint256[] memory amountsIn); + + function swapAVAXForExactTokens( + uint256 amountOut, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address to, + uint256 deadline + ) external payable returns (uint256[] memory amountsIn); + + function swapExactTokensForTokensSupportingFeeOnTransferTokens( + uint256 amountIn, + uint256 amountOutMin, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address to, + uint256 deadline + ) external returns (uint256 amountOut); + + function swapExactTokensForAVAXSupportingFeeOnTransferTokens( + uint256 amountIn, + uint256 amountOutMinAVAX, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address payable to, + uint256 deadline + ) external returns (uint256 amountOut); + + function swapExactAVAXForTokensSupportingFeeOnTransferTokens( + uint256 amountOutMin, + uint256[] memory pairBinSteps, + IERC20[] memory tokenPath, + address to, + uint256 deadline + ) external payable returns (uint256 amountOut); + + function sweep(IERC20 token, address to, uint256 amount) external; + + function sweepLBToken(ILBToken _lbToken, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) + external; +} diff --git a/src/interfaces/ILBRouter.sol b/src/interfaces/ILBRouter.sol index b6a4c833..148d3747 100644 --- a/src/interfaces/ILBRouter.sol +++ b/src/interfaces/ILBRouter.sol @@ -11,10 +11,36 @@ import "./IWAVAX.sol"; /// @author Trader Joe /// @notice Required interface of LBRouter contract interface ILBRouter { + error LBRouter__SenderIsNotWAVAX(); + error LBRouter__PairNotCreated(address tokenX, address tokenY, uint256 binStep); + error LBRouter__WrongAmounts(uint256 amount, uint256 reserve); + error LBRouter__SwapOverflows(uint256 id); + error LBRouter__BrokenSwapSafetyCheck(); + error LBRouter__NotFactoryOwner(); + error LBRouter__TooMuchTokensIn(uint256 excess); + error LBRouter__BinReserveOverflows(uint256 id); + error LBRouter__IdOverflows(int256 id); + error LBRouter__LengthsMismatch(); + error LBRouter__WrongTokenOrder(); + error LBRouter__IdSlippageCaught(uint256 activeIdDesired, uint256 idSlippage, uint256 activeId); + error LBRouter__AmountSlippageCaught(uint256 amountXMin, uint256 amountX, uint256 amountYMin, uint256 amountY); + error LBRouter__IdDesiredOverflows(uint256 idDesired, uint256 idSlippage); + error LBRouter__FailedToSendAVAX(address recipient, uint256 amount); + error LBRouter__DeadlineExceeded(uint256 deadline, uint256 currentTimestamp); + error LBRouter__AmountSlippageBPTooBig(uint256 amountSlippage); + error LBRouter__InsufficientAmountOut(uint256 amountOutMin, uint256 amountOut); + error LBRouter__MaxAmountInExceeded(uint256 amountInMax, uint256 amountIn); + error LBRouter__InvalidTokenPath(address wrongToken); + error LBRouter__InvalidVersion(uint256 version); + error LBRouter__WrongAvaxLiquidityParameters( + address tokenX, address tokenY, uint256 amountX, uint256 amountY, uint256 msgValue + ); + /// @dev The liquidity parameters, such as: /// - tokenX: The address of token X /// - tokenY: The address of token Y /// - binStep: The bin step of the pair + /// - revision: The revision of the pair /// - amountX: The amount to send of token X /// - amountY: The amount to send of token Y /// - amountXMin: The min amount of token X added to liquidity @@ -30,6 +56,7 @@ interface ILBRouter { IERC20 tokenX; IERC20 tokenY; uint256 binStep; + uint256 revision; uint256 amountX; uint256 amountY; uint256 amountXMin; @@ -43,6 +70,16 @@ interface ILBRouter { uint256 deadline; } + /// @dev The path parameters, such as: + /// - pairBinSteps: The list of bin steps of the pairs to go through + /// - revisions: The list of revisions of the pairs to go through + /// - tokenPath: The list of tokens in the path to go through + struct Path { + uint256[] pairBinSteps; + uint256[] revisions; + IERC20[] tokenPath; + } + function factory() external view returns (ILBFactory); function oldFactory() external view returns (IJoeFactory); @@ -53,15 +90,15 @@ interface ILBRouter { function getPriceFromId(ILBPair LBPair, uint24 id) external view returns (uint256); - function getSwapIn(ILBPair LBPair, uint256 amountOut, bool swapForY) + function getSwapIn(ILBPair LBPair, uint128 amountOut, bool swapForY) external view - returns (uint256 amountIn, uint256 feesIn); + returns (uint128 amountIn, uint128 amountOutLeft, uint128 fee); - function getSwapOut(ILBPair LBPair, uint256 amountIn, bool swapForY) + function getSwapOut(ILBPair LBPair, uint128 amountIn, bool swapForY) external view - returns (uint256 amountOut, uint256 feesIn); + returns (uint128 amountInLeft, uint128 amountOut, uint128 fee); function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) external @@ -69,17 +106,18 @@ interface ILBRouter { function addLiquidity(LiquidityParameters calldata liquidityParameters) external - returns (uint256[] memory depositIds, uint256[] memory liquidityMinted); + returns (bytes32[] memory depositIds, uint256[] memory liquidityMinted); function addLiquidityAVAX(LiquidityParameters calldata liquidityParameters) external payable - returns (uint256[] memory depositIds, uint256[] memory liquidityMinted); + returns (bytes32[] memory depositIds, uint256[] memory liquidityMinted); function removeLiquidity( IERC20 tokenX, IERC20 tokenY, uint16 binStep, + uint256 revision, uint256 amountXMin, uint256 amountYMin, uint256[] memory ids, @@ -91,6 +129,7 @@ interface ILBRouter { function removeLiquidityAVAX( IERC20 token, uint16 binStep, + uint256 revision, uint256 amountTokenMin, uint256 amountAVAXMin, uint256[] memory ids, @@ -102,8 +141,7 @@ interface ILBRouter { function swapExactTokensForTokens( uint256 amountIn, uint256 amountOutMin, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, + Path memory path, address to, uint256 deadline ) external returns (uint256 amountOut); @@ -111,25 +149,20 @@ interface ILBRouter { function swapExactTokensForAVAX( uint256 amountIn, uint256 amountOutMinAVAX, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, + Path memory path, address payable to, uint256 deadline ) external returns (uint256 amountOut); - function swapExactAVAXForTokens( - uint256 amountOutMin, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, - address to, - uint256 deadline - ) external payable returns (uint256 amountOut); + function swapExactAVAXForTokens(uint256 amountOutMin, Path memory path, address to, uint256 deadline) + external + payable + returns (uint256 amountOut); function swapTokensForExactTokens( uint256 amountOut, uint256 amountInMax, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, + Path memory path, address to, uint256 deadline ) external returns (uint256[] memory amountsIn); @@ -137,25 +170,20 @@ interface ILBRouter { function swapTokensForExactAVAX( uint256 amountOut, uint256 amountInMax, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, + Path memory path, address payable to, uint256 deadline ) external returns (uint256[] memory amountsIn); - function swapAVAXForExactTokens( - uint256 amountOut, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, - address to, - uint256 deadline - ) external payable returns (uint256[] memory amountsIn); + function swapAVAXForExactTokens(uint256 amountOut, Path memory path, address to, uint256 deadline) + external + payable + returns (uint256[] memory amountsIn); function swapExactTokensForTokensSupportingFeeOnTransferTokens( uint256 amountIn, uint256 amountOutMin, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, + Path memory path, address to, uint256 deadline ) external returns (uint256 amountOut); @@ -163,16 +191,14 @@ interface ILBRouter { function swapExactTokensForAVAXSupportingFeeOnTransferTokens( uint256 amountIn, uint256 amountOutMinAVAX, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, + Path memory path, address payable to, uint256 deadline ) external returns (uint256 amountOut); function swapExactAVAXForTokensSupportingFeeOnTransferTokens( uint256 amountOutMin, - uint256[] memory pairBinSteps, - IERC20[] memory tokenPath, + Path memory path, address to, uint256 deadline ) external payable returns (uint256 amountOut); diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index 54a2ab29..dd4ce39b 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -182,7 +182,7 @@ library BinHelper { ) internal pure returns (bytes32 amountsInToBin, bytes32 amountsOutOfBin, bytes32 totalFees) { uint256 price = activeId.getPriceFromId(binStep); - uint128 binReserveOut = binReserves.decode(!swapForY); + uint128 binReserveOut = binReserves.decode(swapForY); uint128 maxAmountIn = swapForY ? uint256(binReserveOut).shiftDivRoundUp(Constants.SCALE_OFFSET, price).safe128() @@ -195,7 +195,7 @@ library BinHelper { uint128 amountIn128; uint128 amountOut128; - uint128 amountIn = amountsLeft.decode(swapForY); + uint128 amountIn = amountsLeft.decode(!swapForY); if (amountIn >= maxAmountIn + maxFee) { fee128 = maxFee; diff --git a/test/LBFactory.t.sol b/test/LBFactory.t.sol new file mode 100644 index 00000000..9d826952 --- /dev/null +++ b/test/LBFactory.t.sol @@ -0,0 +1,962 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.10; + +import "./helpers/TestHelper.sol"; + +/* +* Test scenarios: +* 1. Constructor +* 2. Set LBPair implementation +* 3. Create LBPair +* 4. Create revision +* 5. Ignore LBPair for routing +* 6. Set preset +* 7. Remove preset +* 8. Set fee parameters on pair +* 9. Set fee recipient +* 10. Set flash loan fee +* 11. Set factory locked state +* 12. Add quote asset to whitelist +* 13. Remove quote asset from whitelist + +Invariant ideas: +- Presets*/ + +contract LiquidityBinFactoryTest is TestHelper { + event QuoteAssetRemoved(IERC20 indexed _quoteAsset); + event QuoteAssetAdded(IERC20 indexed _quoteAsset); + event LBPairImplementationSet(ILBPair oldLBPairImplementation, ILBPair LBPairImplementation); + event LBPairCreated( + IERC20 indexed tokenX, IERC20 indexed tokenY, uint256 indexed binStep, ILBPair LBPair, uint256 pid + ); + event FeeParametersSet( + address indexed sender, + ILBPair indexed LBPair, + uint256 binStep, + uint256 baseFactor, + uint256 filterPeriod, + uint256 decayPeriod, + uint256 reductionFactor, + uint256 variableFeeControl, + uint256 protocolShare, + uint256 maxVolatilityAccumulated + ); + + event PresetSet( + uint256 indexed binStep, + uint256 baseFactor, + uint256 filterPeriod, + uint256 decayPeriod, + uint256 reductionFactor, + uint256 variableFeeControl, + uint256 protocolShare, + uint256 maxVolatilityAccumulated, + uint256 sampleLifetime + ); + + event LBPairIgnoredStateChanged(ILBPair indexed LBPair, bool ignored); + + event PresetRemoved(uint256 indexed binStep); + + event FeeRecipientSet(address oldRecipient, address newRecipient); + + event FlashLoanFeeSet(uint256 oldFlashLoanFee, uint256 newFlashLoanFee); + + event FactoryLockedStatusUpdated(bool unlocked); + + struct LBPairInformation { + uint256 binStep; + ILBPair LBPair; + bool createdByOwner; + bool ignoredForRouting; + } + + function setUp() public override { + super.setUp(); + } + + function test_constructor() public { + assertEq(factory.feeRecipient(), DEV); + assertEq(factory.flashLoanFee(), DEFAULT_FLASHLOAN_FEE); + + vm.expectEmit(true, true, true, true); + emit FlashLoanFeeSet(0, DEFAULT_FLASHLOAN_FEE); + new LBFactory(DEV, DEFAULT_FLASHLOAN_FEE); + + // Reverts if the flash loan fee is above the max fee + uint256 maxFee = factory.MAX_FEE(); + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__FlashLoanFeeAboveMax.selector, maxFee + 1, maxFee)); + new LBFactory(DEV, maxFee + 1); + } + + function test_SetLBPairImplementation() public { + ILBPair newImplementation = new LBPair(factory); + + // Check if the implementation is set + vm.expectEmit(true, true, true, true); + emit LBPairImplementationSet(pairImplementation, newImplementation); + factory.setLBPairImplementation(address(newImplementation)); + assertEq(factory.LBPairImplementation(), address(newImplementation), "test_setLBPairImplementation:1"); + } + + function test_reverts_SetLBPairImplementation() public { + ILBPair newImplementation = new LBPair(factory); + factory.setLBPairImplementation(address(newImplementation)); + + // Reverts if the implementation is the same + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__SameImplementation.selector, newImplementation)); + factory.setLBPairImplementation(address(newImplementation)); + + LBFactory anotherFactory = new LBFactory(DEV, DEFAULT_FLASHLOAN_FEE); + + // Reverts if there is no implementation set + vm.expectRevert(ILBFactory.LBFactory__ImplementationNotSet.selector); + anotherFactory.createLBPair(weth, usdc, ID_ONE, DEFAULT_BIN_STEP); + + ILBPair newImplementationForAnotherFactory = new LBPair(anotherFactory); + + // Reverts if the implementation is not linked to the factory + vm.expectRevert( + abi.encodeWithSelector( + ILBFactory.LBFactory__LBPairSafetyCheckFailed.selector, newImplementationForAnotherFactory + ) + ); + factory.setLBPairImplementation(address(newImplementationForAnotherFactory)); + } + + function test_createLBPair() public { + address expectedPairAddress = Clones.predictDeterministicAddress( + address(pairImplementation), keccak256(abi.encode(usdc, usdt, DEFAULT_BIN_STEP, 1)), address(factory) + ); + + // Check for the correct events + vm.expectEmit(true, true, true, true); + emit LBPairCreated(usdt, usdc, DEFAULT_BIN_STEP, ILBPair(expectedPairAddress), 0); + + vm.expectEmit(true, true, true, true); + emit FeeParametersSet( + address(this), + ILBPair(expectedPairAddress), + DEFAULT_BIN_STEP, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATED + ); + + ILBPair pair = factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + + assertEq(factory.getNumberOfLBPairs(), 1, "test_createLBPair::1"); + + LBFactory.LBPairInformation memory pairInfo = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 1); + assertEq(pairInfo.binStep, DEFAULT_BIN_STEP, "test_createLBPair::2"); + assertEq(address(pairInfo.LBPair), address(pair), "test_createLBPair::2"); + assertTrue(pairInfo.createdByOwner); + assertFalse(pairInfo.ignoredForRouting); + assertEq(pairInfo.revisionIndex, 1); + assertEq(pairInfo.implementation, address(pairImplementation), "test_createLBPair::2"); + + assertEq(factory.getNumberOfRevisions(usdt, usdc, DEFAULT_BIN_STEP), 1, "test_createLBPair::3"); + assertEq(factory.getAllLBPairs(usdt, usdc).length, 1, "test_createLBPair::4"); + assertEq(address(factory.getAllLBPairs(usdt, usdc)[0].LBPair), address(pair), "test_createLBPair::5"); + + assertEq(address(pair.getFactory()), address(factory), "test_createLBPair::6"); + assertEq(address(pair.getTokenX()), address(usdt), "test_createLBPair::7"); + assertEq(address(pair.getTokenY()), address(usdc), "test_createLBPair::8"); + + // FeeHelper.FeeParameters memory feeParameters = pair.feeParameters(); + // assertEq(feeParameters.volatilityAccumulated, 0, "test_createLBPair::9"); + // assertEq(feeParameters.volatilityReference, 0, "test_createLBPair::10"); + // assertEq(feeParameters.indexRef, 0, "test_createLBPair::11"); + // assertEq(feeParameters.time, 0, "test_createLBPair::12"); + // assertEq(feeParameters.maxVolatilityAccumulated, DEFAULT_MAX_VOLATILITY_ACCUMULATED, "test_createLBPair::13"); + // assertEq(feeParameters.filterPeriod, DEFAULT_FILTER_PERIOD, "test_createLBPair::14"); + // assertEq(feeParameters.decayPeriod, DEFAULT_DECAY_PERIOD, "test_createLBPair::15"); + // assertEq(feeParameters.binStep, DEFAULT_BIN_STEP, "test_createLBPair::16"); + // assertEq(feeParameters.baseFactor, DEFAULT_BASE_FACTOR, "test_createLBPair::17"); + // assertEq(feeParameters.protocolShare, DEFAULT_PROTOCOL_SHARE, "test_createLBPair::18"); + } + + function test_createLBPairFactoryUnlocked() public { + factory.setFactoryLockedState(false); + + // Any user should be able to create pairs + vm.prank(ALICE); + factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + + assertFalse(factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 1).createdByOwner); + + vm.prank(BOB); + factory.createLBPair(weth, usdc, ID_ONE, DEFAULT_BIN_STEP); + + assertFalse(factory.getLBPairInformation(weth, usdc, DEFAULT_BIN_STEP, 1).createdByOwner); + + factory.createLBPair(bnb, usdc, ID_ONE, DEFAULT_BIN_STEP); + + assertTrue(factory.getLBPairInformation(bnb, usdc, DEFAULT_BIN_STEP, 1).createdByOwner); + + // Should close pair creations again + factory.setFactoryLockedState(true); + + vm.prank(ALICE); + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__FunctionIsLockedForUsers.selector, ALICE)); + factory.createLBPair(link, usdc, ID_ONE, DEFAULT_BIN_STEP); + + factory.createLBPair(wbtc, usdc, ID_ONE, DEFAULT_BIN_STEP); + } + + function test_reverts_createLBPair() public { + // Alice can't create a pair if the factory is locked + vm.prank(ALICE); + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__FunctionIsLockedForUsers.selector, ALICE)); + factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + + // Can't create pair if the implementation is not set + LBFactory newFactory = new LBFactory(DEV, DEFAULT_FLASHLOAN_FEE); + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__ImplementationNotSet.selector)); + newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + + // Can't create pair if the quote asset is not whitelisted + newFactory.setLBPairImplementation(address(new LBPair(newFactory))); + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__QuoteAssetNotWhitelisted.selector, usdc)); + newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + + // Can't create pair if the quote asset is the same as the base asset + newFactory.addQuoteAsset(usdc); + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__IdenticalAddresses.selector, usdc)); + newFactory.createLBPair(usdc, usdc, ID_ONE, DEFAULT_BIN_STEP); + + // Can't create a pair with an invalid bin step + // vm.expectRevert(abi.encodeWithSelector(ILBFactory.BinHelper__BinStepOverflows.selector, type(uint16).max)); + // newFactory.createLBPair(usdt, usdc, ID_ONE, type(uint16).max); + + // Can't create a pair with address(0) + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__AddressZero.selector)); + newFactory.createLBPair(IERC20(address(0)), usdc, ID_ONE, DEFAULT_BIN_STEP); + + // Can't create a pair if the preset is not set + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__BinStepHasNoPreset.selector, DEFAULT_BIN_STEP)); + newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + + newFactory.setPreset( + DEFAULT_BIN_STEP, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATED, + DEFAULT_SAMPLE_LIFETIME + ); + + // Can't create the same pair twice (a revision should be created instead) + newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + vm.expectRevert( + abi.encodeWithSelector(ILBFactory.LBFactory__LBPairAlreadyExists.selector, usdt, usdc, DEFAULT_BIN_STEP) + ); + newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + } + + function test_CreateRevision() public { + ILBPair pair = factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + + // Updates the pair implementation + pairImplementation = new LBPair(factory); + factory.setLBPairImplementation(address(pairImplementation)); + + address expectedPairAddress = Clones.predictDeterministicAddress( + address(pairImplementation), keccak256(abi.encode(usdc, usdt, DEFAULT_BIN_STEP, 2)), address(factory) + ); + + // Check for the correct events + vm.expectEmit(true, true, true, true); + emit LBPairCreated(usdt, usdc, DEFAULT_BIN_STEP, ILBPair(expectedPairAddress), 1); + + vm.expectEmit(true, true, true, true); + emit FeeParametersSet( + address(this), + ILBPair(expectedPairAddress), + DEFAULT_BIN_STEP, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATED + ); + + ILBPair revision = factory.createLBPairRevision(usdt, usdc, DEFAULT_BIN_STEP); + + assertEq(factory.getNumberOfLBPairs(), 2, "test_createLBPair::1"); + + LBFactory.LBPairInformation memory pairInfo = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 2); + assertEq(pairInfo.binStep, DEFAULT_BIN_STEP, "test_createLBPair::2"); + assertEq(address(pairInfo.LBPair), address(revision), "test_createLBPair::2"); + assertTrue(pairInfo.createdByOwner); + assertFalse(pairInfo.ignoredForRouting); + assertEq(pairInfo.revisionIndex, 2); + assertEq(pairInfo.implementation, address(pairImplementation), "test_createLBPair::2"); + + // Revision and previous pair should have active bin + uint256 pairActiveId = pair.getActiveId(); + uint256 revisionActiveId = revision.getActiveId(); + assertEq(pairActiveId, revisionActiveId); + + assertEq(factory.getNumberOfRevisions(usdt, usdc, DEFAULT_BIN_STEP), 2, "test_createLBPair::3"); + assertEq(factory.getAllLBPairs(usdt, usdc).length, 2, "test_createLBPair::4"); + assertEq(address(factory.getAllLBPairs(usdt, usdc)[0].LBPair), address(pair), "test_createLBPair::5"); + assertEq(address(factory.getAllLBPairs(usdt, usdc)[1].LBPair), address(revision), "test_createLBPair::5"); + + assertEq(address(revision.getFactory()), address(factory), "test_createLBPair::6"); + assertEq(address(revision.getTokenX()), address(usdt), "test_createLBPair::7"); + assertEq(address(revision.getTokenY()), address(usdc), "test_createLBPair::8"); + + // FeeHelper.FeeParameters memory feeParameters = revision.feeParameters(); + // assertEq(feeParameters.volatilityAccumulated, 0, "test_createLBPair::9"); + // assertEq(feeParameters.volatilityReference, 0, "test_createLBPair::10"); + // assertEq(feeParameters.indexRef, 0, "test_createLBPair::11"); + // assertEq(feeParameters.time, 0, "test_createLBPair::12"); + // assertEq(feeParameters.maxVolatilityAccumulated, DEFAULT_MAX_VOLATILITY_ACCUMULATED, "test_createLBPair::13"); + // assertEq(feeParameters.filterPeriod, DEFAULT_FILTER_PERIOD, "test_createLBPair::14"); + // assertEq(feeParameters.decayPeriod, DEFAULT_DECAY_PERIOD, "test_createLBPair::15"); + // assertEq(feeParameters.binStep, DEFAULT_BIN_STEP, "test_createLBPair::16"); + // assertEq(feeParameters.baseFactor, DEFAULT_BASE_FACTOR, "test_createLBPair::17"); + // assertEq(feeParameters.protocolShare, DEFAULT_PROTOCOL_SHARE, "test_createLBPair::18"); + } + + function test_reverts_CreateRevision() public { + // Can't create a revision if not the owner + vm.prank(ALICE); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); + factory.createLBPairRevision(usdt, usdc, DEFAULT_BIN_STEP); + + // Can't create a revision if the pair doesn't exist + vm.expectRevert( + abi.encodeWithSelector(ILBFactory.LBFactory__LBPairDoesNotExists.selector, usdt, usdc, DEFAULT_BIN_STEP) + ); + factory.createLBPairRevision(usdt, usdc, DEFAULT_BIN_STEP); + + factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + + // Can't create a revision if the pair implementation hasn't changed + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__SameImplementation.selector, pairImplementation)); + factory.createLBPairRevision(usdt, usdc, DEFAULT_BIN_STEP); + } + + function test_setLBPairIgnored() public { + factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + factory.createLBPair(weth, usdc, ID_ONE, DEFAULT_BIN_STEP); + factory.setLBPairImplementation(address(new LBPair(factory))); + ILBPair revision2 = ILBPair(factory.createLBPairRevision(usdt, usdc, DEFAULT_BIN_STEP)); + factory.setLBPairImplementation(address(new LBPair(factory))); + factory.createLBPairRevision(usdt, usdc, DEFAULT_BIN_STEP); + + // Ignoring the USDT-USDC rev 2 pair + vm.expectEmit(true, true, true, true); + emit LBPairIgnoredStateChanged(revision2, true); + factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 2, true); + + ILBFactory.LBPairInformation memory revision2Info = + factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 2); + assertEq(address(revision2Info.LBPair), address(revision2), "test_setLBPairIgnored::0"); + assertEq(revision2Info.ignoredForRouting, true, "test_setLBPairIgnored::1"); + + // Put it back to normal + vm.expectEmit(true, true, true, true); + emit LBPairIgnoredStateChanged(revision2, false); + factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 2, false); + + assertEq( + factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 2).ignoredForRouting, + false, + "test_setLBPairIgnored::1" + ); + } + + function test_reverts_setLBPairIgnored() public { + // Can't ignore for routing if not the owner + vm.prank(ALICE); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); + factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 1, true); + + // Can't update a non existing pair + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__AddressZero.selector)); + factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 1, true); + + factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + + // Can't update a pair to the same state + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__LBPairIgnoredIsAlreadyInTheSameState.selector)); + factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 1, false); + + factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 1, true); + + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__LBPairIgnoredIsAlreadyInTheSameState.selector)); + factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 1, true); + } + + function testFuzz_setPreset( + uint16 binStep, + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated, + uint16 sampleLifetime + ) public { + binStep = uint16(bound(binStep, factory.MIN_BIN_STEP(), factory.MAX_BIN_STEP())); + filterPeriod = uint16(bound(filterPeriod, 0, type(uint16).max - 1)); + decayPeriod = uint16(bound(decayPeriod, filterPeriod + 1, type(uint16).max)); + reductionFactor = uint16(bound(reductionFactor, 0, Constants.BASIS_POINT_MAX)); + protocolShare = uint16(bound(protocolShare, 0, factory.MAX_PROTOCOL_SHARE())); + variableFeeControl = uint24(bound(variableFeeControl, 0, Constants.BASIS_POINT_MAX)); + + // TODO: maxVolatilityAccumulated should be bounded but that's quite hard to calculate + uint256 totalFeesMax; + { + uint256 baseFee = (uint256(baseFactor) * binStep) * 1e10; + uint256 prod = uint256(maxVolatilityAccumulated) * binStep; + uint256 maxVariableFee = (prod * prod * variableFeeControl) / 100; + totalFeesMax = baseFee + maxVariableFee; + } + + if (totalFeesMax > factory.MAX_FEE()) { + vm.expectRevert(); + factory.setPreset( + binStep, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated, + sampleLifetime + ); + } else { + vm.expectEmit(true, true, true, true); + emit PresetSet( + binStep, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated, + sampleLifetime + ); + + factory.setPreset( + binStep, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated, + sampleLifetime + ); + + // Bin step DEFAULT_BIN_STEP is already there + if (binStep != DEFAULT_BIN_STEP) { + assertEq(factory.getAllBinSteps().length, 2, "1"); + if (binStep < DEFAULT_BIN_STEP) { + assertEq(factory.getAllBinSteps()[0], binStep, "2"); + } else { + assertEq(factory.getAllBinSteps()[1], binStep, "3"); + } + } else { + assertEq(factory.getAllBinSteps().length, 1, "3"); + assertEq(factory.getAllBinSteps()[0], binStep, "4"); + } + + // Check splitted in two to avoid stack too deep errors + { + ( + uint256 baseFactorView, + uint256 filterPeriodView, + uint256 decayPeriodView, + uint256 reductionFactorView, + , + , + , + ) = factory.getPreset(binStep); + + assertEq(baseFactorView, baseFactor); + assertEq(filterPeriodView, filterPeriod); + assertEq(decayPeriodView, decayPeriod); + assertEq(reductionFactorView, reductionFactor); + } + + { + ( + , + , + , + , + uint256 variableFeeControlView, + uint256 protocolShareView, + uint256 maxVolatilityAccumulatedView, + uint256 sampleLifetimeView + ) = factory.getPreset(binStep); + + assertEq(variableFeeControlView, variableFeeControl); + assertEq(protocolShareView, protocolShare); + assertEq(maxVolatilityAccumulatedView, maxVolatilityAccumulated); + assertEq(sampleLifetimeView, sampleLifetime); + } + } + } + + function testFuzz_reverts_setPreset( + uint16 binStep, + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated, + uint16 sampleLifetime + ) public { + uint256 baseFee = (uint256(baseFactor) * binStep) * 1e10; + uint256 prod = uint256(maxVolatilityAccumulated) * binStep; + uint256 maxVariableFee = (prod * prod * variableFeeControl) / 100; + + if (binStep < factory.MIN_BIN_STEP() || binStep > factory.MAX_BIN_STEP()) { + vm.expectRevert( + abi.encodeWithSelector( + ILBFactory.LBFactory__BinStepRequirementsBreached.selector, + factory.MIN_BIN_STEP(), + binStep, + factory.MAX_BIN_STEP() + ) + ); + factory.setPreset( + binStep, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated, + sampleLifetime + ); + } else if (filterPeriod >= decayPeriod) { + vm.expectRevert( + abi.encodeWithSelector(ILBFactory.LBFactory__DecreasingPeriods.selector, filterPeriod, decayPeriod) + ); + factory.setPreset( + binStep, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated, + sampleLifetime + ); + } else if (reductionFactor > Constants.BASIS_POINT_MAX) { + vm.expectRevert( + abi.encodeWithSelector( + ILBFactory.LBFactory__ReductionFactorOverflows.selector, reductionFactor, Constants.BASIS_POINT_MAX + ) + ); + factory.setPreset( + binStep, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated, + sampleLifetime + ); + } else if (protocolShare > factory.MAX_PROTOCOL_SHARE()) { + vm.expectRevert( + abi.encodeWithSelector( + ILBFactory.LBFactory__ProtocolShareOverflows.selector, protocolShare, factory.MAX_PROTOCOL_SHARE() + ) + ); + factory.setPreset( + binStep, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated, + sampleLifetime + ); + } else if (baseFee + maxVariableFee > factory.MAX_FEE()) { + vm.expectRevert(); + factory.setPreset( + binStep, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated, + sampleLifetime + ); + } else { + factory.setPreset( + binStep, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated, + sampleLifetime + ); + } + } + + function test_removePreset() public { + factory.setPreset( + DEFAULT_BIN_STEP + 1, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATED, + DEFAULT_SAMPLE_LIFETIME + ); + + factory.setPreset( + DEFAULT_BIN_STEP - 1, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATED, + DEFAULT_SAMPLE_LIFETIME + ); + + assertEq(factory.getAllBinSteps().length, 3); + + vm.expectEmit(true, true, true, true); + emit PresetRemoved(DEFAULT_BIN_STEP); + factory.removePreset(DEFAULT_BIN_STEP); + + assertEq(factory.getAllBinSteps().length, 2); + + // getPreset should revert for the removed bin step + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__BinStepHasNoPreset.selector, DEFAULT_BIN_STEP)); + factory.getPreset(DEFAULT_BIN_STEP); + + // Revert if not owner + vm.prank(ALICE); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); + factory.removePreset(DEFAULT_BIN_STEP); + + // Revert if bin step does not exist + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__BinStepHasNoPreset.selector, DEFAULT_BIN_STEP)); + factory.removePreset(DEFAULT_BIN_STEP); + } + + function test_setFeesParametersOnPair() public { + uint16 newBaseFactor = DEFAULT_BASE_FACTOR * 2; + uint16 newFilterPeriod = DEFAULT_FILTER_PERIOD * 2; + uint16 newDecayPeriod = DEFAULT_DECAY_PERIOD * 2; + uint16 newReductionFactor = DEFAULT_REDUCTION_FACTOR * 2; + uint24 newVariableFeeControl = DEFAULT_VARIABLE_FEE_CONTROL * 2; + uint16 newProtocolShare = DEFAULT_PROTOCOL_SHARE * 2; + uint24 newMaxVolatilityAccumulated = DEFAULT_MAX_VOLATILITY_ACCUMULATED * 2; + + ILBPair pair = factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + + // FeeHelper.FeeParameters memory oldFeeParameters = pair.feeParameters(); + + vm.expectEmit(true, true, true, true); + emit FeeParametersSet( + address(this), + pair, + DEFAULT_BIN_STEP, + newBaseFactor, + newFilterPeriod, + newDecayPeriod, + newReductionFactor, + newVariableFeeControl, + newProtocolShare, + newMaxVolatilityAccumulated + ); + + factory.setFeesParametersOnPair( + usdt, + usdc, + DEFAULT_BIN_STEP, + 1, + newBaseFactor, + newFilterPeriod, + newDecayPeriod, + newReductionFactor, + newVariableFeeControl, + newProtocolShare, + newMaxVolatilityAccumulated + ); + + // FeeHelper.FeeParameters memory feeParameters = pair.feeParameters(); + // // Paramters should be updated + // assertEq(feeParameters.baseFactor, newBaseFactor); + // assertEq(feeParameters.filterPeriod, newFilterPeriod); + // assertEq(feeParameters.decayPeriod, newDecayPeriod); + // assertEq(feeParameters.reductionFactor, newReductionFactor); + // assertEq(feeParameters.variableFeeControl, newVariableFeeControl); + // assertEq(feeParameters.protocolShare, newProtocolShare); + // assertEq(feeParameters.maxVolatilityAccumulated, newMaxVolatilityAccumulated); + + // // Rest of the fee parameters slot should be the same + // assertEq(feeParameters.volatilityAccumulated, oldFeeParameters.volatilityAccumulated); + // assertEq(feeParameters.volatilityReference, oldFeeParameters.volatilityReference); + // assertEq(feeParameters.indexRef, oldFeeParameters.indexRef); + // assertEq(feeParameters.time, oldFeeParameters.time); + + // Can't update if not the owner + vm.prank(ALICE); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); + factory.setFeesParametersOnPair( + usdt, + usdc, + DEFAULT_BIN_STEP, + 1, + newBaseFactor, + newFilterPeriod, + newDecayPeriod, + newReductionFactor, + newVariableFeeControl, + newProtocolShare, + newMaxVolatilityAccumulated + ); + + // Can't update a pair that does not exist + vm.expectRevert( + abi.encodeWithSelector(ILBFactory.LBFactory__LBPairNotCreated.selector, usdc, weth, DEFAULT_BIN_STEP) + ); + factory.setFeesParametersOnPair( + weth, + usdc, + DEFAULT_BIN_STEP, + 1, + newBaseFactor, + newFilterPeriod, + newDecayPeriod, + newReductionFactor, + newVariableFeeControl, + newProtocolShare, + newMaxVolatilityAccumulated + ); + } + + function test_setFeeRecipient() public { + vm.expectEmit(true, true, true, true); + emit FeeRecipientSet(address(this), ALICE); + factory.setFeeRecipient(ALICE); + + assertEq(factory.feeRecipient(), ALICE); + + // Can't set if not the owner + vm.prank(BOB); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); + factory.setFeeRecipient(BOB); + + // Can't set to the zero address + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__AddressZero.selector)); + factory.setFeeRecipient(address(0)); + + // Can't set to the same recipient + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__SameFeeRecipient.selector, ALICE)); + factory.setFeeRecipient(ALICE); + } + + function test_setFlashLoanFee() public { + uint256 newFlashLoanFee = 1_000; + vm.expectEmit(true, true, true, true); + emit FlashLoanFeeSet(DEFAULT_FLASHLOAN_FEE, newFlashLoanFee); + factory.setFlashLoanFee(newFlashLoanFee); + + assertEq(factory.flashLoanFee(), newFlashLoanFee); + + // Can't set if not the owner + vm.prank(ALICE); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); + factory.setFlashLoanFee(DEFAULT_FLASHLOAN_FEE); + + // Can't set to the same fee + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__SameFlashLoanFee.selector, newFlashLoanFee)); + factory.setFlashLoanFee(newFlashLoanFee); + + // Can't set to a fee greater than the maximum + uint256 maxFlashLoanFee = factory.MAX_FEE(); + vm.expectRevert( + abi.encodeWithSelector( + ILBFactory.LBFactory__FlashLoanFeeAboveMax.selector, maxFlashLoanFee + 1, maxFlashLoanFee + ) + ); + factory.setFlashLoanFee(maxFlashLoanFee + 1); + } + + function test_setFactoryLockedState() public { + assertEq(factory.creationUnlocked(), false); + + vm.expectEmit(true, true, true, true); + emit FactoryLockedStatusUpdated(false); + factory.setFactoryLockedState(false); + + assertEq(factory.creationUnlocked(), true); + + factory.setFactoryLockedState(true); + assertEq(factory.creationUnlocked(), false); + + // Can't set if not the owner + vm.prank(ALICE); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); + factory.setFactoryLockedState(true); + + // Can't set to the same state + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__FactoryLockIsAlreadyInTheSameState.selector)); + factory.setFactoryLockedState(true); + } + + function test_addQuoteAsset() public { + uint256 numberOfQuoteAssetBefore = factory.getNumberOfQuoteAssets(); + + IERC20 newToken = new ERC20Mock(18); + + assertEq(factory.isQuoteAsset(newToken), false); + + vm.expectEmit(true, true, true, true); + emit QuoteAssetAdded(newToken); + factory.addQuoteAsset(newToken); + + assertEq(factory.isQuoteAsset(newToken), true); + assertEq(factory.getNumberOfQuoteAssets(), numberOfQuoteAssetBefore + 1); + assertEq(address(newToken), address(factory.getQuoteAsset(numberOfQuoteAssetBefore))); + + // Can't add if not the owner + vm.prank(ALICE); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); + factory.addQuoteAsset(newToken); + + // Can't add if the asset is already a quote asset + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__QuoteAssetAlreadyWhitelisted.selector, newToken)); + factory.addQuoteAsset(newToken); + } + + function test_removeQuoteAsset() public { + uint256 numberOfQuoteAssetBefore = factory.getNumberOfQuoteAssets(); + + assertEq(factory.isQuoteAsset(usdc), true); + + vm.expectEmit(true, true, true, true); + emit QuoteAssetRemoved(usdc); + factory.removeQuoteAsset(usdc); + + assertEq(factory.isQuoteAsset(usdc), false); + assertEq(factory.getNumberOfQuoteAssets(), numberOfQuoteAssetBefore - 1); + + // Can't remove if not the owner + vm.prank(ALICE); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); + factory.removeQuoteAsset(usdc); + + // Can't remove if the asset is not a quote asset + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__QuoteAssetNotWhitelisted.selector, usdc)); + factory.removeQuoteAsset(usdc); + } + + function test_forceDecay() public { + ILBPair pair = factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + + factory.forceDecay(pair); + + // Can't force decay if not the owner + vm.prank(ALICE); + vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); + factory.forceDecay(pair); + } + + function test_getAllLBPairs() public { + /* Create pairs: + - WETH/USDC with bin step = 5 + - WETH/USDC with bin step = 20 + - WETH/USDC revision with bin step = 5 + - USDT/USDC with bin step = 5 + */ + + factory.setPreset( + 5, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATED, + DEFAULT_SAMPLE_LIFETIME + ); + + factory.setPreset( + 20, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATED, + DEFAULT_SAMPLE_LIFETIME + ); + + ILBPair pair1 = factory.createLBPair(weth, usdc, ID_ONE, 5); + ILBPair pair2 = factory.createLBPair(weth, usdc, ID_ONE, 20); + + factory.setLBPairImplementation(address(new LBPair(factory))); + ILBPair pair3 = factory.createLBPairRevision(weth, usdc, 5); + factory.createLBPair(usdt, usdc, ID_ONE, 5); + + ILBFactory.LBPairInformation[] memory LBPairsAvailable = factory.getAllLBPairs(weth, usdc); + + assertEq(LBPairsAvailable.length, 3); + + ILBFactory.LBPairInformation memory pair1Info = LBPairsAvailable[0]; + assertEq(address(pair1Info.LBPair), address(pair1)); + assertEq(pair1Info.binStep, 5); + assertEq(pair1Info.revisionIndex, 1); + assertEq(pair1Info.implementation, address(pairImplementation)); + + ILBFactory.LBPairInformation memory pair2Info = LBPairsAvailable[2]; + assertEq(address(pair2Info.LBPair), address(pair2)); + assertEq(pair2Info.binStep, 20); + assertEq(pair2Info.revisionIndex, 1); + assertEq(pair2Info.implementation, address(pairImplementation)); + + ILBFactory.LBPairInformation memory pair3Info = LBPairsAvailable[1]; + assertEq(address(pair3Info.LBPair), address(pair3)); + assertEq(pair3Info.binStep, 5); + assertEq(pair3Info.revisionIndex, 2); + assertEq(pair3Info.implementation, address(factory.LBPairImplementation())); + } +} diff --git a/test/LBRouter.Liquidity.t.sol b/test/LBRouter.Liquidity.t.sol new file mode 100644 index 00000000..46ef0bf8 --- /dev/null +++ b/test/LBRouter.Liquidity.t.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.10; + +import "test/helpers/TestHelper.sol"; + +/* +* Test scenarios: +* 2. Receive +* 3. Create LBPair +* 4. Add Liquidity +* 5. Add liquidity AVAX +* 6. Remove liquidity +* 7. Remove liquidity AVAX +* 8. Sweep ERC20s +* 9. Sweep LBToken*/ +contract LiquidityBinRouterTest is TestHelper { + function setUp() public override { + super.setUp(); + + factory.setFactoryLockedState(false); + + // Create necessary pairs + router.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + router.createLBPair(wavax, usdc, ID_ONE, DEFAULT_BIN_STEP); + router.createLBPair(taxToken, usdc, ID_ONE, DEFAULT_BIN_STEP); + } + + function test_ReceiveAVAX() public { + // Users can't send AVAX to the router + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__SenderIsNotWAVAX.selector)); + (bool success,) = address(router).call{value: 1e18}(""); + + // WAVAX can + deal(address(wavax), 1e18); + vm.prank(address(wavax)); + (success,) = address(router).call{value: 1e18}(""); + + assertTrue(success); + } + + function testFuzz_AddLiquidityNoSlippage(uint256 amountYIn, uint24 binNumber, uint24 gap) public { + amountYIn = bound(amountYIn, 5_000, type(uint112).max); + binNumber = uint24(bound(binNumber, 0, 400)); + binNumber = binNumber * 2 + 1; + gap = uint24(bound(gap, 0, 20)); + + ILBRouter.LiquidityParameters memory liquidityParameters = + getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); + + deal(address(usdt), DEV, liquidityParameters.amountX); + deal(address(usdc), DEV, liquidityParameters.amountY); + + // Add liquidity + router.addLiquidity(liquidityParameters); + } +} diff --git a/test/helpers/TestHelper.sol b/test/helpers/TestHelper.sol index 408e1242..4a023dee 100644 --- a/test/helpers/TestHelper.sol +++ b/test/helpers/TestHelper.sol @@ -10,23 +10,35 @@ import "src/LBRouter.sol"; import "src/LBQuoter.sol"; import "src/interfaces/ILBRouter.sol"; import "src/interfaces/IJoeRouter02.sol"; +import "src/interfaces/ILBLegacyRouter.sol"; +import "src/interfaces/ILBLegacyFactory.sol"; import "src/LBToken.sol"; import "src/libraries/math/Uint256x256Math.sol"; import "src/libraries/Constants.sol"; +import {PriceHelper} from "src/libraries/PriceHelper.sol"; + +import "../../src/interfaces/IPendingOwnable.sol"; + +import "./Utils.sol"; + import "test/mocks/WAVAX.sol"; import "test/mocks/ERC20.sol"; import "test/mocks/FlashloanBorrower.sol"; import "test/mocks/ERC20TransferTax.sol"; -abstract contract TestHelper is Test, IERC165 { +import {AvalancheAddresses} from "../integration/Addresses.sol"; + +abstract contract TestHelper is Test { using Uint256x256Math for uint256; + using Utils for uint256[]; + using Utils for int256[]; uint24 internal constant ID_ONE = 2 ** 23; uint256 internal constant BASIS_POINT_MAX = 10_000; // Avalanche market config for 10bps - uint16 internal constant DEFAULT_BIN_STEP = 10; + uint8 internal constant DEFAULT_BIN_STEP = 10; uint16 internal constant DEFAULT_BASE_FACTOR = 1000; uint16 internal constant DEFAULT_FILTER_PERIOD = 30; uint16 internal constant DEFAULT_DECAY_PERIOD = 600; @@ -61,13 +73,25 @@ abstract contract TestHelper is Test, IERC165 { LBFactory internal factory; LBRouter internal router; - LBPair internal pair; + LBPair internal localPair; LBPair internal pairWavax; LBQuoter internal quoter; + LBPair internal pairImplementation; + + // Forked contracts + IJoeRouter02 internal routerV1; + IJoeFactory internal factoryV1; + ILBLegacyRouter internal legacyRouterV2; + ILBLegacyFactory internal legacyFactoryV2; function setUp() public virtual { + wavax = WAVAX(AvalancheAddresses.WAVAX); + // If not forking, deploy mock + if (address(wavax).code.length == 0) { + vm.etch(address(wavax), address(new WAVAX()).code); + } + // Create mocks - wavax = new WAVAX(); usdc = new ERC20Mock(6); usdt = new ERC20Mock(6); wbtc = new ERC20Mock(8); @@ -86,22 +110,66 @@ abstract contract TestHelper is Test, IERC165 { vm.label(address(bnb), "bnb"); vm.label(address(taxToken), "taxToken"); + // Get forked contracts + routerV1 = IJoeRouter02(AvalancheAddresses.JOE_V1_ROUTER); + factoryV1 = IJoeFactory(AvalancheAddresses.JOE_V1_FACTORY); + legacyRouterV2 = ILBLegacyRouter(AvalancheAddresses.JOE_V2_ROUTER); + legacyFactoryV2 = ILBLegacyFactory(AvalancheAddresses.JOE_V2_FACTORY); + // Create factory factory = new LBFactory(DEV, DEFAULT_FLASHLOAN_FEE); - ILBPair LBPairImplementation = new LBPair(factory); + pairImplementation = new LBPair(factory); // Setup factory - factory.setLBPairImplementation(address(LBPairImplementation)); + factory.setLBPairImplementation(address(pairImplementation)); addAllAssetsToQuoteWhitelist(); setDefaultFactoryPresets(DEFAULT_BIN_STEP); // Create router - router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(0))); + router = new LBRouter(factory, legacyFactoryV2, factoryV1, IWAVAX(address(wavax))); + + // Create quoter + quoter = new LBQuoter( address(factoryV1), address(legacyFactoryV2), address(factory),address(router)); // Label deployed contracts - vm.label(address(factory), "factory"); vm.label(address(router), "router"); - vm.label(address(LBPairImplementation), "LBPairImplementation"); + vm.label(address(quoter), "quoter"); + vm.label(address(factory), "factory"); + vm.label(address(pairImplementation), "pairImplementation"); + + // Label forks + vm.label(address(routerV1), "routerV1"); + vm.label(address(factoryV1), "factoryV1"); + vm.label(address(legacyRouterV2), "legacyRouterV2"); + vm.label(address(legacyFactoryV2), "legacyFactoryV2"); + + // Give approvals to routers + wavax.approve(address(routerV1), type(uint256).max); + usdc.approve(address(routerV1), type(uint256).max); + usdt.approve(address(routerV1), type(uint256).max); + wbtc.approve(address(routerV1), type(uint256).max); + weth.approve(address(routerV1), type(uint256).max); + link.approve(address(routerV1), type(uint256).max); + bnb.approve(address(routerV1), type(uint256).max); + taxToken.approve(address(routerV1), type(uint256).max); + + wavax.approve(address(legacyRouterV2), type(uint256).max); + usdc.approve(address(legacyRouterV2), type(uint256).max); + usdt.approve(address(legacyRouterV2), type(uint256).max); + wbtc.approve(address(legacyRouterV2), type(uint256).max); + weth.approve(address(legacyRouterV2), type(uint256).max); + link.approve(address(legacyRouterV2), type(uint256).max); + bnb.approve(address(legacyRouterV2), type(uint256).max); + taxToken.approve(address(legacyRouterV2), type(uint256).max); + + wavax.approve(address(router), type(uint256).max); + usdc.approve(address(router), type(uint256).max); + usdt.approve(address(router), type(uint256).max); + wbtc.approve(address(router), type(uint256).max); + weth.approve(address(router), type(uint256).max); + link.approve(address(router), type(uint256).max); + bnb.approve(address(router), type(uint256).max); + taxToken.approve(address(router), type(uint256).max); } function supportsInterface(bytes4 interfaceId) external view virtual returns (bool) { @@ -109,15 +177,22 @@ abstract contract TestHelper is Test, IERC165 { } function getPriceFromId(uint24 id) internal pure returns (uint256 price) { - price = BinHelper.getPriceFromId(id, DEFAULT_BIN_STEP); + price = PriceHelper.getPriceFromId(id, DEFAULT_BIN_STEP); } function getIdFromPrice(uint256 price) internal pure returns (uint24 id) { - id = BinHelper.getIdFromPrice(price, DEFAULT_BIN_STEP); + id = PriceHelper.getIdFromPrice(price, DEFAULT_BIN_STEP); } - function createLBPair(IERC20 tokenX, IERC20 tokenY) internal returns (LBPair newPair) { - newPair = createLBPairFromStartId(tokenX, tokenY, ID_ONE); + function addAllAssetsToQuoteWhitelist() internal { + if (address(wavax) != address(0)) factory.addQuoteAsset(wavax); + if (address(usdc) != address(0)) factory.addQuoteAsset(usdc); + if (address(usdt) != address(0)) factory.addQuoteAsset(usdt); + if (address(wbtc) != address(0)) factory.addQuoteAsset(wbtc); + if (address(weth) != address(0)) factory.addQuoteAsset(weth); + if (address(link) != address(0)) factory.addQuoteAsset(link); + if (address(bnb) != address(0)) factory.addQuoteAsset(bnb); + if (address(taxToken) != address(0)) factory.addQuoteAsset(taxToken); } function setDefaultFactoryPresets(uint16 binStep) internal { @@ -134,6 +209,10 @@ abstract contract TestHelper is Test, IERC165 { ); } + function createLBPair(IERC20 tokenX, IERC20 tokenY) internal returns (LBPair newPair) { + newPair = createLBPairFromStartId(tokenX, tokenY, ID_ONE); + } + function createLBPairFromStartId(IERC20 tokenX, IERC20 tokenY, uint24 startId) internal returns (LBPair newPair) { newPair = createLBPairFromStartIdAndBinStep(tokenX, tokenY, startId, DEFAULT_BIN_STEP); } @@ -145,89 +224,34 @@ abstract contract TestHelper is Test, IERC165 { newPair = LBPair(address(factory.createLBPair(tokenX, tokenY, startId, binStep))); } - function convertRelativeIdsToAbsolute(int256[] memory relativeIds, uint24 startId) - internal - pure - returns (uint256[] memory absoluteIds) - { - absoluteIds = new uint256[](relativeIds.length); - for (uint256 i = 0; i < relativeIds.length; i++) { - int256 id = int256(uint256(startId)) + relativeIds[i]; - require(id >= 0, "Id conversion: id must be positive"); - absoluteIds[i] = uint256(id); - } - } - - function convertAbsoluteIdsToRelative(uint256[] memory absoluteIds, uint24 startId) - internal - pure - returns (int256[] memory relativeIds) - { - relativeIds = new int256[](absoluteIds.length); - for (uint256 i = 0; i < absoluteIds.length; i++) { - relativeIds[i] = int256(absoluteIds[i]) - int256(uint256(startId)); - } - } - - function addLiquidityAndReturnAbsoluteIds( - ERC20Mock tokenX, - ERC20Mock tokenY, + function getLiquidityParameters( + IERC20 tokenX, + IERC20 tokenY, uint256 amountYIn, uint24 startId, uint24 numberBins, uint24 gap - ) - internal - returns ( - uint256[] memory ids, - uint256[] memory distributionX, - uint256[] memory distributionY, - uint256 amountXIn - ) - { - (ids, distributionX, distributionY, amountXIn) = - addLiquidity(tokenX, tokenY, amountYIn, startId, numberBins, gap); - } - - function addLiquidityAndReturnRelativeIds( - ERC20Mock tokenX, - ERC20Mock tokenY, - uint256 amountYIn, - uint24 startId, - uint24 numberBins, - uint24 gap - ) - internal - returns (int256[] memory ids, uint256[] memory distributionX, uint256[] memory distributionY, uint256 amountXIn) - { - uint256[] memory absoluteIds; - (absoluteIds, distributionX, distributionY, amountXIn) = - addLiquidity(tokenX, tokenY, amountYIn, startId, numberBins, gap); - ids = convertAbsoluteIdsToRelative(absoluteIds, startId); - } - - function addLiquidity( - ERC20Mock tokenX, - ERC20Mock tokenY, - uint256 amountYIn, - uint24 startId, - uint24 numberBins, - uint24 gap - ) - internal - returns ( - uint256[] memory ids, - uint256[] memory distributionX, - uint256[] memory distributionY, - uint256 amountXIn - ) - { - (ids, distributionX, distributionY, amountXIn) = spreadLiquidity(amountYIn, startId, numberBins, gap); - - tokenX.mint(address(pair), amountXIn); - tokenY.mint(address(pair), amountYIn); - - pair.mint(ids, distributionX, distributionY, DEV); + ) internal view returns (ILBRouter.LiquidityParameters memory liquidityParameters) { + (uint256[] memory ids, uint256[] memory distributionX, uint256[] memory distributionY, uint256 amountXIn) = + spreadLiquidity(amountYIn, startId, numberBins, gap); + + liquidityParameters = ILBRouter.LiquidityParameters({ + tokenX: tokenX, + tokenY: tokenY, + binStep: DEFAULT_BIN_STEP, + revision: 1, + amountX: amountXIn, + amountY: amountYIn, + amountXMin: 0, + amountYMin: 0, + activeIdDesired: startId, + idSlippage: 0, + deltaIds: ids.convertToRelative(startId), + distributionX: distributionX, + distributionY: distributionY, + to: DEV, + deadline: block.timestamp + 1000 + }); } function spreadLiquidity(uint256 amountYIn, uint24 startId, uint24 numberBins, uint24 gap) @@ -260,20 +284,10 @@ abstract contract TestHelper is Test, IERC165 { } if (i >= spread) { distributionX[i] = binDistribution; - amountXIn += - binLiquidity > 0 ? (binLiquidity * Constants.SCALE - 1) / getPriceFromId(uint24(ids[i])) + 1 : 0; + amountXIn += binLiquidity > 0 + ? binLiquidity.shiftDivRoundDown(Constants.SCALE_OFFSET, getPriceFromId(uint24(ids[i]))) + : 0; } } } - - function addAllAssetsToQuoteWhitelist() internal { - if (address(wavax) != address(0)) factory.addQuoteAsset(wavax); - if (address(usdc) != address(0)) factory.addQuoteAsset(usdc); - if (address(usdt) != address(0)) factory.addQuoteAsset(usdt); - if (address(wbtc) != address(0)) factory.addQuoteAsset(wbtc); - if (address(weth) != address(0)) factory.addQuoteAsset(weth); - if (address(link) != address(0)) factory.addQuoteAsset(link); - if (address(bnb) != address(0)) factory.addQuoteAsset(bnb); - if (address(taxToken) != address(0)) factory.addQuoteAsset(taxToken); - } } diff --git a/test/helpers/Utils.sol b/test/helpers/Utils.sol new file mode 100644 index 00000000..abdf4c64 --- /dev/null +++ b/test/helpers/Utils.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import {ILBRouter} from "../../src/interfaces/ILBRouter.sol"; +import {ILBLegacyRouter} from "../../src/interfaces/ILBLegacyRouter.sol"; + +library Utils { + function convertToAbsolute(int256[] memory relativeIds, uint24 startId) + internal + pure + returns (uint256[] memory absoluteIds) + { + absoluteIds = new uint256[](relativeIds.length); + for (uint256 i = 0; i < relativeIds.length; i++) { + int256 id = int256(uint256(startId)) + relativeIds[i]; + require(id >= 0, "Id conversion: id must be positive"); + absoluteIds[i] = uint256(id); + } + } + + function convertToRelative(uint256[] memory absoluteIds, uint24 startId) + internal + pure + returns (int256[] memory relativeIds) + { + relativeIds = new int256[](absoluteIds.length); + for (uint256 i = 0; i < absoluteIds.length; i++) { + relativeIds[i] = int256(absoluteIds[i]) - int256(uint256(startId)); + } + } + + function toLegacy(ILBRouter.LiquidityParameters memory liquidityParameters) + internal + pure + returns (ILBLegacyRouter.LiquidityParameters memory legacyLiquidityParameters) + { + legacyLiquidityParameters = ILBLegacyRouter.LiquidityParameters({ + tokenX: liquidityParameters.tokenX, + tokenY: liquidityParameters.tokenY, + binStep: liquidityParameters.binStep, + amountX: liquidityParameters.amountX, + amountY: liquidityParameters.amountY, + amountXMin: liquidityParameters.amountXMin, + amountYMin: liquidityParameters.amountYMin, + activeIdDesired: liquidityParameters.activeIdDesired, + idSlippage: liquidityParameters.idSlippage, + deltaIds: liquidityParameters.deltaIds, + distributionX: liquidityParameters.distributionX, + distributionY: liquidityParameters.distributionY, + to: liquidityParameters.to, + deadline: liquidityParameters.deadline + }); + } +} diff --git a/test/integration/Addresses.sol b/test/integration/Addresses.sol index b3027497..fec25f67 100644 --- a/test/integration/Addresses.sol +++ b/test/integration/Addresses.sol @@ -2,9 +2,15 @@ pragma solidity 0.8.10; -library Addresses { - address internal constant JOE_V1_FACTORY_ADDRESS = 0x9Ad6C38BE94206cA50bb0d90783181662f0Cfa10; - address internal constant JOE_V1_ROUTER_ADDRESS = 0x60aE616a2155Ee3d9A68541Ba4544862310933d4; - address internal constant WAVAX_AVALANCHE_ADDRESS = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7; - address internal constant USDC_AVALANCHE_ADDRESS = 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E; +library AvalancheAddresses { + address internal constant V2_FACTORY_OWNER = 0x2fbB61a10B96254900C03F1644E9e1d2f5E76DD2; + address internal constant JOE_V1_FACTORY = 0x9Ad6C38BE94206cA50bb0d90783181662f0Cfa10; + address internal constant JOE_V1_ROUTER = 0x60aE616a2155Ee3d9A68541Ba4544862310933d4; + address internal constant JOE_V2_FACTORY = 0x6E77932A92582f504FF6c4BdbCef7Da6c198aEEf; + address internal constant JOE_V2_ROUTER = 0xE3Ffc583dC176575eEA7FD9dF2A7c65F7E23f4C3; + address internal constant WAVAX = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7; + address internal constant USDC = 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E; + address internal constant USDT = 0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7; + address internal constant WETH = 0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB; + address internal constant BNB = 0x264c1383EA520f73dd837F915ef3a732e204a493; } diff --git a/test/integration/LBQuoter.t.sol b/test/integration/LBQuoter.t.sol new file mode 100644 index 00000000..a9ce68f0 --- /dev/null +++ b/test/integration/LBQuoter.t.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.10; + +import "../helpers/TestHelper.sol"; + +/* +* Market deployed: +* - USDT/USDC, V1 with low liquidity, V2 with high liquidity +* - WAVAX/USDC, V1 with high liquidity, V2 with low liquidity +* - WETH/USDC, V1 with low liquidity, V2.1.rev1 with low liquidity, V2.1.rev2 with high liquidity +* - BNB/USDC, V2 with high liquidity, V2.1 with low liquidity +* +* Every market with low liquidity has a slighly higher price. +* It should be picked with small amounts but not with large amounts. +* All tokens are considered 18 decimals for simplification purposes. +**/ + +contract LiquidityBinQuoterTest is TestHelper { + uint256 private defaultBaseFee = DEFAULT_BIN_STEP * uint256(DEFAULT_BASE_FACTOR) * 1e10; + + using Utils for ILBRouter.LiquidityParameters; + + function setUp() public override { + vm.createSelectFork(vm.rpcUrl("avalanche"), 25_396_630); + super.setUp(); + + uint256 lowLiquidityAmount = 1e18; + uint256 highLiquidityAmount = 1e24; + + // Get tokens to add liquidity + deal(address(usdc), address(this), 10 * highLiquidityAmount); + deal(address(usdt), address(this), 10 * highLiquidityAmount); + deal(address(wavax), address(this), 10 * highLiquidityAmount); + deal(address(weth), address(this), 10 * highLiquidityAmount); + deal(address(bnb), address(this), 10 * highLiquidityAmount); + + // Add liquidity to V1 + routerV1.addLiquidity( + address(usdt), + address(usdc), + lowLiquidityAmount / 2, // 1 USDT = 2 USDC + lowLiquidityAmount, + lowLiquidityAmount, + lowLiquidityAmount, + address(this), + block.timestamp + 1 + ); + + routerV1.addLiquidity( + address(wavax), + address(usdc), + highLiquidityAmount, // 1 AVAX = 1 USDC + highLiquidityAmount, + 0, + 0, + address(this), + block.timestamp + 1 + ); + + routerV1.addLiquidity( + address(weth), + address(usdc), + lowLiquidityAmount / 2, // 1 WETH = 2 USDC + lowLiquidityAmount, + 0, + 0, + address(this), + block.timestamp + 1 + ); + + vm.startPrank(AvalancheAddresses.V2_FACTORY_OWNER); + legacyFactoryV2.addQuoteAsset(usdc); + legacyFactoryV2.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); // 1 USDT = 1 USDC + legacyFactoryV2.createLBPair(wavax, usdc, ID_ONE - 50, DEFAULT_BIN_STEP); // 1 AVAX > 1 USDC + legacyFactoryV2.createLBPair(bnb, usdc, ID_ONE, DEFAULT_BIN_STEP); // 1 BNB = 1 USDC + vm.stopPrank(); + + factory.createLBPair(weth, usdc, ID_ONE, DEFAULT_BIN_STEP); // 1 WETH = 1 USDC + factory.createLBPair(bnb, usdc, ID_ONE - 50, DEFAULT_BIN_STEP); // 1 BNB > 1 USDC + factory.setLBPairImplementation(address(new LBPair(factory))); + factory.createLBPairRevision(weth, usdc, DEFAULT_BIN_STEP); // 1 WETH = 1 USDC + + // Add liquidity to V2 + ILBRouter.LiquidityParameters memory liquidityParameters = + getLiquidityParameters(usdt, usdc, highLiquidityAmount, ID_ONE, 7, 0); + legacyRouterV2.addLiquidity(liquidityParameters.toLegacy()); + } + + function test_Constructor() public { + assertEq(address(quoter.routerV2()), address(router)); + assertEq(address(quoter.factoryV1()), AvalancheAddresses.JOE_V1_FACTORY); + assertEq(address(quoter.factoryV2()), address(factory)); + } + + function test_InvalidLength() public { + address[] memory route; + route = new address[](1); + vm.expectRevert(LBQuoter.LBQuoter_InvalidLength.selector); + quoter.findBestPathFromAmountIn(route, 1e18); + vm.expectRevert(LBQuoter.LBQuoter_InvalidLength.selector); + quoter.findBestPathFromAmountOut(route, 20e6); + } + + function test_Scenario1() public { + // USDT/USDC, V1 with low liquidity, V2 with high liquidity + address[] memory route = new address[](2); + route[0] = address(usdt); + route[1] = address(usdc); + + // Small amountIn + uint128 amountIn = 1e16; + LBQuoter.Quote memory quote = quoter.findBestPathFromAmountIn(route, amountIn); + + assertEq(quote.amounts[0], amountIn); + assertApproxEqRel(quote.amounts[1], amountIn * 2, 5e16); + assertEq(quote.binSteps[0], 0); + assertEq(quote.revisions[0], 0); + + // Large amountIn + amountIn = 100e18; + quote = quoter.findBestPathFromAmountIn(route, amountIn); + + // assertEq(quote.amounts[0], amountIn); + // assertApproxEqRel(quote.amounts[1], amountIn * 2, 5e16); + // assertEq(quote.binSteps[0], DEFAULT_BIN_STEP); + // assertEq(quote.revisions[0], 0); + } +} diff --git a/test_old/LBFactory.t.sol b/test_old/LBFactory.t.sol deleted file mode 100644 index c98fff6c..00000000 --- a/test_old/LBFactory.t.sol +++ /dev/null @@ -1,475 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; - -contract LiquidityBinFactoryTest is TestHelper { - event QuoteAssetRemoved(IERC20 indexed _quoteAsset); - event QuoteAssetAdded(IERC20 indexed _quoteAsset); - event LBPairImplementationSet(ILBPair oldLBPairImplementation, ILBPair LBPairImplementation); - - struct LBPairInformation { - uint256 binStep; - ILBPair LBPair; - bool createdByOwner; - bool ignoredForRouting; - } - - function setUp() public override { - usdc = new ERC20Mock(6); - wbtc = new ERC20Mock(12); - weth = new ERC20Mock(18); - wavax = new WAVAX(); - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - addAllAssetsToQuoteWhitelist(factory); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - } - - function testConstructor() public { - assertEq(factory.feeRecipient(), DEV); - assertEq(factory.flashLoanFee(), 8e14); - } - - function testSetLBPairImplementation() public { - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - vm.expectRevert(abi.encodeWithSelector(LBFactory__SameImplementation.selector, _LBPairImplementation)); - factory.setLBPairImplementation(address(_LBPairImplementation)); - - LBFactory anotherFactory = new LBFactory(DEV, 7e14); - vm.expectRevert(LBFactory__ImplementationNotSet.selector); - anotherFactory.createLBPair(usdc, weth, ID_ONE, DEFAULT_BIN_STEP); - - ILBPair _LBPairImplementationAnotherFactory = new LBPair(anotherFactory); - vm.expectRevert( - abi.encodeWithSelector(LBFactory__LBPairSafetyCheckFailed.selector, _LBPairImplementationAnotherFactory) - ); - factory.setLBPairImplementation(address(_LBPairImplementationAnotherFactory)); - - ILBPair _LBPairImplementationNew = new LBPair(factory); - vm.expectEmit(true, true, true, true); - emit LBPairImplementationSet(_LBPairImplementation, _LBPairImplementationNew); - factory.setLBPairImplementation(address(_LBPairImplementationNew)); - } - - function testgetAllLBPairs() public { - assertEq(factory.getAllLBPairs(usdc, weth).length, 0); - ILBPair pair25 = createLBPairDefaultFees(usdc, weth); - assertEq(factory.getAllLBPairs(usdc, weth).length, 1); - setDefaultFactoryPresets(1); - ILBPair pair1 = factory.createLBPair(usdc, weth, ID_ONE, 1); - assertEq(factory.getAllLBPairs(usdc, weth).length, 2); - - factory.setPreset( - 50, - DEFAULT_BASE_FACTOR / 4, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL / 4, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME - ); - router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(0))); - factory.setFactoryLockedState(false); - ILBPair pair50 = router.createLBPair(usdc, weth, ID_ONE, 50); - factory.setLBPairIgnored(usdc, weth, 50, true); - assertEq(factory.getAllLBPairs(usdc, weth).length, 3); - - ILBFactory.LBPairInformation[] memory LBPairsAvailable = factory.getAllLBPairs(usdc, weth); - - assertEq(LBPairsAvailable[0].binStep, 1); - assertEq(address(LBPairsAvailable[0].LBPair), address(pair1)); - assertEq(LBPairsAvailable[0].createdByOwner, true); - assertEq(LBPairsAvailable[0].ignoredForRouting, false); - - assertEq(LBPairsAvailable[1].binStep, 25); - assertEq(address(LBPairsAvailable[1].LBPair), address(pair25)); - assertEq(LBPairsAvailable[1].createdByOwner, true); - assertEq(LBPairsAvailable[1].ignoredForRouting, false); - - assertEq(LBPairsAvailable[2].binStep, 50); - assertEq(address(LBPairsAvailable[2].LBPair), address(pair50)); - assertEq(LBPairsAvailable[2].createdByOwner, false); - assertEq(LBPairsAvailable[2].ignoredForRouting, true); - } - - function testCreateLBPair() public { - ILBPair pair = createLBPairDefaultFees(usdc, wbtc); - - assertEq(factory.getNumberOfLBPairs(), 1); - assertEq(address(factory.getLBPairInformation(usdc, wbtc, DEFAULT_BIN_STEP).LBPair), address(pair)); - - assertEq(address(pair.factory()), address(factory)); - assertEq(address(pair.tokenX()), address(usdc)); - assertEq(address(pair.tokenY()), address(wbtc)); - - FeeHelper.FeeParameters memory feeParameters = pair.feeParameters(); - assertEq(feeParameters.volatilityAccumulated, 0); - assertEq(feeParameters.volatilityReference, 0); - assertEq(feeParameters.indexRef, 0); - assertEq(feeParameters.time, 0); - assertEq(feeParameters.maxVolatilityAccumulated, DEFAULT_MAX_VOLATILITY_ACCUMULATED); - assertEq(feeParameters.filterPeriod, DEFAULT_FILTER_PERIOD); - assertEq(feeParameters.decayPeriod, DEFAULT_DECAY_PERIOD); - assertEq(feeParameters.binStep, DEFAULT_BIN_STEP); - assertEq(feeParameters.baseFactor, DEFAULT_BASE_FACTOR); - assertEq(feeParameters.protocolShare, DEFAULT_PROTOCOL_SHARE); - } - - function testSetFeeRecipient() public { - vm.expectRevert(LBFactory__AddressZero.selector); - factory.setFeeRecipient(address(0)); - - factory.setFeeRecipient(ALICE); - assertEq(factory.feeRecipient(), ALICE); - - vm.expectRevert(abi.encodeWithSelector(LBFactory__SameFeeRecipient.selector, ALICE)); - factory.setFeeRecipient(ALICE); - } - - function testSetFeeRecipientNotByOwnerReverts() public { - vm.prank(ALICE); - vm.expectRevert(PendingOwnable__NotOwner.selector); - factory.setFeeRecipient(ALICE); - } - - function testFactoryLockedReverts() public { - vm.prank(ALICE); - vm.expectRevert(abi.encodeWithSelector(LBFactory__FunctionIsLockedForUsers.selector, ALICE)); - createLBPairDefaultFees(usdc, wbtc); - } - - function testCreatePairWhenFactoryIsUnlocked() public { - factory.setFactoryLockedState(false); - - vm.prank(ALICE); - createLBPairDefaultFees(usdc, wbtc); - - ILBFactory.LBPairInformation[] memory LBPairBinSteps = factory.getAllLBPairs(usdc, wbtc); - assertEq(LBPairBinSteps.length, 1); - assertEq(LBPairBinSteps[0].binStep, DEFAULT_BIN_STEP); - assertEq(LBPairBinSteps[0].ignoredForRouting, false); - assertEq(LBPairBinSteps[0].createdByOwner, false); - } - - function testForIdenticalAddressesReverts() public { - vm.expectRevert(abi.encodeWithSelector(LBFactory__IdenticalAddresses.selector, usdc)); - factory.createLBPair(usdc, usdc, ID_ONE, DEFAULT_BIN_STEP); - } - - function testForZeroAddressPairReverts() public { - factory.addQuoteAsset(IERC20(address(0))); - vm.expectRevert(LBFactory__AddressZero.selector); - factory.createLBPair(usdc, IERC20(address(0)), ID_ONE, DEFAULT_BIN_STEP); - - vm.expectRevert(LBFactory__AddressZero.selector); - factory.createLBPair(IERC20(address(0)), usdc, ID_ONE, DEFAULT_BIN_STEP); - } - - function testIfPairAlreadyExistsReverts() public { - createLBPairDefaultFees(usdc, wbtc); - vm.expectRevert(abi.encodeWithSelector(LBFactory__LBPairAlreadyExists.selector, usdc, wbtc, DEFAULT_BIN_STEP)); - createLBPairDefaultFees(usdc, wbtc); - } - - function testForInvalidBinStepOverflowReverts() public { - uint16 invalidBinStepOverflow = uint16(factory.MAX_BIN_STEP() + 1); - vm.expectRevert( - abi.encodeWithSelector( - LBFactory__BinStepRequirementsBreached.selector, - factory.MIN_BIN_STEP(), - invalidBinStepOverflow, - factory.MAX_BIN_STEP() - ) - ); - - factory.setPreset( - invalidBinStepOverflow, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME - ); - } - - function testForInvalidBinStepUnderflowReverts() public { - uint16 invalidBinStepUnderflow = uint16(factory.MIN_BIN_STEP() - 1); - vm.expectRevert( - abi.encodeWithSelector( - LBFactory__BinStepRequirementsBreached.selector, - factory.MIN_BIN_STEP(), - invalidBinStepUnderflow, - factory.MAX_BIN_STEP() - ) - ); - factory.setPreset( - invalidBinStepUnderflow, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME - ); - } - - function testSetFeesParametersOnPair() public { - ILBPair pair = createLBPairDefaultFees(usdc, wbtc); - - factory.setFeesParametersOnPair( - usdc, - wbtc, - DEFAULT_BIN_STEP, - DEFAULT_BASE_FACTOR - 1, - DEFAULT_FILTER_PERIOD - 1, - DEFAULT_DECAY_PERIOD - 1, - DEFAULT_REDUCTION_FACTOR - 1, - DEFAULT_VARIABLE_FEE_CONTROL - 1, - DEFAULT_PROTOCOL_SHARE - 1, - DEFAULT_MAX_VOLATILITY_ACCUMULATED - 1 - ); - - FeeHelper.FeeParameters memory feeParameters = pair.feeParameters(); - assertEq(feeParameters.volatilityAccumulated, 0); - assertEq(feeParameters.time, 0); - assertEq(feeParameters.binStep, DEFAULT_BIN_STEP); - assertEq(feeParameters.baseFactor, DEFAULT_BASE_FACTOR - 1); - assertEq(feeParameters.filterPeriod, DEFAULT_FILTER_PERIOD - 1); - assertEq(feeParameters.decayPeriod, DEFAULT_DECAY_PERIOD - 1); - assertEq(feeParameters.reductionFactor, DEFAULT_REDUCTION_FACTOR - 1); - assertEq(feeParameters.variableFeeControl, DEFAULT_VARIABLE_FEE_CONTROL - 1); - assertEq(feeParameters.protocolShare, DEFAULT_PROTOCOL_SHARE - 1); - assertEq(feeParameters.maxVolatilityAccumulated, DEFAULT_MAX_VOLATILITY_ACCUMULATED - 1); - } - - function testSetFeesParametersOnPairReverts() public { - createLBPairDefaultFees(usdc, wbtc); - vm.prank(ALICE); - vm.expectRevert(PendingOwnable__NotOwner.selector); - factory.setFeesParametersOnPair( - usdc, - wbtc, - DEFAULT_BIN_STEP, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED - ); - vm.expectRevert(abi.encodeWithSelector(LBFactory__LBPairNotCreated.selector, usdc, weth, DEFAULT_BIN_STEP)); - factory.setFeesParametersOnPair( - usdc, - weth, - DEFAULT_BIN_STEP, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED - ); - } - - function testForInvalidFilterPeriod() public { - createLBPairDefaultFees(usdc, wbtc); - - uint16 invalidFilterPeriod = DEFAULT_DECAY_PERIOD; - vm.expectRevert( - abi.encodeWithSelector(LBFactory__DecreasingPeriods.selector, invalidFilterPeriod, DEFAULT_DECAY_PERIOD) - ); - - factory.setFeesParametersOnPair( - usdc, - wbtc, - DEFAULT_BIN_STEP, - DEFAULT_BASE_FACTOR, - invalidFilterPeriod, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED - ); - } - - function testForInvalidProtocolShare() public { - createLBPairDefaultFees(usdc, wbtc); - uint16 invalidProtocolShare = uint16(factory.MAX_PROTOCOL_SHARE() + 1); - vm.expectRevert( - abi.encodeWithSelector( - LBFactory__ProtocolShareOverflows.selector, invalidProtocolShare, factory.MAX_PROTOCOL_SHARE() - ) - ); - - factory.setFeesParametersOnPair( - usdc, - wbtc, - DEFAULT_BIN_STEP, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - invalidProtocolShare, - DEFAULT_MAX_VOLATILITY_ACCUMULATED - ); - } - - function testForInvalidReductionFactor() public { - uint16 invalidReductionFactor = uint16(Constants.BASIS_POINT_MAX + 1); - vm.expectRevert( - abi.encodeWithSelector( - LBFactory__ReductionFactorOverflows.selector, invalidReductionFactor, Constants.BASIS_POINT_MAX - ) - ); - - factory.setPreset( - DEFAULT_BIN_STEP, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - invalidReductionFactor, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME - ); - } - - function testForSetLBPairIgnoredReverts() public { - createLBPairDefaultFees(usdc, weth); - - factory.setLBPairIgnored(usdc, weth, DEFAULT_BIN_STEP, true); - vm.expectRevert(LBFactory__LBPairIgnoredIsAlreadyInTheSameState.selector); - factory.setLBPairIgnored(usdc, weth, DEFAULT_BIN_STEP, true); - - vm.expectRevert(LBFactory__AddressZero.selector); - factory.setLBPairIgnored(usdc, weth, DEFAULT_BIN_STEP + 1, true); - } - - function testForSettingFlashloanFee() public { - uint256 flashFee = 7e14; - factory.setFlashLoanFee(flashFee); - assertEq(factory.flashLoanFee(), flashFee); - vm.expectRevert(abi.encodeWithSelector(LBFactory__SameFlashLoanFee.selector, flashFee)); - factory.setFlashLoanFee(flashFee); - flashFee = 0.1e18 + 1; - vm.expectRevert(abi.encodeWithSelector(LBFactory__FlashLoanFeeAboveMax.selector, flashFee, 0.1e18)); - factory.setFlashLoanFee(flashFee); - } - - function testForInvalidFeeRecipient() public { - vm.expectRevert(LBFactory__AddressZero.selector); - factory = new LBFactory(address(0), 8e14); - } - - function testsetFactoryLockedState() public { - vm.expectRevert(LBFactory__FactoryLockIsAlreadyInTheSameState.selector); - factory.setFactoryLockedState(true); - } - - function testFeesAboveMaxBaseFactorReverts(uint8 baseFactorIncrement) public { - vm.assume(baseFactorIncrement > 0); - uint16 baseFactorIncreased = DEFAULT_BASE_FACTOR + baseFactorIncrement; - - //copy of part of factory._getPackedFeeParameters function - uint256 _baseFee = (uint256(baseFactorIncreased) * DEFAULT_BIN_STEP) * 1e10; - uint256 _maxVariableFee = ( - DEFAULT_VARIABLE_FEE_CONTROL * (uint256(DEFAULT_MAX_VOLATILITY_ACCUMULATED) * DEFAULT_BIN_STEP) - * (uint256(DEFAULT_MAX_VOLATILITY_ACCUMULATED) * DEFAULT_BIN_STEP) - ) / 100; - - uint256 fee = _baseFee + _maxVariableFee; - vm.expectRevert(abi.encodeWithSelector(LBFactory__FeesAboveMax.selector, fee, factory.MAX_FEE())); - factory.setPreset( - DEFAULT_BIN_STEP, - baseFactorIncreased, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME - ); - } - - function testFeesAboveMaxVolatilityReverts(uint8 maxVolatilityIncrement) public { - vm.assume(maxVolatilityIncrement > 0); - uint24 volatilityAccumulated = DEFAULT_MAX_VOLATILITY_ACCUMULATED + maxVolatilityIncrement; - - //copy of part of factory._getPackedFeeParameters function - uint256 _baseFee = (uint256(DEFAULT_BASE_FACTOR) * DEFAULT_BIN_STEP) * 1e10; - uint256 _maxVariableFee = ( - DEFAULT_VARIABLE_FEE_CONTROL * (uint256(volatilityAccumulated) * DEFAULT_BIN_STEP) - * (uint256(volatilityAccumulated) * DEFAULT_BIN_STEP) - ) / 100; - uint256 fee = _baseFee + _maxVariableFee; - - vm.expectRevert(abi.encodeWithSelector(LBFactory__FeesAboveMax.selector, fee, factory.MAX_FEE())); - factory.setPreset( - DEFAULT_BIN_STEP, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - volatilityAccumulated, - DEFAULT_SAMPLE_LIFETIME - ); - } - - function testInvalidBinStepWhileCreatingLBPair() public { - vm.expectRevert(abi.encodeWithSelector(LBFactory__BinStepHasNoPreset.selector, DEFAULT_BIN_STEP + 1)); - createLBPairDefaultFeesFromStartIdAndBinStep(usdc, wbtc, ID_ONE, DEFAULT_BIN_STEP + 1); - } - - function testQuoteAssets() public { - assertEq(factory.getNumberOfQuoteAssets(), 4); - assertEq(address(factory.getQuoteAsset(0)), address(wavax)); - assertEq(address(factory.getQuoteAsset(1)), address(usdc)); - assertEq(address(factory.getQuoteAsset(2)), address(wbtc)); - assertEq(address(factory.getQuoteAsset(3)), address(weth)); - assertEq(factory.isQuoteAsset(wavax), true); - assertEq(factory.isQuoteAsset(usdc), true); - assertEq(factory.isQuoteAsset(wbtc), true); - assertEq(factory.isQuoteAsset(weth), true); - - vm.expectRevert(abi.encodeWithSelector(LBFactory__QuoteAssetAlreadyWhitelisted.selector, wbtc)); - factory.addQuoteAsset(wbtc); - - wbtc = new ERC20Mock(24); - vm.expectRevert(abi.encodeWithSelector(LBFactory__QuoteAssetNotWhitelisted.selector, wbtc)); - factory.removeQuoteAsset(wbtc); - - assertEq(factory.isQuoteAsset(wbtc), false); - vm.expectRevert(abi.encodeWithSelector(LBFactory__QuoteAssetNotWhitelisted.selector, wbtc)); - factory.createLBPair(usdc, wbtc, ID_ONE, DEFAULT_BIN_STEP); - - vm.expectEmit(true, true, true, true); - emit QuoteAssetAdded(wbtc); - factory.addQuoteAsset(wbtc); - assertEq(factory.isQuoteAsset(wbtc), true); - assertEq(address(factory.getQuoteAsset(4)), address(wbtc)); - - vm.expectEmit(true, true, true, true); - emit QuoteAssetRemoved(wbtc); - factory.removeQuoteAsset(wbtc); - assertEq(factory.isQuoteAsset(wbtc), false); - } -} From 77b015728283a44c16516980605d9c69bf168ba1 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Fri, 27 Jan 2023 04:56:41 +0100 Subject: [PATCH 09/47] safer encode/decode function, refactor of Decoder library --- src/LBFactory.sol | 4 +- src/libraries/OracleHelper.sol | 2 +- src/libraries/PairParameterHelper.sol | 274 ++++++++---------- src/libraries/math/Decoder.sol | 24 -- src/libraries/math/Encoded.sol | 185 ++++++++++++ .../math/LiquidityConfigurations.sol | 26 +- src/libraries/math/PackedUint128Math.sol | 22 +- src/libraries/math/SampleMath.sol | 110 +++---- test/SampleMath.t.sol | 18 -- 9 files changed, 375 insertions(+), 290 deletions(-) delete mode 100644 src/libraries/math/Decoder.sol create mode 100644 src/libraries/math/Encoded.sol diff --git a/src/LBFactory.sol b/src/LBFactory.sol index 663db300..acd4e02f 100644 --- a/src/LBFactory.sol +++ b/src/LBFactory.sol @@ -7,7 +7,7 @@ import "openzeppelin/utils/structs/EnumerableSet.sol"; import "./libraries/BinHelper.sol"; import "./libraries/Constants.sol"; -import "./libraries/math/Decoder.sol"; +import "./libraries/math/Encoded.sol"; import "./libraries/PendingOwnable.sol"; import "./libraries/math/SafeCast.sol"; import "./interfaces/ILBFactory.sol"; @@ -20,7 +20,7 @@ import "./libraries/ImmutableClone.sol"; /// Unless the `creationUnlocked` is `true`, only the owner of the factory can create pairs. contract LBFactory is PendingOwnable, ILBFactory { using SafeCast for uint256; - using Decoder for bytes32; + using Encoded for bytes32; using EnumerableSet for EnumerableSet.AddressSet; using PairParameterHelper for bytes32; diff --git a/src/libraries/OracleHelper.sol b/src/libraries/OracleHelper.sol index f1d6cb56..d8560e56 100644 --- a/src/libraries/OracleHelper.sol +++ b/src/libraries/OracleHelper.sol @@ -72,7 +72,7 @@ library OracleHelper { bytes32 oldestSample = oracle.samples[oracleId]; // Oreacle is not fully initialized yet - if (oldestSample >> SampleMath._SHIFT_CUMULATIVE_ID == 0) { + if (oldestSample >> SampleMath.OFFSET_CUMULATIVE_ID == 0) { length = oracleId; oldestSample = oracle.samples[0]; } diff --git a/src/libraries/PairParameterHelper.sol b/src/libraries/PairParameterHelper.sol index cb44d2d0..8ef6e007 100644 --- a/src/libraries/PairParameterHelper.sol +++ b/src/libraries/PairParameterHelper.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.10; import "./Constants.sol"; import "./math/SafeCast.sol"; +import "./math/Encoded.sol"; /** * @title Liquidity Book Pair Parameter Helper Library @@ -26,177 +27,186 @@ import "./math/SafeCast.sol"; */ library PairParameterHelper { using SafeCast for uint256; + using Encoded for bytes32; error PairParametersHelper__InvalidParameter(); - uint256 internal constant _FILTER_PERIOD_OFFSET = 16; - uint256 internal constant _DECAY_PERIOD_OFFSET = 28; - uint256 internal constant _REDUCTION_FACTOR_OFFSET = 40; - uint256 internal constant _VARIABLE_FEE_CONTROL_OFFSET = 54; - uint256 internal constant _PROTOCOL_SHARE_OFFSET = 78; - uint256 internal constant _MAX_VOL_ACC_OFFSET = 92; - uint256 internal constant _VOL_ACC_OFFSET = 112; - uint256 internal constant _VOLATILITY_REFERENCE_OFFSET = 132; - uint256 internal constant _INDEX_REF_OFFSET = 152; - uint256 internal constant _TIME_OFFSET = 176; - uint256 internal constant _ORACLE_ID_OFFSET = 216; - uint256 internal constant _ACTIVE_ID_OFFSET = 232; - - uint256 internal constant _STATIC_PARAMETER_MASK = 0xffffffffffffffffffffffffffff; - uint256 internal constant _DYNAMIC_PARAMETER_MASK = 0xffffffffffffffffffffffffffffffffffff; - - uint256 internal constant _BASE_FACTOR_MASK = 0xffff; - uint256 internal constant _FILTER_PERIOD_MASK = 0xfff; - uint256 internal constant _DECAY_PERIOD_MASK = 0xfff; - uint256 internal constant _REDUCTION_FACTOR_MASK = 0x3fff; - uint256 internal constant _VARIABLE_FEE_CONTROL_MASK = 0xffffff; - uint256 internal constant _PROTOCOL_SHARE_MASK = 0x3fff; - uint256 internal constant _VOLATILITY_MASK = 0xfffff; - uint256 internal constant _INDEX_REF_MASK = 0xffffff; - uint256 internal constant _TIME_MASK = 0xffffffffff; - uint256 internal constant _ORACLE_ID_MASK = 0xffff; - uint256 internal constant _ACTIVE_ID_MASK = 0xffffff; - - uint256 internal constant _MAX_BASIS_POINTS = 10_000; - uint256 internal constant _MAX_PROTOCOL_SHARE = 2_500; - uint256 internal constant _PRECISION = 1e18; - uint256 internal constant _PRECISION_SUB_ONE = _PRECISION - 1; + uint256 internal constant OFFSET_FILTER_PERIOD = 16; + uint256 internal constant OFFSET_DECAY_PERIOD = 28; + uint256 internal constant OFFSET_REDUCTION_FACTOR = 40; + uint256 internal constant OFFSET_VAR_FEE_CONTROL = 54; + uint256 internal constant OFFSET_PROTOCOL_SHARE = 78; + uint256 internal constant OFFSET_MAX_VOL_ACC = 92; + uint256 internal constant OFFSET_VOL_ACC = 112; + uint256 internal constant OFFSET_VOL_REF = 132; + uint256 internal constant OFFSET_ID_REF = 152; + uint256 internal constant OFFSET_TIME_LAST_UPDATE = 176; + uint256 internal constant OFFSET_ORACLE_ID = 216; + uint256 internal constant OFFSET_ACTIVE_ID = 232; + + uint256 internal constant MASK_STATIC_PARAMETER = 0xffffffffffffffffffffffffffff; + + uint256 internal constant MAX_OFFSET_PROTOCOL_SHARE = 2_500; /** * @dev Get the base factor from the encoded pair parameters - * @param params The encoded pair parameters + * @param params The encoded pair parameters, as follows: + * [0 - 16[: base factor (16 bits) + * [16 - 256[: other parameters * @return baseFactor The base factor */ function getBaseFactor(bytes32 params) internal pure returns (uint16 baseFactor) { - assembly { - baseFactor := and(params, _BASE_FACTOR_MASK) - } + baseFactor = params.decodeUint16(0); } /** * @dev Get the filter period from the encoded pair parameters - * @param params The encoded pair parameters + * @param params The encoded pair parameters, as follows: + * [0 - 16[: other parameters + * [16 - 28[: filter period (12 bits) + * [28 - 256[: other parameters * @return filterPeriod The filter period */ function getFilterPeriod(bytes32 params) internal pure returns (uint16 filterPeriod) { - assembly { - filterPeriod := shr(_FILTER_PERIOD_OFFSET, and(params, _FILTER_PERIOD_MASK)) - } + filterPeriod = params.decodeUint12(OFFSET_FILTER_PERIOD); } /** * @dev Get the decay period from the encoded pair parameters - * @param params The encoded pair parameters + * @param params The encoded pair parameters, as follows: + * [0 - 28[: other parameters + * [28 - 40[: decay period (12 bits) + * [40 - 256[: other parameters * @return decayPeriod The decay period */ function getDecayPeriod(bytes32 params) internal pure returns (uint16 decayPeriod) { - assembly { - decayPeriod := shr(_DECAY_PERIOD_OFFSET, and(params, _DECAY_PERIOD_MASK)) - } + decayPeriod = params.decodeUint12(OFFSET_DECAY_PERIOD); } /** * @dev Get the reduction factor from the encoded pair parameters - * @param params The encoded pair parameters + * @param params The encoded pair parameters, as follows: + * [0 - 40[: other parameters + * [40 - 54[: reduction factor (14 bits) + * [54 - 256[: other parameters * @return reductionFactor The reduction factor */ function getReductionFactor(bytes32 params) internal pure returns (uint16 reductionFactor) { - assembly { - reductionFactor := shr(_REDUCTION_FACTOR_OFFSET, and(params, _REDUCTION_FACTOR_MASK)) - } + reductionFactor = params.decodeUint14(OFFSET_REDUCTION_FACTOR); } /** * @dev Get the variable fee control from the encoded pair parameters - * @param params The encoded pair parameters + * @param params The encoded pair parameters, as follows: + * [0 - 54[: other parameters + * [54 - 78[: variable fee control (24 bits) + * [78 - 256[: other parameters * @return variableFeeControl The variable fee control */ function getVariableFeeControl(bytes32 params) internal pure returns (uint24 variableFeeControl) { - assembly { - variableFeeControl := shr(_VARIABLE_FEE_CONTROL_OFFSET, and(params, _VARIABLE_FEE_CONTROL_MASK)) - } + variableFeeControl = params.decodeUint24(OFFSET_VAR_FEE_CONTROL); } /** * @dev Get the protocol share from the encoded pair parameters - * @param params The encoded pair parameters + * @param params The encoded pair parameters, as follows: + * [0 - 78[: other parameters + * [78 - 92[: protocol share (14 bits) + * [92 - 256[: other parameters * @return protocolShare The protocol share */ function getProtocolShare(bytes32 params) internal pure returns (uint16 protocolShare) { - assembly { - protocolShare := shr(_PROTOCOL_SHARE_OFFSET, and(params, _PROTOCOL_SHARE_MASK)) - } + protocolShare = params.decodeUint14(OFFSET_PROTOCOL_SHARE); } /** * @dev Get the max volatility accumulated from the encoded pair parameters - * @param params The encoded pair parameters + * @param params The encoded pair parameters, as follows: + * [0 - 92[: other parameters + * [92 - 112[: max volatility accumulated (20 bits) + * [112 - 256[: other parameters * @return maxVolatilityAccumulated The max volatility accumulated */ function getMaxVolatilityAccumulated(bytes32 params) internal pure returns (uint24 maxVolatilityAccumulated) { - assembly { - maxVolatilityAccumulated := shr(_MAX_VOL_ACC_OFFSET, and(params, _VOLATILITY_MASK)) - } + maxVolatilityAccumulated = params.decodeUint20(OFFSET_MAX_VOL_ACC); } /** * @dev Get the volatility accumulated from the encoded pair parameters - * @param params The encoded pair parameters + * @param params The encoded pair parameters, as follows: + * [0 - 112[: other parameters + * [112 - 132[: volatility accumulated (20 bits) + * [132 - 256[: other parameters * @return volatilityAccumulated The volatility accumulated */ function getVolatilityAccumulated(bytes32 params) internal pure returns (uint24 volatilityAccumulated) { - assembly { - volatilityAccumulated := shr(_VOL_ACC_OFFSET, and(params, _VOLATILITY_MASK)) - } + volatilityAccumulated = params.decodeUint20(OFFSET_VOL_ACC); } /** * @dev Get the volatility reference from the encoded pair parameters - * @param params The encoded pair parameters + * @param params The encoded pair parameters, as follows: + * [0 - 132[: other parameters + * [132 - 152[: volatility reference (20 bits) + * [152 - 256[: other parameters * @return volatilityReference The volatility reference */ function getVolatilityReference(bytes32 params) internal pure returns (uint24 volatilityReference) { - assembly { - volatilityReference := shr(_VOLATILITY_REFERENCE_OFFSET, and(params, _VOLATILITY_MASK)) - } + volatilityReference = params.decodeUint20(OFFSET_VOL_REF); } /** * @dev Get the index reference from the encoded pair parameters - * @param params The encoded pair parameters + * @param params The encoded pair parameters, as follows: + * [0 - 152[: other parameters + * [152 - 176[: index reference (24 bits) + * [176 - 256[: other parameters * @return idReference The index reference */ function getIdReference(bytes32 params) internal pure returns (uint24 idReference) { - assembly { - idReference := shr(_INDEX_REF_OFFSET, and(params, _INDEX_REF_MASK)) - } + idReference = params.decodeUint24(OFFSET_ID_REF); } /** * @dev Get the time of last update from the encoded pair parameters - * @param params The encoded pair parameters + * @param params The encoded pair parameters, as follows: + * [0 - 176[: other parameters + * [176 - 216[: time of last update (40 bits) + * [216 - 256[: other parameters * @return timeOflastUpdate The time of last update */ function getTimeOfLastUpdate(bytes32 params) internal pure returns (uint40 timeOflastUpdate) { - assembly { - timeOflastUpdate := shr(_TIME_OFFSET, and(params, _TIME_MASK)) - } + timeOflastUpdate = params.decodeUint40(OFFSET_TIME_LAST_UPDATE); } /** * @dev Get the oracle id from the encoded pair parameters - * @param params The encoded pair parameters + * @param params The encoded pair parameters, as follows: + * [0 - 216[: other parameters + * [216 - 232[: oracle id (16 bits) + * [232 - 256[: other parameters * @return oracleId The oracle id */ function getOracleId(bytes32 params) internal pure returns (uint16 oracleId) { - assembly { - oracleId := shr(_ORACLE_ID_OFFSET, params) - } + oracleId = params.decodeUint16(OFFSET_ORACLE_ID); + } + + /** + * @dev Get the active index from the encoded pair parameters + * @param params The encoded pair parameters, as follows: + * [0 - 232[: other parameters + * [232 - 256[: active index (24 bits) + * @return activeId The active index + */ + function getActiveId(bytes32 params) internal pure returns (uint24 activeId) { + activeId = params.decodeUint24(OFFSET_ACTIVE_ID); } /** * @dev Get the delta between the active index and the reference index - * @param params The encoded pair parameters + * @param params The encoded pair parameters, as follows: + * [0 - 232[: other parameters + * [232 - 256[: active index (24 bits) * @param activeId The active index * @return The delta */ @@ -207,17 +217,6 @@ library PairParameterHelper { } } - /** - * @dev Get the active index from the encoded pair parameters - * @param params The encoded pair parameters - * @return activeId The active index - */ - function getActiveId(bytes32 params) internal pure returns (uint24 activeId) { - assembly { - activeId := shr(_ACTIVE_ID_OFFSET, params) - } - } - /** * @dev Set the oracle id in the encoded pair parameters * @param params The encoded pair parameters @@ -225,11 +224,7 @@ library PairParameterHelper { * @return The updated encoded pair parameters */ function setOracleId(bytes32 params, uint16 oracleId) internal pure returns (bytes32) { - assembly { - params := and(params, shl(_ORACLE_ID_OFFSET, not(_ORACLE_ID_MASK))) - params := or(params, shl(_ORACLE_ID_OFFSET, oracleId)) - } - return params; + return params.set(oracleId, Encoded.MASK_UINT16, OFFSET_ORACLE_ID); } /** @@ -239,13 +234,9 @@ library PairParameterHelper { * @return The updated encoded pair parameters */ function setVolatilityReference(bytes32 params, uint24 volRef) internal pure returns (bytes32) { - if (volRef > _VOLATILITY_MASK) revert PairParametersHelper__InvalidParameter(); + if (volRef > Encoded.MASK_UINT20) revert PairParametersHelper__InvalidParameter(); - assembly { - params := and(params, shl(_VOLATILITY_REFERENCE_OFFSET, not(_VOLATILITY_MASK))) - params := or(params, shl(_VOLATILITY_REFERENCE_OFFSET, volRef)) - } - return params; + return params.set(volRef, Encoded.MASK_UINT20, OFFSET_VOL_REF); } /** @@ -255,10 +246,7 @@ library PairParameterHelper { * @return newParams The updated encoded pair parameters */ function setActiveId(bytes32 params, uint24 activeId) internal pure returns (bytes32 newParams) { - assembly { - params := and(params, shl(_ACTIVE_ID_OFFSET, not(_ACTIVE_ID_MASK))) - newParams := or(params, shl(_ACTIVE_ID_OFFSET, activeId)) - } + return params.set(activeId, Encoded.MASK_UINT24, OFFSET_ACTIVE_ID); } /** @@ -284,23 +272,23 @@ library PairParameterHelper { uint24 maxVolatilityAccumulated ) internal pure returns (bytes32) { if ( - filterPeriod > decayPeriod || decayPeriod > _DECAY_PERIOD_MASK || reductionFactor > _MAX_BASIS_POINTS - || protocolShare > _MAX_PROTOCOL_SHARE || maxVolatilityAccumulated > _VOLATILITY_MASK + filterPeriod > decayPeriod || decayPeriod > Encoded.MASK_UINT12 + || reductionFactor > Constants.BASIS_POINT_MAX || protocolShare > MAX_OFFSET_PROTOCOL_SHARE + || maxVolatilityAccumulated > Encoded.MASK_UINT20 ) revert PairParametersHelper__InvalidParameter(); + uint256 staticParams; + assembly { - params := and(params, not(_STATIC_PARAMETER_MASK)) - - params := or(params, and(baseFactor, _BASE_FACTOR_MASK)) - params := or(params, shl(_FILTER_PERIOD_OFFSET, and(filterPeriod, _FILTER_PERIOD_MASK))) - params := or(params, shl(_DECAY_PERIOD_OFFSET, and(decayPeriod, _DECAY_PERIOD_MASK))) - params := or(params, shl(_REDUCTION_FACTOR_OFFSET, and(reductionFactor, _REDUCTION_FACTOR_MASK))) - params := or(params, shl(_VARIABLE_FEE_CONTROL_OFFSET, and(variableFeeControl, _VARIABLE_FEE_CONTROL_MASK))) - params := or(params, shl(_PROTOCOL_SHARE_OFFSET, and(protocolShare, _PROTOCOL_SHARE_MASK))) - params := or(params, shl(_MAX_VOL_ACC_OFFSET, and(maxVolatilityAccumulated, _VOLATILITY_MASK))) + staticParams := or(baseFactor, shl(OFFSET_FILTER_PERIOD, filterPeriod)) + staticParams := or(staticParams, shl(OFFSET_DECAY_PERIOD, decayPeriod)) + staticParams := or(staticParams, shl(OFFSET_REDUCTION_FACTOR, reductionFactor)) + staticParams := or(staticParams, shl(OFFSET_VAR_FEE_CONTROL, variableFeeControl)) + staticParams := or(staticParams, shl(OFFSET_PROTOCOL_SHARE, protocolShare)) + staticParams := or(staticParams, shl(OFFSET_MAX_VOL_ACC, maxVolatilityAccumulated)) } - return params; + return params.set(staticParams, MASK_STATIC_PARAMETER, 0); } /** @@ -310,10 +298,7 @@ library PairParameterHelper { */ function updateIdReference(bytes32 params) internal pure returns (bytes32 newParams) { uint24 activeId = getActiveId(params); - assembly { - params := and(params, shl(_INDEX_REF_OFFSET, not(_INDEX_REF_MASK))) - newParams := or(params, shl(_INDEX_REF_OFFSET, activeId)) - } + return params.set(activeId, Encoded.MASK_UINT24, OFFSET_ID_REF); } /** @@ -323,10 +308,7 @@ library PairParameterHelper { */ function updateTimeOfLastUpdate(bytes32 params) internal view returns (bytes32 newParams) { uint40 currentTime = block.timestamp.safe40(); - assembly { - params := and(params, shl(_TIME_OFFSET, not(_TIME_MASK))) - newParams := or(params, shl(_TIME_OFFSET, currentTime)) - } + return params.set(currentTime, Encoded.MASK_UINT40, OFFSET_TIME_LAST_UPDATE); } /** @@ -340,28 +322,29 @@ library PairParameterHelper { uint24 volRef; unchecked { - volRef = uint24(volAcc * reductionFactor / _MAX_BASIS_POINTS); + volRef = uint24(volAcc * reductionFactor / Constants.BASIS_POINT_MAX); } return setVolatilityReference(params, volRef); } /** - * @dev Calculates the base fee + * @dev Calculates the base fee, with 18 decimals * @param params The encoded pair parameters - * @param binStep The bin step + * @param binStep The bin step (in 20_000th) * @return baseFee The base fee */ function getBaseFee(bytes32 params, uint8 binStep) internal pure returns (uint256) { unchecked { - return uint256(getBaseFactor(params)) * binStep * 1e10; + // Base factor is in basis points, binStep is in 20_000th, so we multiply by 5e9 + return uint256(getBaseFactor(params)) * binStep * 5e9; } } /** * @dev Calculates the variable fee * @param params The encoded pair parameters - * @param binStep The bin step + * @param binStep The bin step (in 20_000th) * @return variableFee The variable fee */ function getVariableFee(bytes32 params, uint8 binStep) internal pure returns (uint256 variableFee) { @@ -369,8 +352,10 @@ library PairParameterHelper { if (variableFeeControl != 0) { unchecked { + // The volatility accumulated is in basis points, binStep is in 20_000th, + // and the variable fee control is in basis points, so the result is in 400e18th uint256 prod = uint256(getVolatilityAccumulated(params)) * binStep; - variableFee = (prod * prod * variableFeeControl + 99) / 100; + variableFee = (prod * prod * variableFeeControl + 399) / 400; } } } @@ -378,7 +363,7 @@ library PairParameterHelper { /** * @dev Calculates the total fee, which is the sum of the base fee and the variable fee * @param params The encoded pair parameters - * @param binStep The bin step + * @param binStep The bin step (in 20_000th) * @return totalFee The total fee */ function getTotalFee(bytes32 params, uint8 binStep) internal pure returns (uint128) { @@ -394,22 +379,18 @@ library PairParameterHelper { * @return The updated encoded pair parameters */ function updateVolatilityAccumulated(bytes32 params, uint24 activeId) internal pure returns (bytes32) { - uint24 deltaId = getDeltaId(params, activeId); + uint256 deltaId = getDeltaId(params, activeId); uint256 volAcc; unchecked { - volAcc = (uint256(getVolatilityAccumulated(params)) + deltaId * _MAX_BASIS_POINTS); + volAcc = (uint256(getVolatilityAccumulated(params)) + deltaId * Constants.BASIS_POINT_MAX); } - uint24 maxVolAcc = getMaxVolatilityAccumulated(params); + uint256 maxVolAcc = getMaxVolatilityAccumulated(params); - if (volAcc > maxVolAcc) volAcc = maxVolAcc; + volAcc = volAcc > maxVolAcc ? maxVolAcc : volAcc; - assembly { - params := and(params, shl(_VOL_ACC_OFFSET, not(_VOLATILITY_MASK))) - params := or(params, shl(_VOL_ACC_OFFSET, volAcc)) - } - return params; + return params.set(volAcc, Encoded.MASK_UINT20, OFFSET_VOL_ACC); } /** @@ -418,12 +399,11 @@ library PairParameterHelper { * @return The updated encoded pair parameters */ function updateReferences(bytes32 params) internal view returns (bytes32) { - uint256 deltaT = block.timestamp - getTimeOfLastUpdate(params); + uint256 dt = block.timestamp - getTimeOfLastUpdate(params); - if (deltaT >= getFilterPeriod(params)) { + if (dt >= getFilterPeriod(params)) { params = updateIdReference(params); - if (deltaT < getDecayPeriod(params)) params = updateVolatilityReference(params); - else params = setVolatilityReference(params, 0); + params = dt < getDecayPeriod(params) ? updateVolatilityReference(params) : setVolatilityReference(params, 0); } return updateTimeOfLastUpdate(params); diff --git a/src/libraries/math/Decoder.sol b/src/libraries/math/Decoder.sol deleted file mode 100644 index b6119629..00000000 --- a/src/libraries/math/Decoder.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -/** - * @title Liquidity Book Decoder Library - * @author Trader Joe - * @notice Helper contract used for decoding bytes32 sample - */ -library Decoder { - /** - * @notice Internal function to decode a bytes32 sample using a mask and offset - * @dev This function can overflow - * @param encoded The encoded value - * @param mask The mask - * @param offset The offset - * @return value The decoded value - */ - function decode(bytes32 encoded, uint256 mask, uint256 offset) internal pure returns (uint256 value) { - assembly { - value := and(shr(offset, encoded), mask) - } - } -} diff --git a/src/libraries/math/Encoded.sol b/src/libraries/math/Encoded.sol new file mode 100644 index 00000000..937578e8 --- /dev/null +++ b/src/libraries/math/Encoded.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +/** + * @title Liquidity Book Encoded Library + * @author Trader Joe + * @notice Helper contract used for decoding bytes32 sample + */ +library Encoded { + uint256 internal constant MASK_UINT1 = 0x1; + uint256 internal constant MASK_UINT8 = 0xff; + uint256 internal constant MASK_UINT12 = 0xfff; + uint256 internal constant MASK_UINT14 = 0x3fff; + uint256 internal constant MASK_UINT16 = 0xffff; + uint256 internal constant MASK_UINT20 = 0xfffff; + uint256 internal constant MASK_UINT24 = 0xffffff; + uint256 internal constant MASK_UINT40 = 0xffffffffff; + uint256 internal constant MASK_UINT64 = 0xffffffffffffffff; + uint256 internal constant MASK_UINT128 = 0xffffffffffffffffffffffffffffffff; + + /** + * @notice Internal function to set a value in an encoded bytes32 using a mask and offset + * @dev This function can overflow + * @param encoded The previous encoded value + * @param value The value to encode + * @param mask The mask + * @param offset The offset + * @return newEncoded The new encoded value + */ + function set(bytes32 encoded, uint256 value, uint256 mask, uint256 offset) + internal + pure + returns (bytes32 newEncoded) + { + assembly { + newEncoded := and(encoded, not(shl(offset, mask))) + newEncoded := or(newEncoded, shl(offset, and(value, mask))) + } + } + + /** + * @notice Internal function to decode a bytes32 sample using a mask and offset + * @dev This function can overflow + * @param encoded The encoded value + * @param mask The mask + * @param offset The offset + * @return value The decoded value + */ + function decode(bytes32 encoded, uint256 mask, uint256 offset) internal pure returns (uint256 value) { + assembly { + value := and(shr(offset, encoded), mask) + } + } + + /** + * @notice Internal function to decode a bytes32 sample into a uint1 using an offset + * @dev This function can overflow + * @param encoded The encoded value + * @param offset The offset + * @return value The decoded value as a uint8, since uint1 is not supported + */ + function decodeUint1(bytes32 encoded, uint256 offset) internal pure returns (uint8 value) { + assembly { + value := and(shr(offset, encoded), MASK_UINT1) + } + } + + /** + * @notice Internal function to decode a bytes32 sample into a uint8 using an offset + * @dev This function can overflow + * @param encoded The encoded value + * @param offset The offset + * @return value The decoded value + */ + function decodeUint8(bytes32 encoded, uint256 offset) internal pure returns (uint8 value) { + assembly { + value := and(shr(offset, encoded), MASK_UINT8) + } + } + + /** + * @notice Internal function to decode a bytes32 sample into a uint12 using an offset + * @dev This function can overflow + * @param encoded The encoded value + * @param offset The offset + * @return value The decoded value as a uint16, since uint12 is not supported + */ + function decodeUint12(bytes32 encoded, uint256 offset) internal pure returns (uint16 value) { + assembly { + value := and(shr(offset, encoded), MASK_UINT12) + } + } + + /** + * @notice Internal function to decode a bytes32 sample into a uint14 using an offset + * @dev This function can overflow + * @param encoded The encoded value + * @param offset The offset + * @return value The decoded value as a uint16, since uint14 is not supported + */ + function decodeUint14(bytes32 encoded, uint256 offset) internal pure returns (uint16 value) { + assembly { + value := and(shr(offset, encoded), MASK_UINT14) + } + } + + /** + * @notice Internal function to decode a bytes32 sample into a uint16 using an offset + * @dev This function can overflow + * @param encoded The encoded value + * @param offset The offset + * @return value The decoded value + */ + function decodeUint16(bytes32 encoded, uint256 offset) internal pure returns (uint16 value) { + assembly { + value := and(shr(offset, encoded), MASK_UINT16) + } + } + + /** + * @notice Internal function to decode a bytes32 sample into a uint20 using an offset + * @dev This function can overflow + * @param encoded The encoded value + * @param offset The offset + * @return value The decoded value as a uint24, since uint20 is not supported + */ + function decodeUint20(bytes32 encoded, uint256 offset) internal pure returns (uint24 value) { + assembly { + value := and(shr(offset, encoded), MASK_UINT20) + } + } + + /** + * @notice Internal function to decode a bytes32 sample into a uint24 using an offset + * @dev This function can overflow + * @param encoded The encoded value + * @param offset The offset + * @return value The decoded value + */ + function decodeUint24(bytes32 encoded, uint256 offset) internal pure returns (uint24 value) { + assembly { + value := and(shr(offset, encoded), MASK_UINT24) + } + } + + /** + * @notice Internal function to decode a bytes32 sample into a uint40 using an offset + * @dev This function can overflow + * @param encoded The encoded value + * @param offset The offset + * @return value The decoded value + */ + function decodeUint40(bytes32 encoded, uint256 offset) internal pure returns (uint40 value) { + assembly { + value := and(shr(offset, encoded), MASK_UINT40) + } + } + + /** + * @notice Internal function to decode a bytes32 sample into a uint64 using an offset + * @dev This function can overflow + * @param encoded The encoded value + * @param offset The offset + * @return value The decoded value + */ + function decodeUint64(bytes32 encoded, uint256 offset) internal pure returns (uint64 value) { + assembly { + value := and(shr(offset, encoded), MASK_UINT64) + } + } + + /** + * @notice Internal function to decode a bytes32 sample into a uint128 using an offset + * @dev This function can overflow + * @param encoded The encoded value + * @param offset The offset + * @return value The decoded value + */ + function decodeUint128(bytes32 encoded, uint256 offset) internal pure returns (uint128 value) { + assembly { + value := and(shr(offset, encoded), MASK_UINT128) + } + } +} diff --git a/src/libraries/math/LiquidityConfigurations.sol b/src/libraries/math/LiquidityConfigurations.sol index cac7254f..2f718806 100644 --- a/src/libraries/math/LiquidityConfigurations.sol +++ b/src/libraries/math/LiquidityConfigurations.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.10; import "./PackedUint128Math.sol"; -import "./Decoder.sol"; +import "./Encoded.sol"; /** * @title Liquidity Book Liquidity Configurations Library @@ -11,16 +11,16 @@ import "./Decoder.sol"; * @notice This library contains functions to encode and decode the config of a pool and interact with the encoded bytes32. */ library LiquidityConfigurations { - using Decoder for bytes32; using PackedUint128Math for bytes32; using PackedUint128Math for uint128; + using Encoded for bytes32; error LiquidityConfigurations__InvalidConfig(); - uint256 private constant _OFFSET_DISTRIBUTION_Y = 24; - uint256 private constant _OFFSET_DISTRIBUTION_X = 88; + uint256 private constant OFFSET_DISTRIBUTION_Y = 24; + uint256 private constant OFFSET_DISTRIBUTION_X = 88; - uint256 private constant _PRECISION = 1e18; + uint256 private constant PRECISION = 1e18; /** * @dev Encode the distributionX, distributionY and id into a single bytes32 @@ -39,7 +39,7 @@ library LiquidityConfigurations { returns (bytes32 config) { assembly { - config := or(shl(_OFFSET_DISTRIBUTION_Y, distributionX), or(shl(_OFFSET_DISTRIBUTION_X, distributionY), id)) + config := or(shl(OFFSET_DISTRIBUTION_X, distributionX), or(shl(OFFSET_DISTRIBUTION_Y, distributionY), id)) } } @@ -59,13 +59,11 @@ library LiquidityConfigurations { pure returns (uint64 distributionX, uint64 distributionY, uint24 id) { - assembly { - distributionX := shr(_OFFSET_DISTRIBUTION_Y, config) - distributionY := shr(_OFFSET_DISTRIBUTION_X, config) - id := config - } + distributionX = config.decodeUint64(OFFSET_DISTRIBUTION_X); + distributionY = config.decodeUint64(OFFSET_DISTRIBUTION_Y); + id = config.decodeUint24(0); - if (uint256(config) > type(uint152).max || distributionX > _PRECISION || distributionY > _PRECISION) { + if (uint256(config) > type(uint152).max || distributionX > PRECISION || distributionY > PRECISION) { revert LiquidityConfigurations__InvalidConfig(); } } @@ -91,8 +89,8 @@ library LiquidityConfigurations { (uint128 x1, uint128 x2) = amountsIn.decode(); assembly { - x1 := div(mul(x1, distributionX), _PRECISION) - x2 := div(mul(x2, distributionY), _PRECISION) + x1 := div(mul(x1, distributionX), PRECISION) + x2 := div(mul(x2, distributionY), PRECISION) } return (x1.encode(x2), id); diff --git a/src/libraries/math/PackedUint128Math.sol b/src/libraries/math/PackedUint128Math.sol index a422a96c..0671439e 100644 --- a/src/libraries/math/PackedUint128Math.sol +++ b/src/libraries/math/PackedUint128Math.sol @@ -19,8 +19,8 @@ library PackedUint128Math { error PackedUint128Math__AddFirstSubSecondOverflow(); error PackedUint128Math__MultiplierBiggerThanMax(); - uint256 private constant _OFFSET = 128; - uint256 private constant _MASK_128 = 0xffffffffffffffffffffffffffffffff; + uint256 private constant OFFSET = 128; + uint256 private constant MASK_128 = 0xffffffffffffffffffffffffffffffff; /** * @dev Encodes two uint128 into a single bytes32 @@ -32,7 +32,7 @@ library PackedUint128Math { */ function encode(uint128 x1, uint128 x2) internal pure returns (bytes32 z) { assembly { - z := or(x1, shl(_OFFSET, x2)) + z := or(x1, shl(OFFSET, x2)) } } @@ -58,7 +58,7 @@ library PackedUint128Math { */ function encodeSecond(uint128 x2) internal pure returns (bytes32 z) { assembly { - z := shl(_OFFSET, x2) + z := shl(OFFSET, x2) } } @@ -88,8 +88,8 @@ library PackedUint128Math { */ function decode(bytes32 z) internal pure returns (uint128 x1, uint128 x2) { assembly { - x1 := and(z, _MASK_128) - x2 := shr(_OFFSET, z) + x1 := and(z, MASK_128) + x2 := shr(OFFSET, z) } } @@ -102,7 +102,7 @@ library PackedUint128Math { */ function decodeFirst(bytes32 z) internal pure returns (uint128 x1) { assembly { - x1 := and(z, _MASK_128) + x1 := and(z, MASK_128) } } @@ -115,7 +115,7 @@ library PackedUint128Math { */ function decodeSecond(bytes32 z) internal pure returns (uint128 x2) { assembly { - x2 := shr(_OFFSET, z) + x2 := shr(OFFSET, z) } } @@ -263,11 +263,11 @@ library PackedUint128Math { // ``` // max(x{1,2} * multiplier) = type(uint128).max * type(uint128).max // = type(uint256).max - (2**129 - 2) - // _MASK_128 = 2**128 - 1 < 2**129 - 2 + // MASK_128 = 2**128 - 1 < 2**129 - 2 // ``` assembly { - x1 := shr(_OFFSET, add(mul(x1, multiplier), _MASK_128)) - x2 := shr(_OFFSET, add(mul(x2, multiplier), _MASK_128)) + x1 := shr(OFFSET, add(mul(x1, multiplier), MASK_128)) + x2 := shr(OFFSET, add(mul(x2, multiplier), MASK_128)) } return encode(x1, x2); diff --git a/src/libraries/math/SampleMath.sol b/src/libraries/math/SampleMath.sol index 4a9920c1..d81a9756 100644 --- a/src/libraries/math/SampleMath.sol +++ b/src/libraries/math/SampleMath.sol @@ -2,6 +2,8 @@ pragma solidity 0.8.10; +import "./Encoded.sol"; + /** * @title Liquidity Book Sample Math Library * @author Trader Joe @@ -16,11 +18,13 @@ pragma solidity 0.8.10; * 216 - 256: sample creation timestamp (40 bits) */ library SampleMath { - uint256 internal constant _SHIFT_CUMULATIVE_ID = 16; - uint256 internal constant _SHIFT_CUMULATIVE_VOLATILITY = 80; - uint256 internal constant _SHIFT_CUMULATIVE_BIN_CROSSED = 144; - uint256 internal constant _SHIFT_SAMPLE_LIFETIME = 208; - uint256 internal constant _SHIFT_SAMPLE_CREATION = 216; + using Encoded for bytes32; + + uint256 internal constant OFFSET_CUMULATIVE_ID = 16; + uint256 internal constant OFFSET_CUMULATIVE_VOLATILITY = 80; + uint256 internal constant OFFSET_CUMULATIVE_BIN_CROSSED = 144; + uint256 internal constant OFFSET_SAMPLE_LIFETIME = 208; + uint256 internal constant OFFSET_SAMPLE_CREATION = 216; /** * @dev Encodes a sample @@ -41,122 +45,82 @@ library SampleMath { uint40 createdAt ) internal pure returns (bytes32 sample) { assembly { - sample := or(oracleLength, shl(_SHIFT_CUMULATIVE_ID, cumulativeId)) - sample := or(sample, shl(_SHIFT_CUMULATIVE_VOLATILITY, cumulativeVolatility)) - sample := or(sample, shl(_SHIFT_CUMULATIVE_BIN_CROSSED, cumulativeBinCrossed)) - sample := or(sample, shl(_SHIFT_SAMPLE_LIFETIME, sampleLifetime)) - sample := or(sample, shl(_SHIFT_SAMPLE_CREATION, createdAt)) + sample := or(oracleLength, shl(OFFSET_CUMULATIVE_ID, cumulativeId)) + sample := or(sample, shl(OFFSET_CUMULATIVE_VOLATILITY, cumulativeVolatility)) + sample := or(sample, shl(OFFSET_CUMULATIVE_BIN_CROSSED, cumulativeBinCrossed)) + sample := or(sample, shl(OFFSET_SAMPLE_LIFETIME, sampleLifetime)) + sample := or(sample, shl(OFFSET_SAMPLE_CREATION, createdAt)) } } - /** - * @dev Decodes an encoded sample and return all the values - * @param sample The encoded sample - * @return oracleLength The oracle length - * @return cumulativeId The cumulative id - * @return cumulativeVolatility The cumulative volatility - * @return cumulativeBinCrossed The cumulative bin crossed - * @return sampleLifetime The sample lifetime - * @return createdAt The sample creation timestamp - */ - function decode(bytes32 sample) - internal - pure - returns ( - uint16 oracleLength, - uint64 cumulativeId, - uint64 cumulativeVolatility, - uint64 cumulativeBinCrossed, - uint8 sampleLifetime, - uint40 createdAt - ) - { - oracleLength = getOracleLength(sample); - cumulativeId = getCumulativeId(sample); - cumulativeVolatility = getCumulativeVolatility(sample); - cumulativeBinCrossed = getCumulativeBinCrossed(sample); - sampleLifetime = getSampleLifetime(sample); - createdAt = getSampleCreation(sample); - } - /** * @dev Gets the oracle length from an encoded sample - * @param sample The encoded sample + * @param sample The encoded sample as follows: + * [0 - 16[: oracle length (16 bits) + * [16 - 256[: any (240 bits) * @return length The oracle length */ function getOracleLength(bytes32 sample) internal pure returns (uint16 length) { - assembly { - length := sample - } + return sample.decodeUint16(0); } /** * @dev Gets the cumulative id from an encoded sample * @param sample The encoded sample as follows: - * [0 - 16[: oracle length (16 bits) - * [16 - 256[: any (240 bits) + * [0 - 16[: any (16 bits) + * [16 - 80[: cumulative id (64 bits) + * [80 - 256[: any (176 bits) * @return id The cumulative id */ function getCumulativeId(bytes32 sample) internal pure returns (uint64 id) { - assembly { - id := shr(_SHIFT_CUMULATIVE_ID, sample) - } + return sample.decodeUint64(OFFSET_CUMULATIVE_ID); } /** * @dev Gets the cumulative volatility accumulated from an encoded sample * @param sample The encoded sample as follows: - * [0 - 16[: any (16 bits) - * [16 - 80[: cumulative id (64 bits) - * [80 - 256[: any (176 bits) + * [0 - 80[: any (80 bits) + * [80 - 144[: cumulative volatility accumulated (64 bits) + * [144 - 256[: any (112 bits) * @return volatilityAccumulated The cumulative volatility */ function getCumulativeVolatility(bytes32 sample) internal pure returns (uint64 volatilityAccumulated) { - assembly { - volatilityAccumulated := shr(_SHIFT_CUMULATIVE_VOLATILITY, sample) - } + return sample.decodeUint64(OFFSET_CUMULATIVE_VOLATILITY); } /** * @dev Gets the cumulative bin crossed from an encoded sample * @param sample The encoded sample as follows: - * [0 - 80[: any (80 bits) - * [80 - 144[: cumulative volatility accumulated (64 bits) - * [144 - 256[: any (112 bits) + * [0 - 144[: any (144 bits) + * [144 - 208[: cumulative bin crossed (64 bits) + * [208 - 256[: any (48 bits) * @return binCrossed The cumulative bin crossed */ function getCumulativeBinCrossed(bytes32 sample) internal pure returns (uint64 binCrossed) { - assembly { - binCrossed := shr(_SHIFT_CUMULATIVE_BIN_CROSSED, sample) - } + return sample.decodeUint64(OFFSET_CUMULATIVE_BIN_CROSSED); } /** * @dev Gets the sample lifetime from an encoded sample * @param sample The encoded sample as follows: - * [0 - 144[: any (144 bits) - * [144 - 208[: cumulative bin crossed (64 bits) - * [208 - 256[: any (48 bits) + * [0 - 208[: any (208 bits) + * [208 - 216[: sample lifetime (8 bits) + * [216 - 256[: any (40 bits) * @return lifetime The sample lifetime */ function getSampleLifetime(bytes32 sample) internal pure returns (uint8 lifetime) { - assembly { - lifetime := shr(_SHIFT_SAMPLE_LIFETIME, sample) - } + return sample.decodeUint8(OFFSET_SAMPLE_LIFETIME); } /** * @dev Gets the sample creation timestamp from an encoded sample * @param sample The encoded sample as follows: - * [0 - 208[: any (208 bits) - * [208 - 216[: sample lifetime (8 bits) - * [216 - 256[: any (40 bits) + * [0 - 216[: any (216 bits) + * [216 - 256[: sample creation timestamp (40 bits) * @return creation The sample creation timestamp */ function getSampleCreation(bytes32 sample) internal pure returns (uint40 creation) { - assembly { - creation := shr(_SHIFT_SAMPLE_CREATION, sample) - } + return sample.decodeUint40(OFFSET_SAMPLE_CREATION); } /** diff --git a/test/SampleMath.t.sol b/test/SampleMath.t.sol index 1b0b50f0..06961493 100644 --- a/test/SampleMath.t.sol +++ b/test/SampleMath.t.sol @@ -78,24 +78,6 @@ contract SampleMathTest is Test { assertEq(sample.getSampleCreation(), createdAt, "testFuzz_encode::6"); } - function testFuzz_decode(bytes32 sample) external { - ( - uint16 oracleLength, - uint64 cumulativeId, - uint64 cumulativeVolatility, - uint64 cumulativeBinCrossed, - uint8 sampleLifetime, - uint40 createdAt - ) = SampleMath.decode(sample); - - assertEq(oracleLength, sample.getOracleLength(), "testFuzz_decode::1"); - assertEq(cumulativeId, sample.getCumulativeId(), "testFuzz_decode::2"); - assertEq(cumulativeVolatility, sample.getCumulativeVolatility(), "testFuzz_decode::3"); - assertEq(cumulativeBinCrossed, sample.getCumulativeBinCrossed(), "testFuzz_decode::4"); - assertEq(sampleLifetime, sample.getSampleLifetime(), "testFuzz_decode::5"); - assertEq(createdAt, sample.getSampleCreation(), "testFuzz_decode::6"); - } - function testFuzz_GetWeightedAverage(bytes32 sample1, bytes32 sample2, uint40 weight1, uint40 weight2) external { uint256 totalWeight = uint256(weight1) + weight2; From 0befb968aa9561e7b78ec1dc4f084f6677e93d64 Mon Sep 17 00:00:00 2001 From: Mathieu <85969303+Mathieu-Be@users.noreply.github.com> Date: Wed, 1 Feb 2023 16:46:40 +0100 Subject: [PATCH 10/47] Merge cleanup (#75) * cleanup * fix tests * fix max bin step * change to lbPair casing * remove underscores on internal variables * last remaining underscores * Add getters for the quoter state variables --- src/LBFactory.sol | 856 ++++++++------- src/LBPair.sol | 10 +- src/LBQuoter.sol | 178 ++-- src/LBRouter.sol | 990 +++++++++--------- src/LBToken.sol | 2 +- src/interfaces/IJoeRouter02.sol | 2 +- src/interfaces/ILBFactory.sol | 49 +- src/interfaces/ILBFlashLoanCallback.sol | 2 +- src/interfaces/ILBLegacyFactory.sol | 42 +- src/interfaces/ILBLegacyPair.sol | 14 + src/interfaces/ILBLegacyRouter.sol | 11 +- src/interfaces/ILBRouter.sol | 26 +- src/interfaces/IWAVAX.sol | 2 +- src/libraries/BinHelper.sol | 20 +- src/libraries/FeeHelper.sol | 4 +- src/libraries/OracleHelper.sol | 6 +- src/libraries/PairParameterHelper.sol | 6 +- src/libraries/PendingOwnable.sol | 2 +- src/libraries/PriceHelper.sol | 6 +- src/libraries/TokenHelper.sol | 4 +- .../math/LiquidityConfigurations.sol | 4 +- src/libraries/math/PackedUint128Math.sol | 4 +- src/libraries/math/SampleMath.sol | 2 +- src/libraries/math/TreeMath.sol | 2 +- src/libraries/math/Uint128x128Math.sol | 6 +- src/libraries/math/Uint256x256Math.sol | 2 +- test/LBFactory.t.sol | 206 ++-- test/helpers/TestHelper.sol | 8 +- test/integration/LBQuoter.t.sol | 7 +- 29 files changed, 1252 insertions(+), 1221 deletions(-) create mode 100644 src/interfaces/ILBLegacyPair.sol diff --git a/src/LBFactory.sol b/src/LBFactory.sol index acd4e02f..f1916f45 100644 --- a/src/LBFactory.sol +++ b/src/LBFactory.sol @@ -2,49 +2,49 @@ pragma solidity 0.8.10; -import "openzeppelin/proxy/Clones.sol"; -import "openzeppelin/utils/structs/EnumerableSet.sol"; +import {EnumerableSet} from "openzeppelin/utils/structs/EnumerableSet.sol"; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; -import "./libraries/BinHelper.sol"; -import "./libraries/Constants.sol"; -import "./libraries/math/Encoded.sol"; -import "./libraries/PendingOwnable.sol"; -import "./libraries/math/SafeCast.sol"; -import "./interfaces/ILBFactory.sol"; -import "./libraries/ImmutableClone.sol"; +import {BinHelper, PairParameterHelper} from "./libraries/BinHelper.sol"; +import {Constants} from "./libraries/Constants.sol"; +import {Encoded} from "./libraries/math/Encoded.sol"; +import {ImmutableClone} from "./libraries/ImmutableClone.sol"; +import {PendingOwnable} from "./libraries/PendingOwnable.sol"; +import {PriceHelper} from "./libraries/PriceHelper.sol"; +import {SafeCast} from "./libraries/math/SafeCast.sol"; + +import {ILBFactory} from "./interfaces/ILBFactory.sol"; +import {ILBPair} from "./interfaces/ILBPair.sol"; /// @title Liquidity Book Factory /// @author Trader Joe /// @notice Contract used to deploy and register new LBPairs. /// Enables setting fee parameters, flashloan fees and LBPair implementation. -/// Unless the `creationUnlocked` is `true`, only the owner of the factory can create pairs. +/// Unless the `_creationUnlocked` is `true`, only the owner of the factory can create pairs. contract LBFactory is PendingOwnable, ILBFactory { using SafeCast for uint256; using Encoded for bytes32; - using EnumerableSet for EnumerableSet.AddressSet; using PairParameterHelper for bytes32; + using EnumerableSet for EnumerableSet.AddressSet; - uint256 public constant override MAX_FEE = 0.1e18; // 10% - - uint256 public constant override MIN_BIN_STEP = 1; // 0.01% - uint256 public constant override MAX_BIN_STEP = 100; // 1%, can't be greater than 247 for indexing reasons - - uint256 public constant override MAX_PROTOCOL_SHARE = 2_500; // 25% - - address public override LBPairImplementation; - - address public override feeRecipient; + uint256 private constant _MIN_BIN_STEP = 1; // 0.01% + uint256 private constant _MAX_BIN_STEP = 200; // 1%, can't be greater than 247 for indexing reasons /// @notice Whether the createLBPair function is unlocked and can be called by anyone (true) or only by owner (false) - bool public override creationUnlocked; + bool private _creationUnlocked; + + uint256 private constant _MAX_FEE = 0.1e18; // 10% + uint256 private constant _MAX_PROTOCOL_SHARE = 2_500; // 25% + address private _feeRecipient; + uint256 private _flashLoanFee; - uint256 public override flashLoanFee; + address private _lbPairImplementation; - ILBPair[] public override allLBPairs; + ILBPair[] private _allLBPairs; /// @dev Mapping from a (tokenA, tokenB, binStep) to a LBPair. The tokens are ordered to save gas, but they can be /// in the reverse order in the actual pair. Always query one of the 2 tokens of the pair to assert the order of the 2 tokens - mapping(IERC20 => mapping(IERC20 => mapping(uint256 => LBPairInformation[]))) private _LBPairsInfos; + mapping(IERC20 => mapping(IERC20 => mapping(uint256 => LBPairInformation[]))) private _lbPairsInfos; /// @dev Whether a preset was set or not, if the bit at `index` is 1, it means that the binStep `index` was set /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas @@ -62,21 +62,58 @@ contract LBFactory is PendingOwnable, ILBFactory { uint256 private constant _REVISION_START_INDEX = 1; /// @notice Constructor - /// @param _feeRecipient The address of the fee recipient - /// @param _flashLoanFee The value of the fee for flash loan - constructor(address _feeRecipient, uint256 _flashLoanFee) { - if (_flashLoanFee > MAX_FEE) revert LBFactory__FlashLoanFeeAboveMax(_flashLoanFee, MAX_FEE); + /// @param feeRecipient The address of the fee recipient + /// @param flashLoanFee The value of the fee for flash loan + constructor(address feeRecipient, uint256 flashLoanFee) { + if (flashLoanFee > _MAX_FEE) revert LBFactory__FlashLoanFeeAboveMax(flashLoanFee, _MAX_FEE); + + _setFeeRecipient(feeRecipient); + + _flashLoanFee = flashLoanFee; + emit FlashLoanFeeSet(0, flashLoanFee); + } + + // TODO: Natspecs + function isCreationUnlocked() external view returns (bool unlocked) { + return _creationUnlocked; + } + + function getMinBinStep() external pure returns (uint256 minBinStep) { + return _MIN_BIN_STEP; + } + + function getMaxBinStep() external pure returns (uint256 maxBinStep) { + return _MAX_BIN_STEP; + } + + function getFeeRecipient() external view returns (address feeRecipient) { + return _feeRecipient; + } - _setFeeRecipient(_feeRecipient); + function getMaxFee() external pure returns (uint256 maxFee) { + return _MAX_FEE; + } + + function getMaxProtocolShare() external pure returns (uint256 maxProtocolShare) { + return _MAX_PROTOCOL_SHARE; + } + + function getFlashloanFee() external view returns (uint256 flashloanFee) { + return _flashLoanFee; + } - flashLoanFee = _flashLoanFee; - emit FlashLoanFeeSet(0, _flashLoanFee); + function getLBPairImplementation() external view returns (address LBPairImplementation) { + return _lbPairImplementation; } /// @notice View function to return the number of LBPairs created /// @return The number of LBPair function getNumberOfLBPairs() external view override returns (uint256) { - return allLBPairs.length; + return _allLBPairs.length; + } + + function getLBPairAtIndex(uint256 index) external view returns (ILBPair pair) { + return _allLBPairs[index]; } /// @notice View function to return the number of quote assets whitelisted @@ -86,51 +123,51 @@ contract LBFactory is PendingOwnable, ILBFactory { } /// @notice View function to return the quote asset whitelisted at index `index` - /// @param _index The index - /// @return The address of the _quoteAsset at index `index` - function getQuoteAsset(uint256 _index) external view override returns (IERC20) { - return IERC20(_quoteAssetWhitelist.at(_index)); + /// @param index The index + /// @return The address of the quoteAsset at index `index` + function getQuoteAsset(uint256 index) external view override returns (IERC20) { + return IERC20(_quoteAssetWhitelist.at(index)); } /// @notice View function to return whether a token is a quotedAsset (true) or not (false) - /// @param _token The address of the asset + /// @param token The address of the asset /// @return Whether the token is a quote asset or not - function isQuoteAsset(IERC20 _token) external view override returns (bool) { - return _quoteAssetWhitelist.contains(address(_token)); + function isQuoteAsset(IERC20 token) external view override returns (bool) { + return _quoteAssetWhitelist.contains(address(token)); } /// @notice View function to return the number of revisions of a LBPair - /// @param _tokenA The address of the first token of the pair. The order doesn't matter - /// @param _tokenB The address of the second token of the pair - /// @param _binStep The bin step of the LBPair + /// @param tokenA The address of the first token of the pair. The order doesn't matter + /// @param tokenB The address of the second token of the pair + /// @param binStep The bin step of the LBPair /// @return The number of revisions - function getNumberOfRevisions(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep) + function getNumberOfRevisions(IERC20 tokenA, IERC20 tokenB, uint256 binStep) external view override returns (uint256) { - (_tokenA, _tokenB) = _sortTokens(_tokenA, _tokenB); + (tokenA, tokenB) = _sortTokens(tokenA, tokenB); - return _LBPairsInfos[_tokenA][_tokenB][_binStep].length; + return _lbPairsInfos[tokenA][tokenB][binStep].length; } /// @notice Returns the LBPairInformation if it exists, /// if not, then the address 0 is returned. The order doesn't matter - /// @param _tokenA The address of the first token of the pair - /// @param _tokenB The address of the second token of the pair - /// @param _binStep The bin step of the LBPair + /// @param tokenA The address of the first token of the pair + /// @param tokenB The address of the second token of the pair + /// @param binStep The bin step of the LBPair /// @return The LBPairInformation - function getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep, uint256 _revision) + function getLBPairInformation(IERC20 tokenA, IERC20 tokenB, uint256 binStep, uint256 revision) external view returns (LBPairInformation memory) { - return _getLBPairInformation(_tokenA, _tokenB, _binStep, _revision); + return _getLBPairInformation(tokenA, tokenB, binStep, revision); } /// @notice View function to return the different parameters of the preset - /// @param _binStep The bin step of the preset + /// @param binStep The bin step of the preset /// @return baseFactor The base factor /// @return filterPeriod The filter period of the preset /// @return decayPeriod The decay period of the preset @@ -138,8 +175,7 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @return variableFeeControl The variable fee control of the preset /// @return protocolShare The protocol share of the preset /// @return maxVolatilityAccumulated The max volatility accumulated of the preset - /// @return sampleLifetime The sample lifetime of the preset - function getPreset(uint16 _binStep) + function getPreset(uint256 binStep) external view override @@ -150,44 +186,36 @@ contract LBFactory is PendingOwnable, ILBFactory { uint256 reductionFactor, uint256 variableFeeControl, uint256 protocolShare, - uint256 maxVolatilityAccumulated, - uint256 sampleLifetime + uint256 maxVolatilityAccumulated ) { - bytes32 _preset = _presets[_binStep]; - if (_preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); - - uint256 _shift; - - // Safety check - require(_binStep == _preset.decode(type(uint16).max, _shift)); - - baseFactor = _preset.decode(type(uint16).max, _shift += 16); - filterPeriod = _preset.decode(type(uint16).max, _shift += 16); - decayPeriod = _preset.decode(type(uint16).max, _shift += 16); - reductionFactor = _preset.decode(type(uint16).max, _shift += 16); - variableFeeControl = _preset.decode(type(uint24).max, _shift += 16); - protocolShare = _preset.decode(type(uint16).max, _shift += 24); - maxVolatilityAccumulated = _preset.decode(type(uint24).max, _shift += 16); - - sampleLifetime = _preset.decode(type(uint16).max, 240); + bytes32 preset = _presets[binStep]; + if (preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(binStep); + + baseFactor = preset.getBaseFactor(); + filterPeriod = preset.getFilterPeriod(); + decayPeriod = preset.getDecayPeriod(); + reductionFactor = preset.getReductionFactor(); + variableFeeControl = preset.getVariableFeeControl(); + protocolShare = preset.getProtocolShare(); + maxVolatilityAccumulated = preset.getMaxVolatilityAccumulated(); } /// @notice View function to return the list of available binStep with a preset /// @return presetsBinStep The list of binStep function getAllBinSteps() external view override returns (uint256[] memory presetsBinStep) { unchecked { - bytes32 _avPresets = _availablePresets; - uint256 _nbPresets = _avPresets.decode(type(uint8).max, 248); + bytes32 avPresets = _availablePresets; + uint256 nbPresets = avPresets.decode(type(uint8).max, 248); - if (_nbPresets > 0) { - presetsBinStep = new uint256[](_nbPresets); + if (nbPresets > 0) { + presetsBinStep = new uint256[](nbPresets); - uint256 _index; - for (uint256 i = MIN_BIN_STEP; i <= MAX_BIN_STEP; ++i) { - if (_avPresets.decode(1, i) == 1) { - presetsBinStep[_index] = i; - if (++_index == _nbPresets) break; + uint256 index; + for (uint256 i = _MIN_BIN_STEP; i <= _MAX_BIN_STEP; ++i) { + if (avPresets.decode(1, i) == 1) { + presetsBinStep[index] = i; + if (++index == nbPresets) break; } } } @@ -195,55 +223,55 @@ contract LBFactory is PendingOwnable, ILBFactory { } /// @notice View function to return all the LBPair of a pair of tokens - /// @param _tokenX The first token of the pair - /// @param _tokenY The second token of the pair - /// @return LBPairsAvailable The list of available LBPairs - function getAllLBPairs(IERC20 _tokenX, IERC20 _tokenY) + /// @param tokenX The first token of the pair + /// @param tokenY The second token of the pair + /// @return lbPairsAvailable The list of available LBPairs + function getAllLBPairs(IERC20 tokenX, IERC20 tokenY) external view override - returns (LBPairInformation[] memory LBPairsAvailable) + returns (LBPairInformation[] memory lbPairsAvailable) { unchecked { - (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); + (IERC20 tokenA, IERC20 tokenB) = _sortTokens(tokenX, tokenY); - bytes32 _avLBPairBinSteps = _availableLBPairBinSteps[_tokenA][_tokenB]; - uint256 _nbExistingBinSteps = _avLBPairBinSteps.decode(type(uint8).max, 248); + bytes32 avLBPairBinSteps = _availableLBPairBinSteps[tokenA][tokenB]; + uint256 nbExistingBinSteps = avLBPairBinSteps.decode(type(uint8).max, 248); uint256 totalPairs; - if (_nbExistingBinSteps > 0) { - uint256 _index; + if (nbExistingBinSteps > 0) { + uint256 index; // Loops a first time to know how many pairs are available - for (uint256 i = MIN_BIN_STEP; i <= MAX_BIN_STEP; ++i) { - if (_avLBPairBinSteps.decode(1, i) == 1) { - totalPairs += _LBPairsInfos[_tokenA][_tokenB][i].length; + for (uint256 i = _MIN_BIN_STEP; i <= _MAX_BIN_STEP; ++i) { + if (avLBPairBinSteps.decode(1, i) == 1) { + totalPairs += _lbPairsInfos[tokenA][tokenB][i].length; - if (++_index == _nbExistingBinSteps) break; + if (++index == nbExistingBinSteps) break; } } - LBPairsAvailable = new LBPairInformation[](totalPairs); + lbPairsAvailable = new LBPairInformation[](totalPairs); - _index = 0; + index = 0; // Loops a second time to fill the array - for (uint256 i = MIN_BIN_STEP; i <= MAX_BIN_STEP; ++i) { - if (_avLBPairBinSteps.decode(1, i) == 1) { - uint256 revisionNumber = _LBPairsInfos[_tokenA][_tokenB][i].length; + for (uint256 i = _MIN_BIN_STEP; i <= _MAX_BIN_STEP; ++i) { + if (avLBPairBinSteps.decode(1, i) == 1) { + uint256 revisionNumber = _lbPairsInfos[tokenA][tokenB][i].length; for (uint256 j = 0; j < revisionNumber; ++j) { - LBPairInformation memory _LBPairInformation = _LBPairsInfos[_tokenA][_tokenB][i][j]; - - LBPairsAvailable[_index++] = LBPairInformation({ - binStep: i.safe16(), - LBPair: _LBPairInformation.LBPair, - createdByOwner: _LBPairInformation.createdByOwner, - ignoredForRouting: _LBPairInformation.ignoredForRouting, - revisionIndex: _LBPairInformation.revisionIndex, - implementation: _LBPairInformation.implementation + LBPairInformation memory lbPairInformation = _lbPairsInfos[tokenA][tokenB][i][j]; + + lbPairsAvailable[index++] = LBPairInformation({ + binStep: i.safe8(), + LBPair: lbPairInformation.LBPair, + createdByOwner: lbPairInformation.createdByOwner, + ignoredForRouting: lbPairInformation.ignoredForRouting, + revisionIndex: lbPairInformation.revisionIndex, + implementation: lbPairInformation.implementation }); } - if (_index == totalPairs) break; + if (index == totalPairs) break; } } } @@ -252,522 +280,474 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @notice Set the LBPair implementation address /// @dev Needs to be called by the owner - /// @param _LBPairImplementation The address of the implementation - function setLBPairImplementation(address _LBPairImplementation) external override onlyOwner { - if (ILBPair(_LBPairImplementation).getFactory() != this) { - revert LBFactory__LBPairSafetyCheckFailed(_LBPairImplementation); + /// @param newLBPairImplementation The address of the implementation + function setLBPairImplementation(address newLBPairImplementation) external override onlyOwner { + if (ILBPair(newLBPairImplementation).getFactory() != this) { + revert LBFactory__LBPairSafetyCheckFailed(newLBPairImplementation); } - address _oldLBPairImplementation = LBPairImplementation; - if (_oldLBPairImplementation == _LBPairImplementation) { - revert LBFactory__SameImplementation(_LBPairImplementation); + address oldLBPairImplementation = _lbPairImplementation; + if (oldLBPairImplementation == newLBPairImplementation) { + revert LBFactory__SameImplementation(newLBPairImplementation); } - LBPairImplementation = _LBPairImplementation; + _lbPairImplementation = newLBPairImplementation; - emit LBPairImplementationSet(_oldLBPairImplementation, _LBPairImplementation); + emit LBPairImplementationSet(oldLBPairImplementation, newLBPairImplementation); } - /// @notice Create a liquidity bin LBPair for _tokenX and _tokenY - /// @param _tokenX The address of the first token - /// @param _tokenY The address of the second token - /// @param _activeId The active id of the pair - /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @return _LBPair The address of the newly created LBPair - function createLBPair(IERC20 _tokenX, IERC20 _tokenY, uint24 _activeId, uint16 _binStep) + /// @notice Create a liquidity bin LBPair for tokenX and tokenY + /// @param tokenX The address of the first token + /// @param tokenY The address of the second token + /// @param activeId The active id of the pair + /// @param binStep The bin step in basis point, used to calculate log(1 + binStep) + /// @return pair The address of the newly created LBPair + function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint8 binStep) external override - returns (ILBPair _LBPair) + returns (ILBPair pair) { + // TODO: fix stack too deep to cache owner // address _owner = owner(); - if (!creationUnlocked && msg.sender != owner()) revert LBFactory__FunctionIsLockedForUsers(msg.sender); + if (!_creationUnlocked && msg.sender != owner()) revert LBFactory__FunctionIsLockedForUsers(msg.sender); - address _LBPairImplementation = LBPairImplementation; + address implementation = _lbPairImplementation; - if (_LBPairImplementation == address(0)) revert LBFactory__ImplementationNotSet(); + if (implementation == address(0)) revert LBFactory__ImplementationNotSet(); - if (!_quoteAssetWhitelist.contains(address(_tokenY))) revert LBFactory__QuoteAssetNotWhitelisted(_tokenY); + if (!_quoteAssetWhitelist.contains(address(tokenY))) revert LBFactory__QuoteAssetNotWhitelisted(tokenY); - if (_tokenX == _tokenY) revert LBFactory__IdenticalAddresses(_tokenX); + if (tokenX == tokenY) revert LBFactory__IdenticalAddresses(tokenX); // safety check, making sure that the price can be calculated - // BinHelper.getPriceFromId(_activeId, _binStep); - TODO - check if necessary + PriceHelper.getPriceFromId(activeId, binStep); // We sort token for storage efficiency, only one input needs to be stored because they are sorted - (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); + (IERC20 tokenA, IERC20 tokenB) = _sortTokens(tokenX, tokenY); // single check is sufficient - if (address(_tokenA) == address(0)) revert LBFactory__AddressZero(); - if (_LBPairsInfos[_tokenA][_tokenB][_binStep].length != 0) { - revert LBFactory__LBPairAlreadyExists(_tokenX, _tokenY, _binStep); + if (address(tokenA) == address(0)) revert LBFactory__AddressZero(); + if (_lbPairsInfos[tokenA][tokenB][binStep].length != 0) { + revert LBFactory__LBPairAlreadyExists(tokenX, tokenY, binStep); } - // uint256 _sampleLifetime = _preset.decode(type(uint16).max, 240); // We remove the bits that are not part of the feeParameters - // _preset &= bytes32(uint256(type(uint144).max)); { - bytes32 _salt = keccak256(abi.encode(_tokenA, _tokenB, _binStep, _REVISION_START_INDEX)); - _LBPair = ILBPair( - ImmutableClone.cloneDeterministic( - _LBPairImplementation, abi.encodePacked(_tokenX, _tokenY, _binStep), _salt - ) + bytes32 salt = keccak256(abi.encode(tokenA, tokenB, binStep, _REVISION_START_INDEX)); + pair = ILBPair( + ImmutableClone.cloneDeterministic(implementation, abi.encodePacked(tokenX, tokenY, binStep), salt) ); } { - bytes32 _preset = _presets[_binStep]; - if (_preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); - - _LBPair.initialize( - _preset.getBaseFactor(), - _preset.getFilterPeriod(), - _preset.getDecayPeriod(), - _preset.getReductionFactor(), - _preset.getVariableFeeControl(), - _preset.getProtocolShare(), - _preset.getMaxVolatilityAccumulated(), - _activeId + bytes32 preset = _presets[binStep]; + + if (preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(binStep); + + pair.initialize( + preset.getBaseFactor(), + preset.getFilterPeriod(), + preset.getDecayPeriod(), + preset.getReductionFactor(), + preset.getVariableFeeControl(), + preset.getProtocolShare(), + preset.getMaxVolatilityAccumulated(), + activeId ); } - _LBPairsInfos[_tokenA][_tokenB][_binStep].push( + _lbPairsInfos[tokenA][tokenB][binStep].push( LBPairInformation({ - binStep: _binStep, - LBPair: _LBPair, + binStep: binStep, + LBPair: pair, createdByOwner: msg.sender == owner(), ignoredForRouting: false, revisionIndex: uint16(_REVISION_START_INDEX), - implementation: LBPairImplementation + implementation: implementation }) ); - allLBPairs.push(_LBPair); + _allLBPairs.push(pair); { - bytes32 _avLBPairBinSteps = _availableLBPairBinSteps[_tokenA][_tokenB]; - // We add a 1 at bit `_binStep` as this binStep is now set - _avLBPairBinSteps = bytes32(uint256(_avLBPairBinSteps) | (1 << _binStep)); + bytes32 avLBPairBinSteps = _availableLBPairBinSteps[tokenA][tokenB]; + // We add a 1 at bit `binStep` as this binStep is now set + avLBPairBinSteps = bytes32(uint256(avLBPairBinSteps) | (1 << binStep)); // Increase the number of lb pairs by 1 - _avLBPairBinSteps = bytes32(uint256(_avLBPairBinSteps) + (1 << 248)); + avLBPairBinSteps = bytes32(uint256(avLBPairBinSteps) + (1 << 248)); // Save the changes - _availableLBPairBinSteps[_tokenA][_tokenB] = _avLBPairBinSteps; + _availableLBPairBinSteps[tokenA][tokenB] = avLBPairBinSteps; } - emit LBPairCreated(_tokenX, _tokenY, _binStep, _LBPair, allLBPairs.length - 1); - - { - bytes32 _preset = _presets[_binStep]; - emit FeeParametersSet( - msg.sender, - _LBPair, - _binStep, - _preset.decode(type(uint16).max, 16), - _preset.decode(type(uint16).max, 32), - _preset.decode(type(uint16).max, 48), - _preset.decode(type(uint16).max, 64), - _preset.decode(type(uint24).max, 80), - _preset.decode(type(uint16).max, 104), - _preset.decode(type(uint24).max, 120) - ); - } + emit LBPairCreated(tokenX, tokenY, binStep, pair, _allLBPairs.length - 1); } /// @notice Function to create a new revision of a pair /// Restricted to the owner - /// @param _tokenX The first token of the pair - /// @param _tokenY The second token of the pair - /// @param _binStep The binStep of the pair - /// @return _LBPair The new LBPair - function createLBPairRevision(IERC20 _tokenX, IERC20 _tokenY, uint16 _binStep) + /// @param tokenX The first token of the pair + /// @param tokenY The second token of the pair + /// @param binStep The binStep of the pair + /// @return pair The new LBPair + function createLBPairRevision(IERC20 tokenX, IERC20 tokenY, uint8 binStep) external override onlyOwner - returns (ILBPair _LBPair) + returns (ILBPair pair) { - (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); + (IERC20 tokenA, IERC20 tokenB) = _sortTokens(tokenX, tokenY); - uint256 currentVersionNumber = _LBPairsInfos[_tokenA][_tokenB][_binStep].length; - if (currentVersionNumber == 0) revert LBFactory__LBPairDoesNotExists(_tokenX, _tokenY, _binStep); + uint256 currentVersionNumber = _lbPairsInfos[tokenA][tokenB][binStep].length; + if (currentVersionNumber == 0) revert LBFactory__LBPairDoesNotExists(tokenX, tokenY, binStep); - address _LBPairImplementation = LBPairImplementation; + address implementation = _lbPairImplementation; // Get latest version - LBPairInformation memory _LBPairInformation = - _LBPairsInfos[_tokenA][_tokenB][_binStep][currentVersionNumber - _REVISION_START_INDEX]; + LBPairInformation memory latestVersionPairInformation = + _lbPairsInfos[tokenA][tokenB][binStep][currentVersionNumber - _REVISION_START_INDEX]; - if (_LBPairInformation.implementation == _LBPairImplementation) { - revert LBFactory__SameImplementation(_LBPairImplementation); + if (latestVersionPairInformation.implementation == implementation) { + revert LBFactory__SameImplementation(implementation); } - ILBPair _oldLBPair = _LBPairInformation.LBPair; + ILBPair oldLBPair = latestVersionPairInformation.LBPair; - bytes32 _preset = _presets[_binStep]; + bytes32 preset = _presets[binStep]; - bytes32 _salt = keccak256(abi.encode(_tokenA, _tokenB, _binStep, ++currentVersionNumber)); - _LBPair = ILBPair( - ImmutableClone.cloneDeterministic( - _LBPairImplementation, abi.encodePacked(_tokenX, _tokenY, _binStep), _salt - ) - ); + bytes32 salt = keccak256(abi.encode(tokenA, tokenB, binStep, ++currentVersionNumber)); + pair = + ILBPair(ImmutableClone.cloneDeterministic(implementation, abi.encodePacked(tokenX, tokenY, binStep), salt)); { - // (uint256 oracleSampleLifetime,,,,,,) = _oldLBPair.getOracleParameters(); - - // (,, uint256 activeId) = _oldLBPair.getReservesAndId(); - - _LBPair.initialize( - _preset.getBaseFactor(), - _preset.getFilterPeriod(), - _preset.getDecayPeriod(), - _preset.getReductionFactor(), - _preset.getVariableFeeControl(), - _preset.getProtocolShare(), - _preset.getMaxVolatilityAccumulated(), - _oldLBPair.getActiveId() + pair.initialize( + preset.getBaseFactor(), + preset.getFilterPeriod(), + preset.getDecayPeriod(), + preset.getReductionFactor(), + preset.getVariableFeeControl(), + preset.getProtocolShare(), + preset.getMaxVolatilityAccumulated(), + oldLBPair.getActiveId() ); - _LBPairsInfos[_tokenA][_tokenB][_binStep].push( + _lbPairsInfos[tokenA][tokenB][binStep].push( LBPairInformation({ - binStep: _binStep, - LBPair: _LBPair, + binStep: binStep, + LBPair: pair, createdByOwner: true, ignoredForRouting: false, revisionIndex: uint16(currentVersionNumber), - implementation: LBPairImplementation + implementation: implementation }) ); } - allLBPairs.push(_LBPair); + _allLBPairs.push(pair); - emit LBPairCreated(_tokenX, _tokenY, _binStep, _LBPair, allLBPairs.length - 1); - - emit FeeParametersSet( - msg.sender, - _LBPair, - _binStep, - _preset.decode(type(uint16).max, 16), - _preset.decode(type(uint16).max, 32), - _preset.decode(type(uint16).max, 48), - _preset.decode(type(uint16).max, 64), - _preset.decode(type(uint24).max, 80), - _preset.decode(type(uint16).max, 104), - _preset.decode(type(uint24).max, 120) - ); + emit LBPairCreated(tokenX, tokenY, binStep, pair, _allLBPairs.length - 1); } /// @notice Function to set whether the pair is ignored or not for routing, it will make the pair unusable by the router - /// @param _tokenX The address of the first token of the pair - /// @param _tokenY The address of the second token of the pair - /// @param _binStep The bin step in basis point of the pair - /// @param _revision The revision of the pair - /// @param _ignored Whether to ignore (true) or not (false) the pair for routing - function setLBPairIgnored(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep, uint256 _revision, bool _ignored) + /// @param tokenX The address of the first token of the pair + /// @param tokenY The address of the second token of the pair + /// @param binStep The bin step in basis point of the pair + /// @param revision The revision of the pair + /// @param ignored Whether to ignore (true) or not (false) the pair for routing + function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, uint256 revision, bool ignored) external override onlyOwner { - (IERC20 _tokenA, IERC20 _tokenB) = _sortTokens(_tokenX, _tokenY); + (IERC20 tokenA, IERC20 tokenB) = _sortTokens(tokenX, tokenY); - uint256 revisionAmount = _LBPairsInfos[_tokenA][_tokenB][_binStep].length; - if (revisionAmount == 0 || _revision > revisionAmount) { + uint256 revisionAmount = _lbPairsInfos[tokenA][tokenB][binStep].length; + if (revisionAmount == 0 || revision > revisionAmount) { revert LBFactory__AddressZero(); } - LBPairInformation memory _LBPairInformation = - _LBPairsInfos[_tokenA][_tokenB][_binStep][_revision - _REVISION_START_INDEX]; + LBPairInformation memory pairInformation = + _lbPairsInfos[tokenA][tokenB][binStep][revision - _REVISION_START_INDEX]; - if (_LBPairInformation.ignoredForRouting == _ignored) revert LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); + if (pairInformation.ignoredForRouting == ignored) revert LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); - _LBPairsInfos[_tokenA][_tokenB][_binStep][_revision - _REVISION_START_INDEX].ignoredForRouting = _ignored; + _lbPairsInfos[tokenA][tokenB][binStep][revision - _REVISION_START_INDEX].ignoredForRouting = ignored; - emit LBPairIgnoredStateChanged(_LBPairInformation.LBPair, _ignored); + emit LBPairIgnoredStateChanged(pairInformation.LBPair, ignored); } /// @notice Sets the preset parameters of a bin step - /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep - /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam - /// @param _decayPeriod The period where the accumulator value is halved - /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator - /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it - /// @param _protocolShare The share of the fees received by the protocol - /// @param _maxVolatilityAccumulated The max value of the volatility accumulated - /// @param _sampleLifetime The lifetime of an oracle's sample + /// @param binStep The bin step in basis point, used to calculate log(1 + binStep) + /// @param baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep + /// @param filterPeriod The period where the accumulator value is untouched, prevent spam + /// @param decayPeriod The period where the accumulator value is halved + /// @param reductionFactor The reduction factor, used to calculate the reduction of the accumulator + /// @param variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it + /// @param protocolShare The share of the fees received by the protocol + /// @param maxVolatilityAccumulated The max value of the volatility accumulated function setPreset( - uint16 _binStep, - uint16 _baseFactor, - uint16 _filterPeriod, - uint16 _decayPeriod, - uint16 _reductionFactor, - uint24 _variableFeeControl, - uint16 _protocolShare, - uint24 _maxVolatilityAccumulated, - uint16 _sampleLifetime + uint8 binStep, + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated ) external override onlyOwner { - bytes32 _packedFeeParameters = _getPackedFeeParameters( - _binStep, - _baseFactor, - _filterPeriod, - _decayPeriod, - _reductionFactor, - _variableFeeControl, - _protocolShare, - _maxVolatilityAccumulated + bytes32 packedFeeParameters = _getPackedFeeParameters( + binStep, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated ); - // The last 16 bits are reserved for sampleLifetime - bytes32 _preset = - bytes32((uint256(_packedFeeParameters) & type(uint144).max) | (uint256(_sampleLifetime) << 240)); + bytes32 preset = bytes32((uint256(packedFeeParameters))); - _presets[_binStep] = _preset; + _presets[binStep] = preset; - bytes32 _avPresets = _availablePresets; - if (_avPresets.decode(1, _binStep) == 0) { - // We add a 1 at bit `_binStep` as this binStep is now set - _avPresets = bytes32(uint256(_avPresets) | (1 << _binStep)); + bytes32 avPresets = _availablePresets; + if (avPresets.decode(1, binStep) == 0) { + // We add a 1 at bit `binStep` as this binStep is now set + avPresets = bytes32(uint256(avPresets) | (1 << binStep)); // Increase the number of preset by 1 - _avPresets = bytes32(uint256(_avPresets) + (1 << 248)); + avPresets = bytes32(uint256(avPresets) + (1 << 248)); // Save the changes - _availablePresets = _avPresets; + _availablePresets = avPresets; } emit PresetSet( - _binStep, - _baseFactor, - _filterPeriod, - _decayPeriod, - _reductionFactor, - _variableFeeControl, - _protocolShare, - _maxVolatilityAccumulated, - _sampleLifetime + binStep, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated ); } /// @notice Remove the preset linked to a binStep - /// @param _binStep The bin step to remove - function removePreset(uint16 _binStep) external override onlyOwner { - if (_presets[_binStep] == bytes32(0)) revert LBFactory__BinStepHasNoPreset(_binStep); + /// @param binStep The bin step to remove + function removePreset(uint8 binStep) external override onlyOwner { + if (_presets[binStep] == bytes32(0)) revert LBFactory__BinStepHasNoPreset(binStep); - // Set the bit `_binStep` to 0 - bytes32 _avPresets = _availablePresets; + // Set the bit `binStep` to 0 + bytes32 avPresets = _availablePresets; - _avPresets &= bytes32(type(uint256).max - (1 << _binStep)); - _avPresets = bytes32(uint256(_avPresets) - (1 << 248)); + avPresets &= bytes32(type(uint256).max - (1 << binStep)); + avPresets = bytes32(uint256(avPresets) - (1 << 248)); // Save the changes - _availablePresets = _avPresets; - delete _presets[_binStep]; + _availablePresets = avPresets; + delete _presets[binStep]; - emit PresetRemoved(_binStep); + emit PresetRemoved(binStep); } /// @notice Function to set the fee parameter of a LBPair - /// @param _tokenX The address of the first token - /// @param _tokenY The address of the second token - /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @param _revision The revision of the LBPair - /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep - /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam - /// @param _decayPeriod The period where the accumulator value is halved - /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator - /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it - /// @param _protocolShare The share of the fees received by the protocol - /// @param _maxVolatilityAccumulated The max value of volatility accumulated + /// @param tokenX The address of the first token + /// @param tokenY The address of the second token + /// @param binStep The bin step in basis point, used to calculate log(1 + binStep) + /// @param revision The revision of the LBPair + /// @param baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep + /// @param filterPeriod The period where the accumulator value is untouched, prevent spam + /// @param decayPeriod The period where the accumulator value is halved + /// @param reductionFactor The reduction factor, used to calculate the reduction of the accumulator + /// @param variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it + /// @param protocolShare The share of the fees received by the protocol + /// @param maxVolatilityAccumulated The max value of volatility accumulated function setFeesParametersOnPair( - IERC20 _tokenX, - IERC20 _tokenY, - uint16 _binStep, - uint256 _revision, - uint16 _baseFactor, - uint16 _filterPeriod, - uint16 _decayPeriod, - uint16 _reductionFactor, - uint24 _variableFeeControl, - uint16 _protocolShare, - uint24 _maxVolatilityAccumulated + IERC20 tokenX, + IERC20 tokenY, + uint8 binStep, + uint16 revision, + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated ) external override onlyOwner { - ILBPair _LBPair = _getLBPairInformation(_tokenX, _tokenY, _binStep, _revision).LBPair; - - _LBPair.setStaticFeeParameters( - _baseFactor, - _filterPeriod, - _decayPeriod, - _reductionFactor, - _variableFeeControl, - _protocolShare, - _maxVolatilityAccumulated + ILBPair lbPair = _getLBPairInformation(tokenX, tokenY, binStep, revision).LBPair; + + lbPair.setStaticFeeParameters( + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated ); emit FeeParametersSet( msg.sender, - _LBPair, - _binStep, - _baseFactor, - _filterPeriod, - _decayPeriod, - _reductionFactor, - _variableFeeControl, - _protocolShare, - _maxVolatilityAccumulated + lbPair, + binStep, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated ); } /// @notice Function to set the recipient of the fees. This address needs to be able to receive ERC20s - /// @param _feeRecipient The address of the recipient - function setFeeRecipient(address _feeRecipient) external override onlyOwner { - _setFeeRecipient(_feeRecipient); + /// @param feeRecipient The address of the recipient + function setFeeRecipient(address feeRecipient) external override onlyOwner { + _setFeeRecipient(feeRecipient); } /// @notice Function to set the flash loan fee - /// @param _flashLoanFee The value of the fee for flash loan - function setFlashLoanFee(uint256 _flashLoanFee) external override onlyOwner { - uint256 _oldFlashLoanFee = flashLoanFee; + /// @param flashLoanFee The value of the fee for flash loan + function setFlashLoanFee(uint256 flashLoanFee) external override onlyOwner { + uint256 oldFlashLoanFee = _flashLoanFee; - if (_oldFlashLoanFee == _flashLoanFee) revert LBFactory__SameFlashLoanFee(_flashLoanFee); - if (_flashLoanFee > MAX_FEE) revert LBFactory__FlashLoanFeeAboveMax(_flashLoanFee, MAX_FEE); + if (oldFlashLoanFee == flashLoanFee) revert LBFactory__SameFlashLoanFee(flashLoanFee); + if (flashLoanFee > _MAX_FEE) revert LBFactory__FlashLoanFeeAboveMax(flashLoanFee, _MAX_FEE); - flashLoanFee = _flashLoanFee; - emit FlashLoanFeeSet(_oldFlashLoanFee, _flashLoanFee); + _flashLoanFee = flashLoanFee; + emit FlashLoanFeeSet(oldFlashLoanFee, flashLoanFee); } /// @notice Function to set the creation restriction of the Factory - /// @param _locked If the creation is restricted (true) or not (false) - function setFactoryLockedState(bool _locked) external override onlyOwner { - if (creationUnlocked != _locked) revert LBFactory__FactoryLockIsAlreadyInTheSameState(); - creationUnlocked = !_locked; - emit FactoryLockedStatusUpdated(_locked); + /// @param locked If the creation is restricted (true) or not (false) + function setFactoryLockedState(bool locked) external override onlyOwner { + if (_creationUnlocked != locked) revert LBFactory__FactoryLockIsAlreadyInTheSameState(); + _creationUnlocked = !locked; + emit FactoryLockedStatusUpdated(locked); } /// @notice Function to add an asset to the whitelist of quote assets - /// @param _quoteAsset The quote asset (e.g: AVAX, USDC...) - function addQuoteAsset(IERC20 _quoteAsset) external override onlyOwner { - if (!_quoteAssetWhitelist.add(address(_quoteAsset))) { - revert LBFactory__QuoteAssetAlreadyWhitelisted(_quoteAsset); + /// @param quoteAsset The quote asset (e.g: AVAX, USDC...) + function addQuoteAsset(IERC20 quoteAsset) external override onlyOwner { + if (!_quoteAssetWhitelist.add(address(quoteAsset))) { + revert LBFactory__QuoteAssetAlreadyWhitelisted(quoteAsset); } - emit QuoteAssetAdded(_quoteAsset); + emit QuoteAssetAdded(quoteAsset); } /// @notice Function to remove an asset from the whitelist of quote assets - /// @param _quoteAsset The quote asset (e.g: AVAX, USDC...) - function removeQuoteAsset(IERC20 _quoteAsset) external override onlyOwner { - if (!_quoteAssetWhitelist.remove(address(_quoteAsset))) revert LBFactory__QuoteAssetNotWhitelisted(_quoteAsset); + /// @param quoteAsset The quote asset (e.g: AVAX, USDC...) + function removeQuoteAsset(IERC20 quoteAsset) external override onlyOwner { + if (!_quoteAssetWhitelist.remove(address(quoteAsset))) revert LBFactory__QuoteAssetNotWhitelisted(quoteAsset); - emit QuoteAssetRemoved(_quoteAsset); + emit QuoteAssetRemoved(quoteAsset); } /// @notice Internal function to set the recipient of the fee - /// @param _feeRecipient The address of the recipient - function _setFeeRecipient(address _feeRecipient) internal { - if (_feeRecipient == address(0)) revert LBFactory__AddressZero(); + /// @param feeRecipient The address of the recipient + function _setFeeRecipient(address feeRecipient) internal { + if (feeRecipient == address(0)) revert LBFactory__AddressZero(); - address _oldFeeRecipient = feeRecipient; - if (_oldFeeRecipient == _feeRecipient) revert LBFactory__SameFeeRecipient(_feeRecipient); + address oldFeeRecipient = _feeRecipient; + if (oldFeeRecipient == feeRecipient) revert LBFactory__SameFeeRecipient(_feeRecipient); - feeRecipient = _feeRecipient; - emit FeeRecipientSet(_oldFeeRecipient, _feeRecipient); + _feeRecipient = feeRecipient; + emit FeeRecipientSet(oldFeeRecipient, feeRecipient); } - function forceDecay(ILBPair _LBPair) external override onlyOwner { - _LBPair.forceDecay(); + function forceDecay(ILBPair pair) external override onlyOwner { + pair.forceDecay(); } /// @notice Internal function to set the fee parameter of a LBPair - /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @param _baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep - /// @param _filterPeriod The period where the accumulator value is untouched, prevent spam - /// @param _decayPeriod The period where the accumulator value is halved - /// @param _reductionFactor The reduction factor, used to calculate the reduction of the accumulator - /// @param _variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it - /// @param _protocolShare The share of the fees received by the protocol - /// @param _maxVolatilityAccumulated The max value of volatility accumulated + /// @param binStep The bin step in basis point, used to calculate log(1 + binStep) + /// @param baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep + /// @param filterPeriod The period where the accumulator value is untouched, prevent spam + /// @param decayPeriod The period where the accumulator value is halved + /// @param reductionFactor The reduction factor, used to calculate the reduction of the accumulator + /// @param variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it + /// @param protocolShare The share of the fees received by the protocol + /// @param maxVolatilityAccumulated The max value of volatility accumulated function _getPackedFeeParameters( - uint16 _binStep, - uint16 _baseFactor, - uint16 _filterPeriod, - uint16 _decayPeriod, - uint16 _reductionFactor, - uint24 _variableFeeControl, - uint16 _protocolShare, - uint24 _maxVolatilityAccumulated - ) private pure returns (bytes32) { - if (_binStep < MIN_BIN_STEP || _binStep > MAX_BIN_STEP) { - revert LBFactory__BinStepRequirementsBreached(MIN_BIN_STEP, _binStep, MAX_BIN_STEP); + uint8 binStep, + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated + ) private pure returns (bytes32 preset) { + if (binStep < _MIN_BIN_STEP || binStep > _MAX_BIN_STEP) { + revert LBFactory__BinStepRequirementsBreached(_MIN_BIN_STEP, binStep, _MAX_BIN_STEP); } - if (_filterPeriod >= _decayPeriod) revert LBFactory__DecreasingPeriods(_filterPeriod, _decayPeriod); + if (filterPeriod >= decayPeriod) revert LBFactory__DecreasingPeriods(filterPeriod, decayPeriod); - if (_reductionFactor > Constants.BASIS_POINT_MAX) { - revert LBFactory__ReductionFactorOverflows(_reductionFactor, Constants.BASIS_POINT_MAX); + if (reductionFactor > Constants.BASIS_POINT_MAX) { + revert LBFactory__ReductionFactorOverflows(reductionFactor, Constants.BASIS_POINT_MAX); } - if (_protocolShare > MAX_PROTOCOL_SHARE) { - revert LBFactory__ProtocolShareOverflows(_protocolShare, MAX_PROTOCOL_SHARE); + if (protocolShare > _MAX_PROTOCOL_SHARE) { + revert LBFactory__ProtocolShareOverflows(protocolShare, _MAX_PROTOCOL_SHARE); } { - uint256 _baseFee = (uint256(_baseFactor) * _binStep) * 1e10; + uint256 baseFee = (uint256(baseFactor) * binStep) * 1e10; // Can't overflow as the max value is `max(uint24) * (max(uint24) * max(uint16)) ** 2 < max(uint104)` // It returns 18 decimals as: // decimals(variableFeeControl * (volatilityAccumulated * binStep)**2 / 100) = 4 + (4 + 4) * 2 - 2 = 18 - uint256 _prod = uint256(_maxVolatilityAccumulated) * _binStep; - uint256 _maxVariableFee = (_prod * _prod * _variableFeeControl) / 100; + uint256 prod = uint256(maxVolatilityAccumulated) * binStep; + uint256 maxVariableFee = (prod * prod * variableFeeControl) / 100; - if (_baseFee + _maxVariableFee > MAX_FEE) { - revert LBFactory__FeesAboveMax(_baseFee + _maxVariableFee, MAX_FEE); + if (baseFee + maxVariableFee > _MAX_FEE) { + revert LBFactory__FeesAboveMax(baseFee + maxVariableFee, _MAX_FEE); } } - /// @dev It's very important that the sum of the sizes of those values is exactly 256 bits - /// here, (112 + 24) + 16 + 24 + 16 + 16 + 16 + 16 + 16 = 256 - return bytes32( - abi.encodePacked( - uint136(_maxVolatilityAccumulated), // The first 112 bits are reserved for the dynamic parameters - _protocolShare, - _variableFeeControl, - _reductionFactor, - _decayPeriod, - _filterPeriod, - _baseFactor, - _binStep - ) + return preset.setStaticFeeParameters( + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulated ); } /// @notice Returns the LBPairInformation if it exists, /// if not, then the address 0 is returned. The order doesn't matter - /// @param _tokenA The address of the first token of the pair - /// @param _tokenB The address of the second token of the pair - /// @param _binStep The bin step of the LBPair - /// @param _revision The revision of the LBPair + /// @param tokenA The address of the first token of the pair + /// @param tokenB The address of the second token of the pair + /// @param binStep The bin step of the LBPair + /// @param revision The revision of the LBPair /// @return The LBPairInformation - function _getLBPairInformation(IERC20 _tokenA, IERC20 _tokenB, uint256 _binStep, uint256 _revision) + function _getLBPairInformation(IERC20 tokenA, IERC20 tokenB, uint256 binStep, uint256 revision) private view returns (LBPairInformation memory) { - (_tokenA, _tokenB) = _sortTokens(_tokenA, _tokenB); + (tokenA, tokenB) = _sortTokens(tokenA, tokenB); - if (_LBPairsInfos[_tokenA][_tokenB][_binStep].length == 0) { - revert LBFactory__LBPairNotCreated(_tokenA, _tokenB, _binStep); + if (_lbPairsInfos[tokenA][tokenB][binStep].length == 0) { + revert LBFactory__LBPairNotCreated(tokenA, tokenB, binStep); } - return _LBPairsInfos[_tokenA][_tokenB][_binStep][_revision - _REVISION_START_INDEX]; + return _lbPairsInfos[tokenA][tokenB][binStep][revision - _REVISION_START_INDEX]; } /// @notice Private view function to sort 2 tokens in ascending order - /// @param _tokenA The first token - /// @param _tokenB The second token + /// @param tokenA The first token + /// @param tokenB The second token /// @return The sorted first token /// @return The sorted second token - function _sortTokens(IERC20 _tokenA, IERC20 _tokenB) private pure returns (IERC20, IERC20) { - if (_tokenA > _tokenB) (_tokenA, _tokenB) = (_tokenB, _tokenA); - return (_tokenA, _tokenB); + function _sortTokens(IERC20 tokenA, IERC20 tokenB) private pure returns (IERC20, IERC20) { + if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); + return (tokenA, tokenB); } } diff --git a/src/LBPair.sol b/src/LBPair.sol index 83dea7c8..cc02d926 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -19,7 +19,6 @@ import {PairParameterHelper} from "./libraries/PairParameterHelper.sol"; import {PriceHelper} from "./libraries/PriceHelper.sol"; import {ReentrancyGuardUpgradeable} from "./libraries/ReentrancyGuardUpgradeable.sol"; import {SafeCast} from "./libraries/math/SafeCast.sol"; -import {SampleMath} from "./libraries/math/SampleMath.sol"; import {TreeMath} from "./libraries/math/TreeMath.sol"; import {Uint256x256Math} from "./libraries/math/Uint256x256Math.sol"; @@ -43,7 +42,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { } modifier onlyProtocolFeeReceiver() { - if (msg.sender != _factory.feeRecipient()) revert LBPair__OnlyProtocolFeeReceiver(); + if (msg.sender != _factory.getFeeRecipient()) revert LBPair__OnlyProtocolFeeReceiver(); _; } @@ -287,7 +286,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { * @param id The id of the bin * @return price The price corresponding to this id */ - function getPriceFromId(uint24 id) external view override returns (uint256 price) { + function getPriceFromId(uint24 id) external pure override returns (uint256 price) { price = id.getPriceFromId(_binStep()); } @@ -298,7 +297,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { * @param price The price of y per x as a 128.128-binary fixed-point number * @return id The id of the bin corresponding to this price */ - function getIdFromPrice(uint256 price) external view override returns (uint24 id) { + function getIdFromPrice(uint256 price) external pure override returns (uint24 id) { id = price.getIdFromPrice(_binStep()); } @@ -648,6 +647,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { if (binReserves == 0) _tree.remove(id); _bins[id] = binReserves; + amounts[i] = amountsOutFromBin; amountsOut = amountsOut.add(amountsOutFromBin); unchecked { @@ -785,7 +785,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { * @return The encoded fees amounts */ function _getFlashLoanFees(bytes32 amounts) private view returns (bytes32) { - uint128 fee = uint128(_factory.flashLoanFee()); + uint128 fee = uint128(_factory.getFlashloanFee()); (uint128 x, uint128 y) = amounts.decode(); unchecked { diff --git a/src/LBQuoter.sol b/src/LBQuoter.sol index b492e49e..0bf6d6b5 100644 --- a/src/LBQuoter.sol +++ b/src/LBQuoter.sol @@ -2,16 +2,20 @@ pragma solidity 0.8.10; -import "./libraries/BinHelper.sol"; -import "./libraries/JoeLibrary.sol"; -import "./libraries/math/Uint256x256Math.sol"; -import "./interfaces/IJoeFactory.sol"; -import "./interfaces/IJoePair.sol"; -import "./interfaces/ILBFactory.sol"; -import "./interfaces/ILBLegacyFactory.sol"; -import "./interfaces/ILBRouter.sol"; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; +import {Constants} from "./libraries/Constants.sol"; +import {JoeLibrary} from "./libraries/JoeLibrary.sol"; import {PriceHelper} from "./libraries/PriceHelper.sol"; +import {Uint256x256Math} from "./libraries/math/Uint256x256Math.sol"; + +import {IJoeFactory} from "./interfaces/IJoeFactory.sol"; +import {ILBFactory} from "./interfaces/ILBFactory.sol"; +import {ILBLegacyFactory} from "./interfaces/ILBLegacyFactory.sol"; +import {IJoePair} from "./interfaces/IJoePair.sol"; +import {ILBLegacyPair} from "./interfaces/ILBLegacyPair.sol"; +import {ILBPair} from "./interfaces/ILBPair.sol"; +import {ILBRouter} from "./interfaces/ILBRouter.sol"; /// @title Liquidity Book Quoter /// @author Trader Joe @@ -22,18 +26,18 @@ contract LBQuoter { error LBQuoter_InvalidLength(); /// @notice Dex V2 router address - address public immutable routerV2; + address private immutable _routerV2; /// @notice Dex V1 factory address - address public immutable factoryV1; + address private immutable _factoryV1; /// @notice Dex V2 factory address - address public immutable legacyFactoryV2; + address private immutable _legacyFactoryV2; /// @notice Dex V2.1 factory address - address public immutable factoryV2; + address private immutable _factoryV2; struct Quote { address[] route; address[] pairs; - uint8[] binSteps; + uint256[] binSteps; uint256[] revisions; uint128[] amounts; uint128[] virtualAmountsWithoutSlippage; @@ -41,49 +45,73 @@ contract LBQuoter { } /// @notice Constructor - /// @param _routerV2 Dex V2 router address - /// @param _factoryV1 Dex V1 factory address - /// @param _legacyFactoryV2 Dex V2 factory address - /// @param _factoryV2 Dex V2.1 factory address - constructor(address _factoryV1, address _legacyFactoryV2, address _factoryV2, address _routerV2) { + /// @param routerV2 Dex V2 router address + /// @param factoryV1 Dex V1 factory address + /// @param legacyFactoryV2 Dex V2 factory address + /// @param factoryV2 Dex V2.1 factory address + constructor(address factoryV1, address legacyFactoryV2, address factoryV2, address routerV2) { + _factoryV1 = factoryV1; + _legacyFactoryV2 = legacyFactoryV2; + _factoryV2 = factoryV2; + _routerV2 = routerV2; + } + + /// @notice Returns the Dex V1 factory address + /// @return factoryV1 Dex V1 factory address + function getFactoryV1() public view returns (address factoryV1) { factoryV1 = _factoryV1; + } + + /// @notice Returns the Dex V2 factory address + /// @return legacyFactoryV2 Dex V2 factory address + function getLegacyFactoryV2() public view returns (address legacyFactoryV2) { legacyFactoryV2 = _legacyFactoryV2; + } + + /// @notice Returns the Dex V2.1 factory address + /// @return factoryV2 Dex V2.1 factory address + function getFactoryV2() public view returns (address factoryV2) { factoryV2 = _factoryV2; + } + + /// @notice Returns the Dex V2 router address + /// @return routerV2 Dex V2 router address + function getRouterV2() public view returns (address routerV2) { routerV2 = _routerV2; } /// @notice Finds the best path given a list of tokens and the input amount wanted from the swap - /// @param _route List of the tokens to go through - /// @param _amountIn Swap amount in + /// @param route List of the tokens to go through + /// @param amountIn Swap amount in /// @return quote The Quote structure containing the necessary element to perform the swap - function findBestPathFromAmountIn(address[] calldata _route, uint128 _amountIn) + function findBestPathFromAmountIn(address[] calldata route, uint128 amountIn) public view returns (Quote memory quote) { - if (_route.length < 2) { + if (route.length < 2) { revert LBQuoter_InvalidLength(); } - quote.route = _route; + quote.route = route; - uint256 swapLength = _route.length - 1; + uint256 swapLength = route.length - 1; quote.pairs = new address[](swapLength); - quote.binSteps = new uint8[](swapLength); + quote.binSteps = new uint256[](swapLength); quote.revisions = new uint256[](swapLength); quote.fees = new uint128[](swapLength); - quote.amounts = new uint128[](_route.length); - quote.virtualAmountsWithoutSlippage = new uint128[](_route.length); + quote.amounts = new uint128[](route.length); + quote.virtualAmountsWithoutSlippage = new uint128[](route.length); - quote.amounts[0] = _amountIn; - quote.virtualAmountsWithoutSlippage[0] = _amountIn; + quote.amounts[0] = amountIn; + quote.virtualAmountsWithoutSlippage[0] = amountIn; for (uint256 i; i < swapLength; i++) { // Fetch swap for V1 - quote.pairs[i] = IJoeFactory(factoryV1).getPair(_route[i], _route[i + 1]); + quote.pairs[i] = IJoeFactory(_factoryV1).getPair(route[i], route[i + 1]); if (quote.pairs[i] != address(0) && quote.amounts[i] > 0) { - (uint256 reserveIn, uint256 reserveOut) = _getReserves(quote.pairs[i], _route[i], _route[i + 1]); + (uint256 reserveIn, uint256 reserveOut) = _getReserves(quote.pairs[i], route[i], route[i + 1]); if (reserveIn > 0 && reserveOut > 0) { quote.amounts[i + 1] = uint128(JoeLibrary.getAmountOut(quote.amounts[i], reserveIn, reserveOut)); @@ -99,15 +127,15 @@ contract LBQuoter { for (uint256 k = 0; k < 2; k++) { if (k == 0) { - LBPairsAvailable = ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i]), IERC20(_route[i + 1])); + LBPairsAvailable = ILBFactory(_factoryV2).getAllLBPairs(IERC20(route[i]), IERC20(route[i + 1])); } else { ILBLegacyFactory.LBPairInformation[] memory LBPairsAvailableLegacy = - ILBLegacyFactory(legacyFactoryV2).getAllLBPairs(IERC20(_route[i]), IERC20(_route[i + 1])); + ILBLegacyFactory(_legacyFactoryV2).getAllLBPairs(IERC20(route[i]), IERC20(route[i + 1])); LBPairsAvailable = new ILBFactory.LBPairInformation[](LBPairsAvailableLegacy.length); for (uint256 l = 0; l < LBPairsAvailableLegacy.length; l++) { LBPairsAvailable[l] = ILBFactory.LBPairInformation( - LBPairsAvailableLegacy[l].binStep, + uint8(LBPairsAvailableLegacy[l].binStep), ILBPair(address(LBPairsAvailableLegacy[l].LBPair)), LBPairsAvailableLegacy[l].createdByOwner, LBPairsAvailableLegacy[l].ignoredForRouting, @@ -120,10 +148,14 @@ contract LBQuoter { if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { for (uint256 j; j < LBPairsAvailable.length; j++) { if (!LBPairsAvailable[j].ignoredForRouting) { - bool swapForY = address(LBPairsAvailable[j].LBPair.getTokenY()) == _route[i + 1]; - - try ILBRouter(routerV2).getSwapOut(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) - returns (uint128 amountInLeft, uint128 swapAmountOut, uint128 fees) { + bool swapForY = address( + k == 0 + ? LBPairsAvailable[j].LBPair.getTokenY() + : ILBLegacyPair(address(LBPairsAvailable[j].LBPair)).tokenY() + ) == route[i + 1]; + + try ILBRouter(_routerV2).getSwapOut(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) + returns (uint128, uint128 swapAmountOut, uint128 fees) { if (swapAmountOut > quote.amounts[i + 1]) { quote.amounts[i + 1] = swapAmountOut; quote.pairs[i] = address(LBPairsAvailable[j].LBPair); @@ -152,35 +184,35 @@ contract LBQuoter { } /// @notice Finds the best path given a list of tokens and the output amount wanted from the swap - /// @param _route List of the tokens to go through - /// @param _amountOut Swap amount out + /// @param route List of the tokens to go through + /// @param amountOut Swap amount out /// @return quote The Quote structure containing the necessary element to perform the swap - function findBestPathFromAmountOut(address[] calldata _route, uint128 _amountOut) + function findBestPathFromAmountOut(address[] calldata route, uint128 amountOut) public view returns (Quote memory quote) { - if (_route.length < 2) { + if (route.length < 2) { revert LBQuoter_InvalidLength(); } - quote.route = _route; + quote.route = route; - uint256 swapLength = _route.length - 1; + uint256 swapLength = route.length - 1; quote.pairs = new address[](swapLength); - quote.binSteps = new uint8[](swapLength); + quote.binSteps = new uint256[](swapLength); quote.revisions = new uint256[](swapLength); quote.fees = new uint128[](swapLength); - quote.amounts = new uint128[](_route.length); - quote.virtualAmountsWithoutSlippage = new uint128[](_route.length); + quote.amounts = new uint128[](route.length); + quote.virtualAmountsWithoutSlippage = new uint128[](route.length); - quote.amounts[swapLength] = _amountOut; - quote.virtualAmountsWithoutSlippage[swapLength] = _amountOut; + quote.amounts[swapLength] = amountOut; + quote.virtualAmountsWithoutSlippage[swapLength] = amountOut; for (uint256 i = swapLength; i > 0; i--) { // Fetch swap for V1 - quote.pairs[i - 1] = IJoeFactory(factoryV1).getPair(_route[i - 1], _route[i]); + quote.pairs[i - 1] = IJoeFactory(_factoryV1).getPair(route[i - 1], route[i]); if (quote.pairs[i - 1] != address(0) && quote.amounts[i] > 0) { - (uint256 reserveIn, uint256 reserveOut) = _getReserves(quote.pairs[i - 1], _route[i - 1], _route[i]); + (uint256 reserveIn, uint256 reserveOut) = _getReserves(quote.pairs[i - 1], route[i - 1], route[i]); if (reserveIn > 0 && reserveOut > quote.amounts[i]) { quote.amounts[i - 1] = uint128(JoeLibrary.getAmountIn(quote.amounts[i], reserveIn, reserveOut)); @@ -197,17 +229,21 @@ contract LBQuoter { for (uint256 k = 0; k < 2; k++) { if (k == 0) { - LBPairsAvailable = ILBFactory(factoryV2).getAllLBPairs(IERC20(_route[i - 1]), IERC20(_route[i])); + LBPairsAvailable = ILBFactory(_factoryV2).getAllLBPairs(IERC20(route[i - 1]), IERC20(route[i])); } else { LBPairsAvailable = - ILBFactory(legacyFactoryV2).getAllLBPairs(IERC20(_route[i - 1]), IERC20(_route[i])); + ILBFactory(_legacyFactoryV2).getAllLBPairs(IERC20(route[i - 1]), IERC20(route[i])); } if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { for (uint256 j; j < LBPairsAvailable.length; j++) { if (!LBPairsAvailable[j].ignoredForRouting) { - bool swapForY = address(LBPairsAvailable[j].LBPair.getTokenY()) == _route[i]; - try ILBRouter(routerV2).getSwapIn(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) + bool swapForY = address( + k == 0 + ? LBPairsAvailable[j].LBPair.getTokenY() + : ILBLegacyPair(address(LBPairsAvailable[j].LBPair)).tokenY() + ) == route[i]; + try ILBRouter(_routerV2).getSwapIn(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) returns (uint128 swapAmountIn, uint128, uint128 fees) { if ( swapAmountIn != 0 @@ -241,36 +277,38 @@ contract LBQuoter { /// @dev Forked from JoeLibrary /// @dev Doesn't rely on the init code hash of the factory - /// @param _pair Address of the pair - /// @param _tokenA Address of token A - /// @param _tokenB Address of token B + /// @param pair Address of the pair + /// @param tokenA Address of token A + /// @param tokenB Address of token B /// @return reserveA Reserve of token A in the pair /// @return reserveB Reserve of token B in the pair - function _getReserves(address _pair, address _tokenA, address _tokenB) + function _getReserves(address pair, address tokenA, address tokenB) internal view returns (uint256 reserveA, uint256 reserveB) { - (address token0,) = JoeLibrary.sortTokens(_tokenA, _tokenB); - (uint256 reserve0, uint256 reserve1,) = IJoePair(_pair).getReserves(); - (reserveA, reserveB) = _tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); + (address token0,) = JoeLibrary.sortTokens(tokenA, tokenB); + (uint256 reserve0, uint256 reserve1,) = IJoePair(pair).getReserves(); + (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); } /// @dev Calculates a quote for a V2 pair - /// @param _amount Amount in to consider - /// @param _activeId Current active Id of the considred pair - /// @param _binStep Bin step of the considered pair - /// @param _swapForY Boolean describing if we are swapping from X to Y or the opposite + /// @param amount Amount in to consider + /// @param activeId Current active Id of the considred pair + /// @param binStep Bin step of the considered pair + /// @param swapForY Boolean describing if we are swapping from X to Y or the opposite /// @return quote Amount Out if _amount was swapped with no slippage and no fees - function _getV2Quote(uint256 _amount, uint24 _activeId, uint8 _binStep, bool _swapForY) + function _getV2Quote(uint256 amount, uint24 activeId, uint256 binStep, bool swapForY) internal pure returns (uint256 quote) { - if (_swapForY) { - quote = PriceHelper.getPriceFromId(_activeId, _binStep).mulShiftRoundDown(_amount, Constants.SCALE_OFFSET); + if (swapForY) { + quote = + PriceHelper.getPriceFromId(activeId, uint8(binStep)).mulShiftRoundDown(amount, Constants.SCALE_OFFSET); } else { - quote = _amount.shiftDivRoundDown(Constants.SCALE_OFFSET, PriceHelper.getPriceFromId(_activeId, _binStep)); + quote = + amount.shiftDivRoundDown(Constants.SCALE_OFFSET, PriceHelper.getPriceFromId(activeId, uint8(binStep))); } } } diff --git a/src/LBRouter.sol b/src/LBRouter.sol index 6ccb175b..3e961908 100644 --- a/src/LBRouter.sol +++ b/src/LBRouter.sol @@ -2,21 +2,26 @@ pragma solidity 0.8.10; -import "openzeppelin/token/ERC20/IERC20.sol"; - -import "./libraries/BinHelper.sol"; -import "./libraries/Constants.sol"; -import "./libraries/FeeHelper.sol"; -import "./libraries/JoeLibrary.sol"; -import "./libraries/math/Uint256x256Math.sol"; -import "./libraries/math/PackedUint128Math.sol"; -import "./libraries/TokenHelper.sol"; -import "./interfaces/IJoePair.sol"; -import "./interfaces/ILBToken.sol"; -import "./interfaces/ILBRouter.sol"; -import "./interfaces/ILBLegacyFactory.sol"; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; +import {BinHelper} from "./libraries/BinHelper.sol"; +import {Constants} from "./libraries/Constants.sol"; +import {Encoded} from "./libraries/math/Encoded.sol"; +import {FeeHelper} from "./libraries/FeeHelper.sol"; +import {JoeLibrary} from "./libraries/JoeLibrary.sol"; import {LiquidityConfigurations} from "./libraries/math/LiquidityConfigurations.sol"; +import {PackedUint128Math} from "./libraries/math/PackedUint128Math.sol"; +import {TokenHelper} from "./libraries/TokenHelper.sol"; +import {Uint256x256Math} from "./libraries/math/Uint256x256Math.sol"; + +import {IJoePair} from "./interfaces/IJoePair.sol"; +import {ILBPair} from "./interfaces/ILBPair.sol"; +import {ILBToken} from "./interfaces/ILBToken.sol"; +import {ILBRouter} from "./interfaces/ILBRouter.sol"; +import {IJoeFactory} from "./interfaces/IJoeFactory.sol"; +import {ILBLegacyFactory} from "./interfaces/ILBLegacyFactory.sol"; +import {ILBFactory} from "./interfaces/ILBFactory.sol"; +import {IWAVAX} from "./interfaces/IWAVAX.sol"; /// @title Liquidity Book Router /// @author Trader Joe @@ -24,788 +29,811 @@ import {LiquidityConfigurations} from "./libraries/math/LiquidityConfigurations. contract LBRouter is ILBRouter { using TokenHelper for IERC20; using TokenHelper for IWAVAX; - using Uint256x256Math for uint256; - using LiquidityConfigurations for bytes32; - // using SwapHelper for ILBPair.Bin; + // using Uint256x256Math for uint256; + using Encoded for bytes32; using JoeLibrary for uint256; using PackedUint128Math for bytes32; - ILBFactory public immutable override factory; - ILBLegacyFactory public immutable legacyFactory; - IJoeFactory public immutable override oldFactory; - IWAVAX public immutable override wavax; + ILBFactory private immutable _factory; + ILBLegacyFactory private immutable _legacyFactory; + IJoeFactory private immutable _oldFactory; + IWAVAX private immutable _wavax; modifier onlyFactoryOwner() { - if (msg.sender != factory.owner()) revert LBRouter__NotFactoryOwner(); + if (msg.sender != _factory.owner()) revert LBRouter__NotFactoryOwner(); _; } - modifier ensure(uint256 _deadline) { - if (block.timestamp > _deadline) revert LBRouter__DeadlineExceeded(_deadline, block.timestamp); + modifier ensure(uint256 deadline) { + if (block.timestamp > deadline) revert LBRouter__DeadlineExceeded(deadline, block.timestamp); _; } - modifier verifyPathValidity(Path memory _path) { + modifier verifyPathValidity(Path memory path) { if ( - _path.pairBinSteps.length == 0 || _path.revisions.length != _path.pairBinSteps.length - || _path.pairBinSteps.length + 1 != _path.tokenPath.length + path.pairBinSteps.length == 0 || path.revisions.length != path.pairBinSteps.length + || path.pairBinSteps.length + 1 != path.tokenPath.length ) revert LBRouter__LengthsMismatch(); _; } /// @notice Constructor - /// @param _factory LBFactory address - /// @param _oldFactory Address of old factory (Joe V1) - /// @param _wavax Address of WAVAX - constructor(ILBFactory _factory, ILBLegacyFactory _legacyFactory, IJoeFactory _oldFactory, IWAVAX _wavax) { - factory = _factory; - legacyFactory = _legacyFactory; - oldFactory = _oldFactory; - wavax = _wavax; + /// @param factory_ LBFactory address + /// @param legacyFactory_ V2 LBFactory address + /// @param oldFactory_ Address of old factory (Joe V1) + /// @param wavax_ Address of WAVAX + constructor(ILBFactory factory_, ILBLegacyFactory legacyFactory_, IJoeFactory oldFactory_, IWAVAX wavax_) { + _factory = factory_; + _legacyFactory = legacyFactory_; + _oldFactory = oldFactory_; + _wavax = wavax_; } /// @dev Receive function that only accept AVAX from the WAVAX contract receive() external payable { - if (msg.sender != address(wavax)) revert LBRouter__SenderIsNotWAVAX(); + if (msg.sender != address(_wavax)) revert LBRouter__SenderIsNotWAVAX(); + } + + function getFactory() external view override returns (ILBFactory) { + return _factory; + } + + function getLegacyFactory() external view override returns (ILBLegacyFactory) { + return _legacyFactory; + } + + function getOldFactory() external view override returns (IJoeFactory) { + return _oldFactory; + } + + function getWAVAX() external view override returns (IWAVAX) { + return _wavax; } /// @notice Returns the approximate id corresponding to the inputted price. /// Warning, the returned id may be inaccurate close to the start price of a bin - /// @param _LBPair The address of the LBPair - /// @param _price The price of y per x (multiplied by 1e36) + /// @param pair The address of the LBPair + /// @param price The price of y per x (multiplied by 1e36) /// @return The id corresponding to this price - function getIdFromPrice(ILBPair _LBPair, uint256 _price) external view override returns (uint24) { - return _LBPair.getIdFromPrice(_price); + function getIdFromPrice(ILBPair pair, uint256 price) external view override returns (uint24) { + return pair.getIdFromPrice(price); } /// @notice Returns the price corresponding to the inputted id - /// @param _LBPair The address of the LBPair - /// @param _id The id + /// @param pair The address of the LBPair + /// @param id The id /// @return The price corresponding to this id - function getPriceFromId(ILBPair _LBPair, uint24 _id) external view override returns (uint256) { - return _LBPair.getPriceFromId(_id); + function getPriceFromId(ILBPair pair, uint24 id) external view override returns (uint256) { + return pair.getPriceFromId(id); } /// @notice Simulate a swap in - /// @param _LBPair The address of the LBPair - /// @param _amountOut The amount of token to receive - /// @param _swapForY Whether you swap X for Y (true), or Y for X (false) - /// @return amountIn The amount of token to send in order to receive _amountOut token + /// @param pair The address of the LBPair + /// @param amountOut The amount of token to receive + /// @param swapForY Whether you swap X for Y (true), or Y for X (false) + /// @return amountIn The amount of token to send in order to receive amountOut token /// @return amountOutLeft The amount of token Out that can't be returned due to a lack of liquidity /// @return fee The amount of fees paid in token sent - function getSwapIn(ILBPair _LBPair, uint128 _amountOut, bool _swapForY) + function getSwapIn(ILBPair pair, uint128 amountOut, bool swapForY) public view override returns (uint128 amountIn, uint128 amountOutLeft, uint128 fee) { - (amountIn, amountOutLeft, fee) = _LBPair.getSwapIn(_amountOut, _swapForY); + (amountIn, amountOutLeft, fee) = pair.getSwapIn(amountOut, swapForY); } /// @notice Simulate a swap out - /// @param _LBPair The address of the LBPair - /// @param _amountIn The amount of token sent - /// @param _swapForY Whether you swap X for Y (true), or Y for X (false) + /// @param pair The address of the LBPair + /// @param amountIn The amount of token sent + /// @param swapForY Whether you swap X for Y (true), or Y for X (false) /// @return amountInLeft The amount of token In that can't be swapped due to a lack of liquidity - /// @return amountOut The amount of token received if _amountIn tokenX are sent + /// @return amountOut The amount of token received if amountIn tokenX are sent /// @return fee The amount of fees paid in token sent - function getSwapOut(ILBPair _LBPair, uint128 _amountIn, bool _swapForY) + function getSwapOut(ILBPair pair, uint128 amountIn, bool swapForY) external view override returns (uint128 amountInLeft, uint128 amountOut, uint128 fee) { - (amountInLeft, amountOut, fee) = _LBPair.getSwapOut(_amountIn, _swapForY); + (amountInLeft, amountOut, fee) = pair.getSwapOut(amountIn, swapForY); } - /// @notice Create a liquidity bin LBPair for _tokenX and _tokenY using the factory - /// @param _tokenX The address of the first token - /// @param _tokenY The address of the second token - /// @param _activeId The active id of the pair - /// @param _binStep The bin step in basis point, used to calculate log(1 + binStep) + /// @notice Create a liquidity bin LBPair for tokenX and tokenY using the factory + /// @param tokenX The address of the first token + /// @param tokenY The address of the second token + /// @param activeId The active id of the pair + /// @param binStep The bin step in basis point, used to calculate log(1 + binStep) /// @return pair The address of the newly created LBPair - function createLBPair(IERC20 _tokenX, IERC20 _tokenY, uint24 _activeId, uint16 _binStep) + function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint8 binStep) external override returns (ILBPair pair) { - pair = factory.createLBPair(_tokenX, _tokenY, _activeId, _binStep); + pair = _factory.createLBPair(tokenX, tokenY, activeId, binStep); } /// @notice Add liquidity while performing safety checks /// @dev This function is compliant with fee on transfer tokens - /// @param _liquidityParameters The liquidity parameters + /// @param liquidityParameters The liquidity parameters /// @return depositIds Bin ids where the liquidity was actually deposited /// @return liquidityMinted Amounts of LBToken minted for each bin - function addLiquidity(LiquidityParameters calldata _liquidityParameters) + function addLiquidity(LiquidityParameters calldata liquidityParameters) external override returns (bytes32[] memory depositIds, uint256[] memory liquidityMinted) { - ILBPair _LBPair = _getLBPairInformation( - _liquidityParameters.tokenX, - _liquidityParameters.tokenY, - _liquidityParameters.binStep, - _liquidityParameters.revision + ILBPair lbPair = _getLBPairInformation( + liquidityParameters.tokenX, + liquidityParameters.tokenY, + liquidityParameters.binStep, + liquidityParameters.revision ); - if (_liquidityParameters.tokenX != _LBPair.getTokenX()) revert LBRouter__WrongTokenOrder(); + if (liquidityParameters.tokenX != lbPair.getTokenX()) revert LBRouter__WrongTokenOrder(); - _liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountX); - _liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountY); + liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(lbPair), liquidityParameters.amountX); + liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(lbPair), liquidityParameters.amountY); - (depositIds, liquidityMinted) = _addLiquidity(_liquidityParameters, _LBPair); + (depositIds, liquidityMinted) = _addLiquidity(liquidityParameters, lbPair); } /// @notice Add liquidity with AVAX while performing safety checks /// @dev This function is compliant with fee on transfer tokens - /// @param _liquidityParameters The liquidity parameters + /// @param liquidityParameters The liquidity parameters /// @return depositIds Bin ids where the liquidity was actually deposited /// @return liquidityMinted Amounts of LBToken minted for each bin - function addLiquidityAVAX(LiquidityParameters calldata _liquidityParameters) + function addLiquidityAVAX(LiquidityParameters calldata liquidityParameters) external payable override returns (bytes32[] memory depositIds, uint256[] memory liquidityMinted) { - ILBPair _LBPair = _getLBPairInformation( - _liquidityParameters.tokenX, - _liquidityParameters.tokenY, - _liquidityParameters.binStep, - _liquidityParameters.revision + ILBPair lbPair = _getLBPairInformation( + liquidityParameters.tokenX, + liquidityParameters.tokenY, + liquidityParameters.binStep, + liquidityParameters.revision ); - if (_liquidityParameters.tokenX != _LBPair.getTokenX()) revert LBRouter__WrongTokenOrder(); - - if (_liquidityParameters.tokenX == wavax && _liquidityParameters.amountX == msg.value) { - _wavaxDepositAndTransfer(address(_LBPair), msg.value); - _liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountY); - } else if (_liquidityParameters.tokenY == wavax && _liquidityParameters.amountY == msg.value) { - _liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(_LBPair), _liquidityParameters.amountX); - _wavaxDepositAndTransfer(address(_LBPair), msg.value); + if (liquidityParameters.tokenX != lbPair.getTokenX()) revert LBRouter__WrongTokenOrder(); + + if (liquidityParameters.tokenX == _wavax && liquidityParameters.amountX == msg.value) { + _wavaxDepositAndTransfer(address(lbPair), msg.value); + liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(lbPair), liquidityParameters.amountY); + } else if (liquidityParameters.tokenY == _wavax && liquidityParameters.amountY == msg.value) { + liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(lbPair), liquidityParameters.amountX); + _wavaxDepositAndTransfer(address(lbPair), msg.value); } else { revert LBRouter__WrongAvaxLiquidityParameters( - address(_liquidityParameters.tokenX), - address(_liquidityParameters.tokenY), - _liquidityParameters.amountX, - _liquidityParameters.amountY, + address(liquidityParameters.tokenX), + address(liquidityParameters.tokenY), + liquidityParameters.amountX, + liquidityParameters.amountY, msg.value ); } - (depositIds, liquidityMinted) = _addLiquidity(_liquidityParameters, _LBPair); + (depositIds, liquidityMinted) = _addLiquidity(liquidityParameters, lbPair); } /// @notice Remove liquidity while performing safety checks /// @dev This function is compliant with fee on transfer tokens - /// @param _tokenX The address of token X - /// @param _tokenY The address of token Y - /// @param _binStep The bin step of the LBPair - /// @param _amountXMin The min amount to receive of token X - /// @param _amountYMin The min amount to receive of token Y - /// @param _ids The list of ids to burn - /// @param _amounts The list of amounts to burn of each id in `_ids` - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx + /// @param tokenX The address of token X + /// @param tokenY The address of token Y + /// @param binStep The bin step of the LBPair + /// @param amountXMin The min amount to receive of token X + /// @param amountYMin The min amount to receive of token Y + /// @param ids The list of ids to burn + /// @param amounts The list of amounts to burn of each id in `ids` + /// @param to The address of the recipient + /// @param deadline The deadline of the tx /// @return amountX Amount of token X returned /// @return amountY Amount of token Y returned function removeLiquidity( - IERC20 _tokenX, - IERC20 _tokenY, - uint16 _binStep, - uint256 _revision, - uint256 _amountXMin, - uint256 _amountYMin, - uint256[] memory _ids, - uint256[] memory _amounts, - address _to, - uint256 _deadline - ) external override ensure(_deadline) returns (uint256 amountX, uint256 amountY) { - ILBPair _LBPair = _getLBPairInformation(_tokenX, _tokenY, _binStep, _revision); - bool _isWrongOrder = _tokenX != _LBPair.getTokenX(); + IERC20 tokenX, + IERC20 tokenY, + uint8 binStep, + uint256 revision, + uint256 amountXMin, + uint256 amountYMin, + uint256[] memory ids, + uint256[] memory amounts, + address to, + uint256 deadline + ) external override ensure(deadline) returns (uint256 amountX, uint256 amountY) { + ILBPair lbPair = _getLBPairInformation(tokenX, tokenY, binStep, revision); + bool isWrongOrder = tokenX != lbPair.getTokenX(); - if (_isWrongOrder) (_amountXMin, _amountYMin) = (_amountYMin, _amountXMin); + if (isWrongOrder) (amountXMin, amountYMin) = (amountYMin, amountXMin); - (amountX, amountY) = _removeLiquidity(_LBPair, _amountXMin, _amountYMin, _ids, _amounts, _to); + (amountX, amountY) = _removeLiquidity(lbPair, amountXMin, amountYMin, ids, amounts, to); - if (_isWrongOrder) (amountX, amountY) = (amountY, amountX); + if (isWrongOrder) (amountX, amountY) = (amountY, amountX); } /// @notice Remove AVAX liquidity while performing safety checks /// @dev This function is **NOT** compliant with fee on transfer tokens. /// This is wanted as it would make users pays the fee on transfer twice, /// use the `removeLiquidity` function to remove liquidity with fee on transfer tokens. - /// @param _token The address of token - /// @param _binStep The bin step of the LBPair - /// @param _amountTokenMin The min amount to receive of token - /// @param _amountAVAXMin The min amount to receive of AVAX - /// @param _ids The list of ids to burn - /// @param _amounts The list of amounts to burn of each id in `_ids` - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx + /// @param token The address of token + /// @param binStep The bin step of the LBPair + /// @param amountTokenMin The min amount to receive of token + /// @param amountAVAXMin The min amount to receive of AVAX + /// @param ids The list of ids to burn + /// @param amounts The list of amounts to burn of each id in `ids` + /// @param to The address of the recipient + /// @param deadline The deadline of the tx /// @return amountToken Amount of token returned /// @return amountAVAX Amount of AVAX returned function removeLiquidityAVAX( - IERC20 _token, - uint16 _binStep, - uint256 _revision, - uint256 _amountTokenMin, - uint256 _amountAVAXMin, - uint256[] memory _ids, - uint256[] memory _amounts, - address payable _to, - uint256 _deadline - ) external override ensure(_deadline) returns (uint256 amountToken, uint256 amountAVAX) { - ILBPair _LBPair = _getLBPairInformation(_token, IERC20(wavax), _binStep, _revision); + IERC20 token, + uint8 binStep, + uint256 revision, + uint256 amountTokenMin, + uint256 amountAVAXMin, + uint256[] memory ids, + uint256[] memory amounts, + address payable to, + uint256 deadline + ) external override ensure(deadline) returns (uint256 amountToken, uint256 amountAVAX) { + // TODO - avoid stack too deep and cache wavax + // IWAVAX wavax_ = _wavax; + + ILBPair lbPair = _getLBPairInformation(token, IERC20(_wavax), binStep, revision); { - bool _isAVAXTokenY = IERC20(wavax) == _LBPair.getTokenY(); + bool isAVAXTokenY = IERC20(_wavax) == lbPair.getTokenY(); - if (!_isAVAXTokenY) { - (_amountTokenMin, _amountAVAXMin) = (_amountAVAXMin, _amountTokenMin); + if (!isAVAXTokenY) { + (amountTokenMin, amountAVAXMin) = (amountAVAXMin, amountTokenMin); } - (uint256 _amountX, uint256 _amountY) = - _removeLiquidity(_LBPair, _amountTokenMin, _amountAVAXMin, _ids, _amounts, address(this)); + (uint256 amountX, uint256 amountY) = + _removeLiquidity(lbPair, amountTokenMin, amountAVAXMin, ids, amounts, address(this)); - (amountToken, amountAVAX) = _isAVAXTokenY ? (_amountX, _amountY) : (_amountY, _amountX); + (amountToken, amountAVAX) = isAVAXTokenY ? (amountX, amountY) : (amountY, amountX); } - _token.safeTransfer(_to, amountToken); + token.safeTransfer(to, amountToken); - wavax.withdraw(amountAVAX); - _safeTransferAVAX(_to, amountAVAX); + _wavax.withdraw(amountAVAX); + _safeTransferAVAX(to, amountAVAX); } /// @notice Swaps exact tokens for tokens while performing safety checks - /// @param _amountIn The amount of token to send - /// @param _amountOutMin The min amount of token to receive - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx + /// @param amountIn The amount of token to send + /// @param amountOutMin The min amount of token to receive + /// @param to The address of the recipient + /// @param deadline The deadline of the tx /// @return amountOut Output amount of the swap function swapExactTokensForTokens( - uint256 _amountIn, - uint256 _amountOutMin, - Path memory _path, - address _to, - uint256 _deadline - ) external override ensure(_deadline) verifyPathValidity(_path) returns (uint256 amountOut) { - address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); + uint256 amountIn, + uint256 amountOutMin, + Path memory path, + address to, + uint256 deadline + ) external override ensure(deadline) verifyPathValidity(path) returns (uint256 amountOut) { + address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); - _path.tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountIn); - amountOut = _swapExactTokensForTokens(_amountIn, _pairs, _path.pairBinSteps, _path.tokenPath, _to); + amountOut = _swapExactTokensForTokens(amountIn, pairs, path.pairBinSteps, path.tokenPath, to); - if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); + if (amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMin, amountOut); } /// @notice Swaps exact tokens for AVAX while performing safety checks - /// @param _amountIn The amount of token to send - /// @param _amountOutMinAVAX The min amount of AVAX to receive - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx + /// @param amountIn The amount of token to send + /// @param amountOutMinAVAX The min amount of AVAX to receive + /// @param to The address of the recipient + /// @param deadline The deadline of the tx /// @return amountOut Output amount of the swap function swapExactTokensForAVAX( - uint256 _amountIn, - uint256 _amountOutMinAVAX, - Path memory _path, - address payable _to, - uint256 _deadline - ) external override ensure(_deadline) verifyPathValidity(_path) returns (uint256 amountOut) { - if (_path.tokenPath[_path.pairBinSteps.length] != IERC20(wavax)) { - revert LBRouter__InvalidTokenPath(address(_path.tokenPath[_path.pairBinSteps.length])); + uint256 amountIn, + uint256 amountOutMinAVAX, + Path memory path, + address payable to, + uint256 deadline + ) external override ensure(deadline) verifyPathValidity(path) returns (uint256 amountOut) { + if (path.tokenPath[path.pairBinSteps.length] != IERC20(_wavax)) { + revert LBRouter__InvalidTokenPath(address(path.tokenPath[path.pairBinSteps.length])); } - address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); + address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); - _path.tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountIn); - amountOut = _swapExactTokensForTokens(_amountIn, _pairs, _path.pairBinSteps, _path.tokenPath, address(this)); + amountOut = _swapExactTokensForTokens(amountIn, pairs, path.pairBinSteps, path.tokenPath, address(this)); - if (_amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMinAVAX, amountOut); + if (amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMinAVAX, amountOut); - wavax.withdraw(amountOut); - _safeTransferAVAX(_to, amountOut); + _wavax.withdraw(amountOut); + _safeTransferAVAX(to, amountOut); } /// @notice Swaps exact AVAX for tokens while performing safety checks - /// @param _amountOutMin The min amount of token to receive - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx + /// @param amountOutMin The min amount of token to receive + /// @param to The address of the recipient + /// @param deadline The deadline of the tx /// @return amountOut Output amount of the swap - function swapExactAVAXForTokens(uint256 _amountOutMin, Path memory _path, address _to, uint256 _deadline) + function swapExactAVAXForTokens(uint256 amountOutMin, Path memory path, address to, uint256 deadline) external payable override - ensure(_deadline) - verifyPathValidity(_path) + ensure(deadline) + verifyPathValidity(path) returns (uint256 amountOut) { - if (_path.tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_path.tokenPath[0])); + if (path.tokenPath[0] != IERC20(_wavax)) revert LBRouter__InvalidTokenPath(address(path.tokenPath[0])); - address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); + address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); - _wavaxDepositAndTransfer(_pairs[0], msg.value); + _wavaxDepositAndTransfer(pairs[0], msg.value); - amountOut = _swapExactTokensForTokens(msg.value, _pairs, _path.pairBinSteps, _path.tokenPath, _to); + amountOut = _swapExactTokensForTokens(msg.value, pairs, path.pairBinSteps, path.tokenPath, to); - if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); + if (amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMin, amountOut); } /// @notice Swaps tokens for exact tokens while performing safety checks function swapTokensForExactTokens( - uint256 _amountOut, - uint256 _amountInMax, - Path memory _path, - address _to, - uint256 _deadline - ) external override ensure(_deadline) verifyPathValidity(_path) returns (uint256[] memory amountsIn) { - address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); + uint256 amountOut, + uint256 amountInMax, + Path memory path, + address to, + uint256 deadline + ) external override ensure(deadline) verifyPathValidity(path) returns (uint256[] memory amountsIn) { + address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); { - amountsIn = _getAmountsIn(_path.pairBinSteps, _pairs, _path.tokenPath, _amountOut); + amountsIn = _getAmountsIn(path.pairBinSteps, pairs, path.tokenPath, amountOut); - if (amountsIn[0] > _amountInMax) revert LBRouter__MaxAmountInExceeded(_amountInMax, amountsIn[0]); + if (amountsIn[0] > amountInMax) revert LBRouter__MaxAmountInExceeded(amountInMax, amountsIn[0]); - _path.tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], amountsIn[0]); + path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountsIn[0]); - uint256 _amountOutReal = - _swapTokensForExactTokens(_pairs, _path.pairBinSteps, _path.tokenPath, amountsIn, _to); + uint256 amountOutReal = _swapTokensForExactTokens(pairs, path.pairBinSteps, path.tokenPath, amountsIn, to); - if (_amountOutReal < _amountOut) revert LBRouter__InsufficientAmountOut(_amountOut, _amountOutReal); + if (amountOutReal < amountOut) revert LBRouter__InsufficientAmountOut(amountOut, amountOutReal); } } /// @notice Swaps tokens for exact AVAX while performing safety checks - /// @param _amountAVAXOut The amount of AVAX to receive - /// @param _amountInMax The max amount of token to send - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx - /// @return amountsIn _path amounts for every step of the swap + /// @param amountAVAXOut The amount of AVAX to receive + /// @param amountInMax The max amount of token to send + /// @param to The address of the recipient + /// @param deadline The deadline of the tx + /// @return amountsIn path amounts for every step of the swap function swapTokensForExactAVAX( - uint256 _amountAVAXOut, - uint256 _amountInMax, - Path memory _path, - address payable _to, - uint256 _deadline - ) external override ensure(_deadline) verifyPathValidity(_path) returns (uint256[] memory amountsIn) { - if (_path.tokenPath[_path.pairBinSteps.length] != IERC20(wavax)) { - revert LBRouter__InvalidTokenPath(address(_path.tokenPath[_path.pairBinSteps.length])); + uint256 amountAVAXOut, + uint256 amountInMax, + Path memory path, + address payable to, + uint256 deadline + ) external override ensure(deadline) verifyPathValidity(path) returns (uint256[] memory amountsIn) { + if (path.tokenPath[path.pairBinSteps.length] != IERC20(_wavax)) { + revert LBRouter__InvalidTokenPath(address(path.tokenPath[path.pairBinSteps.length])); } - address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); - amountsIn = _getAmountsIn(_path.pairBinSteps, _pairs, _path.tokenPath, _amountAVAXOut); + address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); + amountsIn = _getAmountsIn(path.pairBinSteps, pairs, path.tokenPath, amountAVAXOut); - if (amountsIn[0] > _amountInMax) revert LBRouter__MaxAmountInExceeded(_amountInMax, amountsIn[0]); + if (amountsIn[0] > amountInMax) revert LBRouter__MaxAmountInExceeded(amountInMax, amountsIn[0]); - _path.tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], amountsIn[0]); + path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountsIn[0]); - uint256 _amountOutReal = - _swapTokensForExactTokens(_pairs, _path.pairBinSteps, _path.tokenPath, amountsIn, address(this)); + uint256 amountOutReal = + _swapTokensForExactTokens(pairs, path.pairBinSteps, path.tokenPath, amountsIn, address(this)); - if (_amountOutReal < _amountAVAXOut) revert LBRouter__InsufficientAmountOut(_amountAVAXOut, _amountOutReal); + if (amountOutReal < amountAVAXOut) revert LBRouter__InsufficientAmountOut(amountAVAXOut, amountOutReal); - wavax.withdraw(_amountOutReal); - _safeTransferAVAX(_to, _amountOutReal); + _wavax.withdraw(amountOutReal); + _safeTransferAVAX(to, amountOutReal); } /// @notice Swaps AVAX for exact tokens while performing safety checks /// @dev Will refund any AVAX amount sent in excess to `msg.sender` - /// @param _amountOut The amount of tokens to receive - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx - /// @return amountsIn _path amounts for every step of the swap - function swapAVAXForExactTokens(uint256 _amountOut, Path memory _path, address _to, uint256 _deadline) + /// @param amountOut The amount of tokens to receive + /// @param to The address of the recipient + /// @param deadline The deadline of the tx + /// @return amountsIn path amounts for every step of the swap + function swapAVAXForExactTokens(uint256 amountOut, Path memory path, address to, uint256 deadline) external payable override - ensure(_deadline) - verifyPathValidity(_path) + ensure(deadline) + verifyPathValidity(path) returns (uint256[] memory amountsIn) { - if (_path.tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_path.tokenPath[0])); + if (path.tokenPath[0] != IERC20(_wavax)) revert LBRouter__InvalidTokenPath(address(path.tokenPath[0])); - address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); - amountsIn = _getAmountsIn(_path.pairBinSteps, _pairs, _path.tokenPath, _amountOut); + address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); + amountsIn = _getAmountsIn(path.pairBinSteps, pairs, path.tokenPath, amountOut); if (amountsIn[0] > msg.value) revert LBRouter__MaxAmountInExceeded(msg.value, amountsIn[0]); - _wavaxDepositAndTransfer(_pairs[0], amountsIn[0]); + _wavaxDepositAndTransfer(pairs[0], amountsIn[0]); - uint256 _amountOutReal = _swapTokensForExactTokens(_pairs, _path.pairBinSteps, _path.tokenPath, amountsIn, _to); + uint256 amountOutReal = _swapTokensForExactTokens(pairs, path.pairBinSteps, path.tokenPath, amountsIn, to); - if (_amountOutReal < _amountOut) revert LBRouter__InsufficientAmountOut(_amountOut, _amountOutReal); + if (amountOutReal < amountOut) revert LBRouter__InsufficientAmountOut(amountOut, amountOutReal); if (msg.value > amountsIn[0]) _safeTransferAVAX(msg.sender, msg.value - amountsIn[0]); } /// @notice Swaps exact tokens for tokens while performing safety checks supporting for fee on transfer tokens - /// @param _amountIn The amount of token to send - /// @param _amountOutMin The min amount of token to receive - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx + /// @param amountIn The amount of token to send + /// @param amountOutMin The min amount of token to receive + /// @param to The address of the recipient + /// @param deadline The deadline of the tx /// @return amountOut Output amount of the swap function swapExactTokensForTokensSupportingFeeOnTransferTokens( - uint256 _amountIn, - uint256 _amountOutMin, - Path memory _path, - address _to, - uint256 _deadline - ) external override ensure(_deadline) verifyPathValidity(_path) returns (uint256 amountOut) { - address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); + uint256 amountIn, + uint256 amountOutMin, + Path memory path, + address to, + uint256 deadline + ) external override ensure(deadline) verifyPathValidity(path) returns (uint256 amountOut) { + address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); - IERC20 _targetToken = _path.tokenPath[_pairs.length]; + IERC20 targetToken = path.tokenPath[pairs.length]; - uint256 _balanceBefore = _targetToken.balanceOf(_to); + uint256 balanceBefore = targetToken.balanceOf(to); - _path.tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountIn); - _swapSupportingFeeOnTransferTokens(_pairs, _path.pairBinSteps, _path.tokenPath, _to); + _swapSupportingFeeOnTransferTokens(pairs, path.pairBinSteps, path.tokenPath, to); - amountOut = _targetToken.balanceOf(_to) - _balanceBefore; - if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); + amountOut = targetToken.balanceOf(to) - balanceBefore; + if (amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMin, amountOut); } /// @notice Swaps exact tokens for AVAX while performing safety checks supporting for fee on transfer tokens - /// @param _amountIn The amount of token to send - /// @param _amountOutMinAVAX The min amount of AVAX to receive - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx + /// @param amountIn The amount of token to send + /// @param amountOutMinAVAX The min amount of AVAX to receive + /// @param to The address of the recipient + /// @param deadline The deadline of the tx /// @return amountOut Output amount of the swap function swapExactTokensForAVAXSupportingFeeOnTransferTokens( - uint256 _amountIn, - uint256 _amountOutMinAVAX, - Path memory _path, - address payable _to, - uint256 _deadline - ) external override ensure(_deadline) verifyPathValidity(_path) returns (uint256 amountOut) { - if (_path.tokenPath[_path.pairBinSteps.length] != IERC20(wavax)) { - revert LBRouter__InvalidTokenPath(address(_path.tokenPath[_path.pairBinSteps.length])); + uint256 amountIn, + uint256 amountOutMinAVAX, + Path memory path, + address payable to, + uint256 deadline + ) external override ensure(deadline) verifyPathValidity(path) returns (uint256 amountOut) { + if (path.tokenPath[path.pairBinSteps.length] != IERC20(_wavax)) { + revert LBRouter__InvalidTokenPath(address(path.tokenPath[path.pairBinSteps.length])); } - address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); + address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); - uint256 _balanceBefore = wavax.balanceOf(address(this)); + uint256 balanceBefore = _wavax.balanceOf(address(this)); - _path.tokenPath[0].safeTransferFrom(msg.sender, _pairs[0], _amountIn); + path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountIn); - _swapSupportingFeeOnTransferTokens(_pairs, _path.pairBinSteps, _path.tokenPath, address(this)); + _swapSupportingFeeOnTransferTokens(pairs, path.pairBinSteps, path.tokenPath, address(this)); - amountOut = wavax.balanceOf(address(this)) - _balanceBefore; - if (_amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMinAVAX, amountOut); + amountOut = _wavax.balanceOf(address(this)) - balanceBefore; + if (amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMinAVAX, amountOut); - wavax.withdraw(amountOut); - _safeTransferAVAX(_to, amountOut); + _wavax.withdraw(amountOut); + _safeTransferAVAX(to, amountOut); } /// @notice Swaps exact AVAX for tokens while performing safety checks supporting for fee on transfer tokens - /// @param _amountOutMin The min amount of token to receive - /// @param _to The address of the recipient - /// @param _deadline The deadline of the tx + /// @param amountOutMin The min amount of token to receive + /// @param to The address of the recipient + /// @param deadline The deadline of the tx /// @return amountOut Output amount of the swap function swapExactAVAXForTokensSupportingFeeOnTransferTokens( - uint256 _amountOutMin, - Path memory _path, - address _to, - uint256 _deadline - ) external payable override ensure(_deadline) verifyPathValidity(_path) returns (uint256 amountOut) { - if (_path.tokenPath[0] != IERC20(wavax)) revert LBRouter__InvalidTokenPath(address(_path.tokenPath[0])); + uint256 amountOutMin, + Path memory path, + address to, + uint256 deadline + ) external payable override ensure(deadline) verifyPathValidity(path) returns (uint256 amountOut) { + if (path.tokenPath[0] != IERC20(_wavax)) revert LBRouter__InvalidTokenPath(address(path.tokenPath[0])); - address[] memory _pairs = _getPairs(_path.pairBinSteps, _path.revisions, _path.tokenPath); + address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); - IERC20 _targetToken = _path.tokenPath[_pairs.length]; + IERC20 targetToken = path.tokenPath[pairs.length]; - uint256 _balanceBefore = _targetToken.balanceOf(_to); + uint256 balanceBefore = targetToken.balanceOf(to); - _wavaxDepositAndTransfer(_pairs[0], msg.value); + _wavaxDepositAndTransfer(pairs[0], msg.value); - _swapSupportingFeeOnTransferTokens(_pairs, _path.pairBinSteps, _path.tokenPath, _to); + _swapSupportingFeeOnTransferTokens(pairs, path.pairBinSteps, path.tokenPath, to); - amountOut = _targetToken.balanceOf(_to) - _balanceBefore; - if (_amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(_amountOutMin, amountOut); + amountOut = targetToken.balanceOf(to) - balanceBefore; + if (amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMin, amountOut); } /// @notice Unstuck tokens that are sent to this contract by mistake /// @dev Only callable by the factory owner - /// @param _token The address of the token - /// @param _to The address of the user to send back the tokens - /// @param _amount The amount to send - function sweep(IERC20 _token, address _to, uint256 _amount) external override onlyFactoryOwner { - if (address(_token) == address(0)) { - if (_amount == type(uint256).max) _amount = address(this).balance; - _safeTransferAVAX(_to, _amount); + /// @param token The address of the token + /// @param to The address of the user to send back the tokens + /// @param amount The amount to send + function sweep(IERC20 token, address to, uint256 amount) external override onlyFactoryOwner { + if (address(token) == address(0)) { + if (amount == type(uint256).max) amount = address(this).balance; + _safeTransferAVAX(to, amount); } else { - if (_amount == type(uint256).max) _amount = _token.balanceOf(address(this)); - _token.safeTransfer(_to, _amount); + if (amount == type(uint256).max) amount = token.balanceOf(address(this)); + token.safeTransfer(to, amount); } } /// @notice Unstuck LBTokens that are sent to this contract by mistake /// @dev Only callable by the factory owner - /// @param _lbToken The address of the LBToken - /// @param _to The address of the user to send back the tokens - /// @param _ids The list of token ids - /// @param _amounts The list of amounts to send - function sweepLBToken(ILBToken _lbToken, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) + /// @param lbToken The address of the LBToken + /// @param to The address of the user to send back the tokens + /// @param ids The list of token ids + /// @param amounts The list of amounts to send + function sweepLBToken(ILBToken lbToken, address to, uint256[] calldata ids, uint256[] calldata amounts) external override onlyFactoryOwner { - _lbToken.batchTransferFrom(address(this), _to, _ids, _amounts); + lbToken.batchTransferFrom(address(this), to, ids, amounts); } /// @notice Helper function to add liquidity - /// @param _liq The liquidity parameter - /// @param _LBPair LBPair where liquidity is deposited + /// @param liq The liquidity parameter + /// @param pair LBPair where liquidity is deposited /// @return liquidityConfigs Bin ids where the liquidity was actually deposited /// @return liquidityMinted Amounts of LBToken minted for each bin - function _addLiquidity(LiquidityParameters calldata _liq, ILBPair _LBPair) + function _addLiquidity(LiquidityParameters calldata liq, ILBPair pair) private - ensure(_liq.deadline) + ensure(liq.deadline) returns (bytes32[] memory liquidityConfigs, uint256[] memory liquidityMinted) { unchecked { - if (_liq.deltaIds.length != _liq.distributionX.length && _liq.deltaIds.length != _liq.distributionY.length) - { + if (liq.deltaIds.length != liq.distributionX.length && liq.deltaIds.length != liq.distributionY.length) { revert LBRouter__LengthsMismatch(); } - if (_liq.activeIdDesired > type(uint24).max || _liq.idSlippage > type(uint24).max) { - revert LBRouter__IdDesiredOverflows(_liq.activeIdDesired, _liq.idSlippage); + if (liq.activeIdDesired > type(uint24).max || liq.idSlippage > type(uint24).max) { + revert LBRouter__IdDesiredOverflows(liq.activeIdDesired, liq.idSlippage); } - uint256 _activeId = _LBPair.getActiveId(); - if ( - _liq.activeIdDesired + _liq.idSlippage < _activeId || _activeId + _liq.idSlippage < _liq.activeIdDesired - ) revert LBRouter__IdSlippageCaught(_liq.activeIdDesired, _liq.idSlippage, _activeId); + uint256 activeId = pair.getActiveId(); + if (liq.activeIdDesired + liq.idSlippage < activeId || activeId + liq.idSlippage < liq.activeIdDesired) { + revert LBRouter__IdSlippageCaught(liq.activeIdDesired, liq.idSlippage, activeId); + } - liquidityConfigs = new bytes32[](_liq.deltaIds.length); + liquidityConfigs = new bytes32[](liq.deltaIds.length); for (uint256 i; i < liquidityConfigs.length; ++i) { - int256 _id = int256(_activeId) + _liq.deltaIds[i]; - if (_id < 0 || uint256(_id) > type(uint24).max) revert LBRouter__IdOverflows(_id); + int256 id = int256(activeId) + liq.deltaIds[i]; + if (id < 0 || uint256(id) > type(uint24).max) revert LBRouter__IdOverflows(id); liquidityConfigs[i] = LiquidityConfigurations.encodeParams( - uint64(_liq.distributionX[i]), uint64(_liq.distributionY[i]), uint24(uint256(_id)) + uint64(liq.distributionX[i]), uint64(liq.distributionY[i]), uint24(uint256(id)) ); } - uint256 _amountXAdded; - uint256 _amountYAdded; + bytes32 amountsReceived; + (amountsReceived,, liquidityMinted) = pair.mint(msg.sender, liquidityConfigs, liq.to); - (bytes32 amountsReceived, bytes32 amountsLeft, uint256[] memory liquidityMinted) = - _LBPair.mint(msg.sender, liquidityConfigs, _liq.to); + uint256 amountXAdded = amountsReceived.decodeUint128(0); + uint256 amountYAdded = amountsReceived.decodeUint128(128); - if (_amountXAdded < _liq.amountXMin || _amountYAdded < _liq.amountYMin) { - revert LBRouter__AmountSlippageCaught(_liq.amountXMin, _amountXAdded, _liq.amountYMin, _amountYAdded); + if (amountXAdded < liq.amountXMin || amountYAdded < liq.amountYMin) { + revert LBRouter__AmountSlippageCaught(liq.amountXMin, amountXAdded, liq.amountYMin, amountYAdded); } } } /// @notice Helper function to return the amounts in /// @param pairBinSteps The bin step of the pairs (0: V1, other values will use V2) - /// @param _pairs The list of pairs + /// @param pairs The list of pairs /// @param tokenPath The swap path - /// @param _amountOut The amount out + /// @param amountOut The amount out /// @return amountsIn The list of amounts in function _getAmountsIn( uint256[] memory pairBinSteps, - address[] memory _pairs, + address[] memory pairs, IERC20[] memory tokenPath, - uint256 _amountOut + uint256 amountOut ) private view returns (uint256[] memory amountsIn) { amountsIn = new uint256[](tokenPath.length); - // Avoid doing -1, as `_pairs.length == pairBinSteps.length-1` - amountsIn[_pairs.length] = _amountOut; + // Avoid doing -1, as `pairs.length == pairBinSteps.length-1` + amountsIn[pairs.length] = amountOut; - for (uint256 i = _pairs.length; i != 0; i--) { - IERC20 _token = tokenPath[i - 1]; - uint256 _binStep = pairBinSteps[i - 1]; + for (uint256 i = pairs.length; i != 0; i--) { + IERC20 token = tokenPath[i - 1]; + uint256 binStep = pairBinSteps[i - 1]; - address _pair = _pairs[i - 1]; + address pair = pairs[i - 1]; - if (_binStep == 0) { - (uint256 _reserveIn, uint256 _reserveOut,) = IJoePair(_pair).getReserves(); - if (_token > tokenPath[i]) { - (_reserveIn, _reserveOut) = (_reserveOut, _reserveIn); + if (binStep == 0) { + (uint256 reserveIn, uint256 reserveOut,) = IJoePair(pair).getReserves(); + if (token > tokenPath[i]) { + (reserveIn, reserveOut) = (reserveOut, reserveIn); } uint256 amountOut_ = amountsIn[i]; - amountsIn[i - 1] = uint128(uint256(amountOut_).getAmountIn(_reserveIn, _reserveOut)); + amountsIn[i - 1] = uint128(uint256(amountOut_).getAmountIn(reserveIn, reserveOut)); } else { (amountsIn[i - 1],,) = - getSwapIn(ILBPair(_pair), uint128(amountsIn[i]), ILBPair(_pair).getTokenX() == _token); + getSwapIn(ILBPair(pair), uint128(amountsIn[i]), ILBPair(pair).getTokenX() == token); } } } /// @notice Helper function to remove liquidity - /// @param _LBPair The address of the LBPair - /// @param _amountXMin The min amount to receive of token X - /// @param _amountYMin The min amount to receive of token Y - /// @param _ids The list of ids to burn - /// @param _amounts The list of amounts to burn of each id in `_ids` - /// @param _to The address of the recipient + /// @param pair The address of the LBPair + /// @param amountXMin The min amount to receive of token X + /// @param amountYMin The min amount to receive of token Y + /// @param ids The list of ids to burn + /// @param amounts The list of amounts to burn of each id in `ids` + /// @param to The address of the recipient /// @return amountX The amount of token X sent by the pair /// @return amountY The amount of token Y sent by the pair function _removeLiquidity( - ILBPair _LBPair, - uint256 _amountXMin, - uint256 _amountYMin, - uint256[] memory _ids, - uint256[] memory _amounts, - address _to + ILBPair pair, + uint256 amountXMin, + uint256 amountYMin, + uint256[] memory ids, + uint256[] memory amounts, + address to ) private returns (uint256 amountX, uint256 amountY) { - ILBToken(address(_LBPair)).batchTransferFrom(msg.sender, address(_LBPair), _ids, _amounts); - (bytes32[] memory amounts) = _LBPair.burn(msg.sender, _to, _ids, _amounts); - if (amountX < _amountXMin || amountY < _amountYMin) { - revert LBRouter__AmountSlippageCaught(_amountXMin, amountX, _amountYMin, amountY); + ILBToken(address(pair)).batchTransferFrom(msg.sender, address(pair), ids, amounts); + (bytes32[] memory amountsBurned) = pair.burn(msg.sender, to, ids, amounts); + + for (uint256 i; i < amountsBurned.length; ++i) { + amountX += amountsBurned[i].decodeUint128(0); + amountY += amountsBurned[i].decodeUint128(128); + } + + if (amountX < amountXMin || amountY < amountYMin) { + revert LBRouter__AmountSlippageCaught(amountXMin, amountX, amountYMin, amountY); } } /// @notice Helper function to swap exact tokens for tokens - /// @param _amountIn The amount of token sent - /// @param _pairs The list of pairs + /// @param amountIn The amount of token sent + /// @param pairs The list of pairs /// @param pairBinSteps The bin step of the pairs (0: V1, other values will use V2) /// @param tokenPath The swap path using the binSteps following `pairBinSteps` - /// @param _to The address of the recipient - /// @return amountOut The amount of token sent to `_to` + /// @param to The address of the recipient + /// @return amountOut The amount of token sent to `to` function _swapExactTokensForTokens( - uint256 _amountIn, - address[] memory _pairs, + uint256 amountIn, + address[] memory pairs, uint256[] memory pairBinSteps, IERC20[] memory tokenPath, - address _to + address to ) private returns (uint256 amountOut) { - IERC20 _token; - uint256 _binStep; - address _recipient; - address _pair; + IERC20 token; + uint256 binStep; + address recipient; + address pair; - IERC20 _tokenNext = tokenPath[0]; - amountOut = _amountIn; + IERC20 tokenNext = tokenPath[0]; + amountOut = amountIn; unchecked { - for (uint256 i; i < _pairs.length; ++i) { - _pair = _pairs[i]; - _binStep = pairBinSteps[i]; + for (uint256 i; i < pairs.length; ++i) { + pair = pairs[i]; + binStep = pairBinSteps[i]; - _token = _tokenNext; - _tokenNext = tokenPath[i + 1]; + token = tokenNext; + tokenNext = tokenPath[i + 1]; - _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; + recipient = i + 1 == pairs.length ? to : pairs[i + 1]; - if (_binStep == 0) { - (uint256 _reserve0, uint256 _reserve1,) = IJoePair(_pair).getReserves(); + if (binStep == 0) { + (uint256 reserve0, uint256 reserve1,) = IJoePair(pair).getReserves(); - if (_token < _tokenNext) { - amountOut = amountOut.getAmountOut(_reserve0, _reserve1); - IJoePair(_pair).swap(0, amountOut, _recipient, ""); + if (token < tokenNext) { + amountOut = amountOut.getAmountOut(reserve0, reserve1); + IJoePair(pair).swap(0, amountOut, recipient, ""); } else { - amountOut = amountOut.getAmountOut(_reserve1, _reserve0); - IJoePair(_pair).swap(amountOut, 0, _recipient, ""); + amountOut = amountOut.getAmountOut(reserve1, reserve0); + IJoePair(pair).swap(amountOut, 0, recipient, ""); } } else { - bool _swapForY = _tokenNext == ILBPair(_pair).getTokenY(); + bool swapForY = tokenNext == ILBPair(pair).getTokenY(); - (uint256 _amountXOut, uint256 _amountYOut) = ILBPair(_pair).swap(_swapForY, _recipient).decode(); + (uint256 amountXOut, uint256 amountYOut) = ILBPair(pair).swap(swapForY, recipient).decode(); - if (_swapForY) amountOut = _amountYOut; - else amountOut = _amountXOut; + if (swapForY) amountOut = amountYOut; + else amountOut = amountXOut; } } } } /// @notice Helper function to swap tokens for exact tokens - /// @param _pairs The array of pairs + /// @param pairs The array of pairs /// @param pairBinSteps The bin step of the pairs (0: V1, other values will use V2) /// @param tokenPath The swap path using the binSteps following `pairBinSteps` - /// @param _amountsIn The list of amounts in - /// @param _to The address of the recipient - /// @return amountOut The amount of token sent to `_to` + /// @param amountsIn The list of amounts in + /// @param to The address of the recipient + /// @return amountOut The amount of token sent to `to` function _swapTokensForExactTokens( - address[] memory _pairs, + address[] memory pairs, uint256[] memory pairBinSteps, IERC20[] memory tokenPath, - uint256[] memory _amountsIn, - address _to + uint256[] memory amountsIn, + address to ) private returns (uint256 amountOut) { - IERC20 _token; - uint256 _binStep; - address _recipient; - address _pair; + IERC20 token; + uint256 binStep; + address recipient; + address pair; - IERC20 _tokenNext = tokenPath[0]; + IERC20 tokenNext = tokenPath[0]; unchecked { - for (uint256 i; i < _pairs.length; ++i) { - _pair = _pairs[i]; - _binStep = pairBinSteps[i]; + for (uint256 i; i < pairs.length; ++i) { + pair = pairs[i]; + binStep = pairBinSteps[i]; - _token = _tokenNext; - _tokenNext = tokenPath[i + 1]; + token = tokenNext; + tokenNext = tokenPath[i + 1]; - _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; + recipient = i + 1 == pairs.length ? to : pairs[i + 1]; - if (_binStep == 0) { - amountOut = _amountsIn[i + 1]; - if (_token < _tokenNext) { - IJoePair(_pair).swap(0, amountOut, _recipient, ""); + if (binStep == 0) { + amountOut = amountsIn[i + 1]; + if (token < tokenNext) { + IJoePair(pair).swap(0, amountOut, recipient, ""); } else { - IJoePair(_pair).swap(amountOut, 0, _recipient, ""); + IJoePair(pair).swap(amountOut, 0, recipient, ""); } } else { - bool _swapForY = _tokenNext == ILBPair(_pair).getTokenY(); + bool swapForY = tokenNext == ILBPair(pair).getTokenY(); - (uint256 _amountXOut, uint256 _amountYOut) = ILBPair(_pair).swap(_swapForY, _recipient).decode(); + (uint256 amountXOut, uint256 amountYOut) = ILBPair(pair).swap(swapForY, recipient).decode(); - if (_swapForY) amountOut = _amountYOut; - else amountOut = _amountXOut; + if (swapForY) amountOut = amountYOut; + else amountOut = amountXOut; } } } } /// @notice Helper function to swap exact tokens supporting for fee on transfer tokens - /// @param _pairs The list of pairs + /// @param pairs The list of pairs /// @param pairBinSteps The bin step of the pairs (0: V1, other values will use V2) /// @param tokenPath The swap path using the binSteps following `pairBinSteps` - /// @param _to The address of the recipient + /// @param to The address of the recipient function _swapSupportingFeeOnTransferTokens( - address[] memory _pairs, + address[] memory pairs, uint256[] memory pairBinSteps, IERC20[] memory tokenPath, - address _to + address to ) private { - IERC20 _token; - uint256 _binStep; - address _recipient; - address _pair; + IERC20 token; + uint256 binStep; + address recipient; + address pair; - IERC20 _tokenNext = tokenPath[0]; + IERC20 tokenNext = tokenPath[0]; unchecked { - for (uint256 i; i < _pairs.length; ++i) { - _pair = _pairs[i]; - _binStep = pairBinSteps[i]; + for (uint256 i; i < pairs.length; ++i) { + pair = pairs[i]; + binStep = pairBinSteps[i]; - _token = _tokenNext; - _tokenNext = tokenPath[i + 1]; + token = tokenNext; + tokenNext = tokenPath[i + 1]; - _recipient = i + 1 == _pairs.length ? _to : _pairs[i + 1]; + recipient = i + 1 == pairs.length ? to : pairs[i + 1]; - if (_binStep == 0) { - (uint256 _reserve0, uint256 _reserve1,) = IJoePair(_pair).getReserves(); - if (_token < _tokenNext) { - uint256 _amountIn = _token.balanceOf(_pair) - _reserve0; - uint256 _amountOut = _amountIn.getAmountOut(_reserve0, _reserve1); + if (binStep == 0) { + (uint256 reserve0, uint256 reserve1,) = IJoePair(pair).getReserves(); + if (token < tokenNext) { + uint256 amountIn = token.balanceOf(pair) - reserve0; + uint256 amountOut = amountIn.getAmountOut(reserve0, reserve1); - IJoePair(_pair).swap(0, _amountOut, _recipient, ""); + IJoePair(pair).swap(0, amountOut, recipient, ""); } else { - uint256 _amountIn = _token.balanceOf(_pair) - _reserve1; - uint256 _amountOut = _amountIn.getAmountOut(_reserve1, _reserve0); + uint256 amountIn = token.balanceOf(pair) - reserve1; + uint256 amountOut = amountIn.getAmountOut(reserve1, reserve0); - IJoePair(_pair).swap(_amountOut, 0, _recipient, ""); + IJoePair(pair).swap(amountOut, 0, recipient, ""); } } else { - ILBPair(_pair).swap(_tokenNext == ILBPair(_pair).getTokenY(), _recipient); + ILBPair(pair).swap(tokenNext == ILBPair(pair).getTokenY(), recipient); } } } @@ -813,44 +841,44 @@ contract LBRouter is ILBRouter { /// @notice Helper function to return the address of the LBPair /// @dev Revert if the pair is not created yet - /// @param _tokenX The address of the tokenX - /// @param _tokenY The address of the tokenY - /// @param _binStep The bin step of the LBPair + /// @param tokenX The address of the tokenX + /// @param tokenY The address of the tokenY + /// @param binStep The bin step of the LBPair /// @return The address of the LBPair - function _getLBPairInformation(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep, uint256 _revision) + function _getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep, uint256 revision) private view returns (ILBPair) { - ILBPair _LBPair; - if (_revision == 0) { - _LBPair = legacyFactory.getLBPairInformation(_tokenX, _tokenY, _binStep).LBPair; + ILBPair pair; + if (revision == 0) { + pair = _legacyFactory.getLBPairInformation(tokenX, tokenY, binStep).LBPair; } else { - _LBPair = factory.getLBPairInformation(_tokenX, _tokenY, _binStep, _revision).LBPair; + pair = _factory.getLBPairInformation(tokenX, tokenY, binStep, revision).LBPair; } - if (address(_LBPair) == address(0)) { - revert LBRouter__PairNotCreated(address(_tokenX), address(_tokenY), _binStep); + if (address(pair) == address(0)) { + revert LBRouter__PairNotCreated(address(tokenX), address(tokenY), binStep); } - return _LBPair; + return pair; } - /// @notice Helper function to return the address of the pair (v1 or v2, according to `_binStep`) + /// @notice Helper function to return the address of the pair (v1 or v2, according to `binStep`) /// @dev Revert if the pair is not created yet - /// @param _binStep The bin step of the LBPair, 0 means using V1 pair, any other value will use V2 - /// @param _tokenX The address of the tokenX - /// @param _tokenY The address of the tokenY - /// @return _pair The address of the pair of binStep `_binStep` - function _getPair(IERC20 _tokenX, IERC20 _tokenY, uint256 _binStep, uint256 _revision) + /// @param binStep The bin step of the LBPair, 0 means using V1 pair, any other value will use V2 + /// @param tokenX The address of the tokenX + /// @param tokenY The address of the tokenY + /// @return pair The address of the pair of binStep `binStep` + function _getPair(IERC20 tokenX, IERC20 tokenY, uint256 binStep, uint256 revision) private view - returns (address _pair) + returns (address pair) { - if (_binStep == 0) { - _pair = oldFactory.getPair(address(_tokenX), address(_tokenY)); - if (_pair == address(0)) revert LBRouter__PairNotCreated(address(_tokenX), address(_tokenY), _binStep); + if (binStep == 0) { + pair = _oldFactory.getPair(address(tokenX), address(tokenY)); + if (pair == address(0)) revert LBRouter__PairNotCreated(address(tokenX), address(tokenY), binStep); } else { - _pair = address(_getLBPairInformation(_tokenX, _tokenY, _binStep, _revision)); + pair = address(_getLBPairInformation(tokenX, tokenY, binStep, revision)); } } @@ -861,31 +889,31 @@ contract LBRouter is ILBRouter { { pairs = new address[](pairBinSteps.length); - IERC20 _token; - IERC20 _tokenNext = tokenPath[0]; + IERC20 token; + IERC20 tokenNext = tokenPath[0]; unchecked { for (uint256 i; i < pairs.length; ++i) { - _token = _tokenNext; - _tokenNext = tokenPath[i + 1]; + token = tokenNext; + tokenNext = tokenPath[i + 1]; - pairs[i] = _getPair(_token, _tokenNext, pairBinSteps[i], revisions[i]); + pairs[i] = _getPair(token, tokenNext, pairBinSteps[i], revisions[i]); } } } /// @notice Helper function to transfer AVAX - /// @param _to The address of the recipient - /// @param _amount The AVAX amount to send - function _safeTransferAVAX(address _to, uint256 _amount) private { - (bool success,) = _to.call{value: _amount}(""); - if (!success) revert LBRouter__FailedToSendAVAX(_to, _amount); - } - - /// @notice Helper function to deposit and transfer wavax - /// @param _to The address of the recipient - /// @param _amount The AVAX amount to wrap - function _wavaxDepositAndTransfer(address _to, uint256 _amount) private { - wavax.deposit{value: _amount}(); - wavax.safeTransfer(_to, _amount); + /// @param to The address of the recipient + /// @param amount The AVAX amount to send + function _safeTransferAVAX(address to, uint256 amount) private { + (bool success,) = to.call{value: amount}(""); + if (!success) revert LBRouter__FailedToSendAVAX(to, amount); + } + + /// @notice Helper function to deposit and transfer _wavax + /// @param to The address of the recipient + /// @param amount The AVAX amount to wrap + function _wavaxDepositAndTransfer(address to, uint256 amount) private { + _wavax.deposit{value: amount}(); + _wavax.safeTransfer(to, amount); } } diff --git a/src/LBToken.sol b/src/LBToken.sol index e99f9bdb..6816f0ca 100644 --- a/src/LBToken.sol +++ b/src/LBToken.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.10; -import "./interfaces/ILBToken.sol"; +import {ILBToken} from "./interfaces/ILBToken.sol"; /** * @title Liquidity Book Token diff --git a/src/interfaces/IJoeRouter02.sol b/src/interfaces/IJoeRouter02.sol index 98206971..318ddd6e 100644 --- a/src/interfaces/IJoeRouter02.sol +++ b/src/interfaces/IJoeRouter02.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.10; -import "./IJoeRouter01.sol"; +import {IJoeRouter01} from "./IJoeRouter01.sol"; /// @title Joe V1 Router Interface /// @notice Interface to interact with Joe V1 Router diff --git a/src/interfaces/ILBFactory.sol b/src/interfaces/ILBFactory.sol index e320ae27..04d01884 100644 --- a/src/interfaces/ILBFactory.sol +++ b/src/interfaces/ILBFactory.sol @@ -2,10 +2,10 @@ pragma solidity 0.8.10; -import "openzeppelin/token/ERC20/IERC20.sol"; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; -import "./ILBPair.sol"; -import "./IPendingOwnable.sol"; +import {ILBPair} from "./ILBPair.sol"; +import {IPendingOwnable} from "./IPendingOwnable.sol"; /// @title Liquidity Book Factory Interface /// @author Trader Joe @@ -44,7 +44,7 @@ interface ILBFactory is IPendingOwnable { /// - revisionIndex: The revision index of the LBPair /// - implementation: The implementation address of the LBPair struct LBPairInformation { - uint16 binStep; + uint8 binStep; ILBPair LBPair; bool createdByOwner; bool ignoredForRouting; @@ -87,8 +87,7 @@ interface ILBFactory is IPendingOwnable { uint256 reductionFactor, uint256 variableFeeControl, uint256 protocolShare, - uint256 maxVolatilityAccumulated, - uint256 sampleLifetime + uint256 maxVolatilityAccumulated ); event PresetRemoved(uint256 indexed binStep); @@ -97,15 +96,15 @@ interface ILBFactory is IPendingOwnable { event QuoteAssetRemoved(IERC20 indexed quoteAsset); - function MAX_FEE() external pure returns (uint256); + function getMaxFee() external pure returns (uint256); - function MIN_BIN_STEP() external pure returns (uint256); + function getMinBinStep() external pure returns (uint256); - function MAX_BIN_STEP() external pure returns (uint256); + function getMaxBinStep() external pure returns (uint256); - function MAX_PROTOCOL_SHARE() external pure returns (uint256); + function getMaxProtocolShare() external pure returns (uint256); - function LBPairImplementation() external view returns (address); + function getLBPairImplementation() external view returns (address); function getNumberOfQuoteAssets() external view returns (uint256); @@ -113,13 +112,13 @@ interface ILBFactory is IPendingOwnable { function isQuoteAsset(IERC20 token) external view returns (bool); - function feeRecipient() external view returns (address); + function getFeeRecipient() external view returns (address); - function flashLoanFee() external view returns (uint256); + function getFlashloanFee() external view returns (uint256); - function creationUnlocked() external view returns (bool); + function isCreationUnlocked() external view returns (bool); - function allLBPairs(uint256 id) external returns (ILBPair); + function getLBPairAtIndex(uint256 id) external returns (ILBPair); function getNumberOfLBPairs() external view returns (uint256); @@ -130,7 +129,7 @@ interface ILBFactory is IPendingOwnable { view returns (LBPairInformation memory); - function getPreset(uint16 binStep) + function getPreset(uint256 binStep) external view returns ( @@ -140,8 +139,7 @@ interface ILBFactory is IPendingOwnable { uint256 reductionFactor, uint256 variableFeeControl, uint256 protocolShare, - uint256 maxAccumulator, - uint256 sampleLifetime + uint256 maxAccumulator ); function getAllBinSteps() external view returns (uint256[] memory presetsBinStep); @@ -153,33 +151,32 @@ interface ILBFactory is IPendingOwnable { function setLBPairImplementation(address LBPairImplementation) external; - function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) + function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint8 binStep) external returns (ILBPair pair); - function createLBPairRevision(IERC20 _tokenX, IERC20 _tokenY, uint16 _binStep) external returns (ILBPair pair); + function createLBPairRevision(IERC20 tokenX, IERC20 tokenY, uint8 binStep) external returns (ILBPair pair); function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, uint256 revision, bool ignored) external; function setPreset( - uint16 binStep, + uint8 binStep, uint16 baseFactor, uint16 filterPeriod, uint16 decayPeriod, uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated, - uint16 sampleLifetime + uint24 maxVolatilityAccumulated ) external; - function removePreset(uint16 binStep) external; + function removePreset(uint8 binStep) external; function setFeesParametersOnPair( IERC20 tokenX, IERC20 tokenY, - uint16 binStep, - uint256 revision, + uint8 binStep, + uint16 revision, uint16 baseFactor, uint16 filterPeriod, uint16 decayPeriod, diff --git a/src/interfaces/ILBFlashLoanCallback.sol b/src/interfaces/ILBFlashLoanCallback.sol index de6d0dd3..6afa791b 100644 --- a/src/interfaces/ILBFlashLoanCallback.sol +++ b/src/interfaces/ILBFlashLoanCallback.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.10; -import "openzeppelin/token/ERC20/IERC20.sol"; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; /// @title Liquidity Book Flashloan Callback Interface /// @author Trader Joe diff --git a/src/interfaces/ILBLegacyFactory.sol b/src/interfaces/ILBLegacyFactory.sol index 515a97d6..096bf3af 100644 --- a/src/interfaces/ILBLegacyFactory.sol +++ b/src/interfaces/ILBLegacyFactory.sol @@ -2,10 +2,10 @@ pragma solidity 0.8.10; -import "openzeppelin/token/ERC20/IERC20.sol"; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; -import "./ILBPair.sol"; -import "./IPendingOwnable.sol"; +import {ILBPair} from "./ILBPair.sol"; +import {IPendingOwnable} from "./IPendingOwnable.sol"; /// @title Liquidity Book Factory Interface /// @author Trader Joe @@ -24,11 +24,7 @@ interface ILBLegacyFactory is IPendingOwnable { } event LBPairCreated( - IERC20 indexed tokenX, - IERC20 indexed tokenY, - uint256 indexed binStep, - ILBPair LBPair, - uint256 pid + IERC20 indexed tokenX, IERC20 indexed tokenY, uint256 indexed binStep, ILBPair LBPair, uint256 pid ); event FeeRecipientSet(address oldRecipient, address newRecipient); @@ -98,15 +94,12 @@ interface ILBLegacyFactory is IPendingOwnable { function getNumberOfLBPairs() external view returns (uint256); - function getLBPairInformation( - IERC20 tokenX, - IERC20 tokenY, - uint256 binStep - ) external view returns (LBPairInformation memory); + function getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep) + external + view + returns (LBPairInformation memory); - function getPreset( - uint16 binStep - ) + function getPreset(uint16 binStep) external view returns ( @@ -122,19 +115,16 @@ interface ILBLegacyFactory is IPendingOwnable { function getAllBinSteps() external view returns (uint256[] memory presetsBinStep); - function getAllLBPairs( - IERC20 tokenX, - IERC20 tokenY - ) external view returns (LBPairInformation[] memory LBPairsBinStep); + function getAllLBPairs(IERC20 tokenX, IERC20 tokenY) + external + view + returns (LBPairInformation[] memory LBPairsBinStep); function setLBPairImplementation(address LBPairImplementation) external; - function createLBPair( - IERC20 tokenX, - IERC20 tokenY, - uint24 activeId, - uint16 binStep - ) external returns (ILBPair pair); + function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) + external + returns (ILBPair pair); function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) external; diff --git a/src/interfaces/ILBLegacyPair.sol b/src/interfaces/ILBLegacyPair.sol new file mode 100644 index 00000000..c25179da --- /dev/null +++ b/src/interfaces/ILBLegacyPair.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "openzeppelin/token/ERC20/IERC20.sol"; + +/// @title Liquidity Book Pair V2 Interface +/// @author Trader Joe +/// @notice Required interface of LBPair contract +interface ILBLegacyPair { + function tokenX() external view returns (IERC20); + + function tokenY() external view returns (IERC20); +} diff --git a/src/interfaces/ILBLegacyRouter.sol b/src/interfaces/ILBLegacyRouter.sol index 78590901..caae7910 100644 --- a/src/interfaces/ILBLegacyRouter.sol +++ b/src/interfaces/ILBLegacyRouter.sol @@ -2,10 +2,13 @@ pragma solidity 0.8.10; -import "./IJoeFactory.sol"; -import "./ILBPair.sol"; -import "./ILBToken.sol"; -import "./IWAVAX.sol"; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; + +import {ILBFactory} from "./ILBFactory.sol"; +import {IJoeFactory} from "./IJoeFactory.sol"; +import {ILBPair} from "./ILBPair.sol"; +import {ILBToken} from "./ILBToken.sol"; +import {IWAVAX} from "./IWAVAX.sol"; /// @title Liquidity Book Router Interface /// @author Trader Joe diff --git a/src/interfaces/ILBRouter.sol b/src/interfaces/ILBRouter.sol index 148d3747..5d760403 100644 --- a/src/interfaces/ILBRouter.sol +++ b/src/interfaces/ILBRouter.sol @@ -2,10 +2,14 @@ pragma solidity 0.8.10; -import "./IJoeFactory.sol"; -import "./ILBPair.sol"; -import "./ILBToken.sol"; -import "./IWAVAX.sol"; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; + +import {IJoeFactory} from "./IJoeFactory.sol"; +import {ILBFactory} from "./ILBFactory.sol"; +import {ILBLegacyFactory} from "./ILBLegacyFactory.sol"; +import {ILBPair} from "./ILBPair.sol"; +import {ILBToken} from "./ILBToken.sol"; +import {IWAVAX} from "./IWAVAX.sol"; /// @title Liquidity Book Router Interface /// @author Trader Joe @@ -80,11 +84,13 @@ interface ILBRouter { IERC20[] tokenPath; } - function factory() external view returns (ILBFactory); + function getFactory() external view returns (ILBFactory); + + function getLegacyFactory() external view returns (ILBLegacyFactory); - function oldFactory() external view returns (IJoeFactory); + function getOldFactory() external view returns (IJoeFactory); - function wavax() external view returns (IWAVAX); + function getWAVAX() external view returns (IWAVAX); function getIdFromPrice(ILBPair LBPair, uint256 price) external view returns (uint24); @@ -100,7 +106,7 @@ interface ILBRouter { view returns (uint128 amountInLeft, uint128 amountOut, uint128 fee); - function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) + function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint8 binStep) external returns (ILBPair pair); @@ -116,7 +122,7 @@ interface ILBRouter { function removeLiquidity( IERC20 tokenX, IERC20 tokenY, - uint16 binStep, + uint8 binStep, uint256 revision, uint256 amountXMin, uint256 amountYMin, @@ -128,7 +134,7 @@ interface ILBRouter { function removeLiquidityAVAX( IERC20 token, - uint16 binStep, + uint8 binStep, uint256 revision, uint256 amountTokenMin, uint256 amountAVAXMin, diff --git a/src/interfaces/IWAVAX.sol b/src/interfaces/IWAVAX.sol index 74f77bdb..e40ba884 100644 --- a/src/interfaces/IWAVAX.sol +++ b/src/interfaces/IWAVAX.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.10; -import "openzeppelin/token/ERC20/IERC20.sol"; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; /** * @title WAVAX Interface diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index dd4ce39b..ab1667d4 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -2,16 +2,16 @@ pragma solidity 0.8.10; -import "openzeppelin/token/ERC20/IERC20.sol"; - -import "./math/PackedUint128Math.sol"; -import "./math/Uint256x256Math.sol"; -import "./math/SafeCast.sol"; -import "./Constants.sol"; -import "./PairParameterHelper.sol"; -import "./FeeHelper.sol"; -import "./PriceHelper.sol"; -import "./TokenHelper.sol"; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; + +import {PackedUint128Math} from "./math/PackedUint128Math.sol"; +import {Uint256x256Math} from "./math/Uint256x256Math.sol"; +import {SafeCast} from "./math/SafeCast.sol"; +import {Constants} from "./Constants.sol"; +import {PairParameterHelper} from "./PairParameterHelper.sol"; +import {FeeHelper} from "./FeeHelper.sol"; +import {PriceHelper} from "./PriceHelper.sol"; +import {TokenHelper} from "./TokenHelper.sol"; /** * @title Liquidity Book Bin Helper Library diff --git a/src/libraries/FeeHelper.sol b/src/libraries/FeeHelper.sol index 101c3620..3c6be67e 100644 --- a/src/libraries/FeeHelper.sol +++ b/src/libraries/FeeHelper.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.10; -import "./Constants.sol"; -import "./math/SafeCast.sol"; +import {Constants} from "./Constants.sol"; +import {SafeCast} from "./math/SafeCast.sol"; /** * @title Liquidity Book Fee Helper Library diff --git a/src/libraries/OracleHelper.sol b/src/libraries/OracleHelper.sol index d8560e56..dc3941a5 100644 --- a/src/libraries/OracleHelper.sol +++ b/src/libraries/OracleHelper.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.10; -import "./math/SampleMath.sol"; -import "./math/SafeCast.sol"; -import "./PairParameterHelper.sol"; +import {SampleMath} from "./math/SampleMath.sol"; +import {SafeCast} from "./math/SafeCast.sol"; +import {PairParameterHelper} from "./PairParameterHelper.sol"; /** * @title Liquidity Book Oracle Helper Library diff --git a/src/libraries/PairParameterHelper.sol b/src/libraries/PairParameterHelper.sol index 8ef6e007..ba0dd225 100644 --- a/src/libraries/PairParameterHelper.sol +++ b/src/libraries/PairParameterHelper.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.10; -import "./Constants.sol"; -import "./math/SafeCast.sol"; -import "./math/Encoded.sol"; +import {Constants} from "./Constants.sol"; +import {SafeCast} from "./math/SafeCast.sol"; +import {Encoded} from "./math/Encoded.sol"; /** * @title Liquidity Book Pair Parameter Helper Library diff --git a/src/libraries/PendingOwnable.sol b/src/libraries/PendingOwnable.sol index 8ac22433..30e8ff43 100644 --- a/src/libraries/PendingOwnable.sol +++ b/src/libraries/PendingOwnable.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.10; -import "../interfaces/IPendingOwnable.sol"; +import {IPendingOwnable} from "../interfaces/IPendingOwnable.sol"; /** * @title Pending Ownable diff --git a/src/libraries/PriceHelper.sol b/src/libraries/PriceHelper.sol index 5f15dab4..f47b82f6 100644 --- a/src/libraries/PriceHelper.sol +++ b/src/libraries/PriceHelper.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.10; -import "./math/Uint128x128Math.sol"; -import "./math/SafeCast.sol"; -import "./Constants.sol"; +import {Uint128x128Math} from "./math/Uint128x128Math.sol"; +import {SafeCast} from "./math/SafeCast.sol"; +import {Constants} from "./Constants.sol"; /** * @title Liquidity Book Price Helper Library diff --git a/src/libraries/TokenHelper.sol b/src/libraries/TokenHelper.sol index f547a4f1..15651889 100644 --- a/src/libraries/TokenHelper.sol +++ b/src/libraries/TokenHelper.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.10; -import "openzeppelin/token/ERC20/IERC20.sol"; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; -import "./AddressesHelper.sol"; +import {AddressesHelper} from "./AddressesHelper.sol"; /** * @title Liquidity Book Token Helper Library diff --git a/src/libraries/math/LiquidityConfigurations.sol b/src/libraries/math/LiquidityConfigurations.sol index 2f718806..8c5ed592 100644 --- a/src/libraries/math/LiquidityConfigurations.sol +++ b/src/libraries/math/LiquidityConfigurations.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.10; -import "./PackedUint128Math.sol"; -import "./Encoded.sol"; +import {PackedUint128Math} from "./PackedUint128Math.sol"; +import {Encoded} from "./Encoded.sol"; /** * @title Liquidity Book Liquidity Configurations Library diff --git a/src/libraries/math/PackedUint128Math.sol b/src/libraries/math/PackedUint128Math.sol index 0671439e..9e78fe1b 100644 --- a/src/libraries/math/PackedUint128Math.sol +++ b/src/libraries/math/PackedUint128Math.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.10; -import "../Constants.sol"; -import "./SafeCast.sol"; +import {Constants} from "../Constants.sol"; +import {SafeCast} from "./SafeCast.sol"; /** * @title Liquidity Book Packed Uint128 Math Library diff --git a/src/libraries/math/SampleMath.sol b/src/libraries/math/SampleMath.sol index d81a9756..383dbf63 100644 --- a/src/libraries/math/SampleMath.sol +++ b/src/libraries/math/SampleMath.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.10; -import "./Encoded.sol"; +import {Encoded} from "./Encoded.sol"; /** * @title Liquidity Book Sample Math Library diff --git a/src/libraries/math/TreeMath.sol b/src/libraries/math/TreeMath.sol index 52ccba7b..43c6f2b7 100644 --- a/src/libraries/math/TreeMath.sol +++ b/src/libraries/math/TreeMath.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.10; -import "./BitMath.sol"; +import {BitMath} from "./BitMath.sol"; /** * @title Liquidity Book Tree Math Library diff --git a/src/libraries/math/Uint128x128Math.sol b/src/libraries/math/Uint128x128Math.sol index f1bd5709..510005f6 100644 --- a/src/libraries/math/Uint128x128Math.sol +++ b/src/libraries/math/Uint128x128Math.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.10; -import "../Constants.sol"; -import "./BitMath.sol"; -import "./Uint256x256Math.sol"; +import {Constants} from "../Constants.sol"; +import {BitMath} from "./BitMath.sol"; +import {Uint256x256Math} from "./Uint256x256Math.sol"; /** * @title Liquidity Book Uint128x128 Math Library diff --git a/src/libraries/math/Uint256x256Math.sol b/src/libraries/math/Uint256x256Math.sol index 9284a560..7322b8b9 100644 --- a/src/libraries/math/Uint256x256Math.sol +++ b/src/libraries/math/Uint256x256Math.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.10; -import "./BitMath.sol"; +import {BitMath} from "./BitMath.sol"; /** * @title Liquidity Book Uint256x256 Math Library diff --git a/test/LBFactory.t.sol b/test/LBFactory.t.sol index 9d826952..626e04ab 100644 --- a/test/LBFactory.t.sol +++ b/test/LBFactory.t.sol @@ -4,6 +4,8 @@ pragma solidity 0.8.10; import "./helpers/TestHelper.sol"; +import "src/libraries/ImmutableClone.sol"; + /* * Test scenarios: * 1. Constructor @@ -30,17 +32,16 @@ contract LiquidityBinFactoryTest is TestHelper { event LBPairCreated( IERC20 indexed tokenX, IERC20 indexed tokenY, uint256 indexed binStep, ILBPair LBPair, uint256 pid ); - event FeeParametersSet( + + event StaticFeeParametersSet( address indexed sender, - ILBPair indexed LBPair, - uint256 binStep, - uint256 baseFactor, - uint256 filterPeriod, - uint256 decayPeriod, - uint256 reductionFactor, - uint256 variableFeeControl, - uint256 protocolShare, - uint256 maxVolatilityAccumulated + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulated ); event PresetSet( @@ -51,8 +52,7 @@ contract LiquidityBinFactoryTest is TestHelper { uint256 reductionFactor, uint256 variableFeeControl, uint256 protocolShare, - uint256 maxVolatilityAccumulated, - uint256 sampleLifetime + uint256 maxVolatilityAccumulated ); event LBPairIgnoredStateChanged(ILBPair indexed LBPair, bool ignored); @@ -77,15 +77,15 @@ contract LiquidityBinFactoryTest is TestHelper { } function test_constructor() public { - assertEq(factory.feeRecipient(), DEV); - assertEq(factory.flashLoanFee(), DEFAULT_FLASHLOAN_FEE); + assertEq(factory.getFeeRecipient(), DEV); + assertEq(factory.getFlashloanFee(), DEFAULT_FLASHLOAN_FEE); vm.expectEmit(true, true, true, true); emit FlashLoanFeeSet(0, DEFAULT_FLASHLOAN_FEE); new LBFactory(DEV, DEFAULT_FLASHLOAN_FEE); // Reverts if the flash loan fee is above the max fee - uint256 maxFee = factory.MAX_FEE(); + uint256 maxFee = factory.getMaxFee(); vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__FlashLoanFeeAboveMax.selector, maxFee + 1, maxFee)); new LBFactory(DEV, maxFee + 1); } @@ -97,7 +97,7 @@ contract LiquidityBinFactoryTest is TestHelper { vm.expectEmit(true, true, true, true); emit LBPairImplementationSet(pairImplementation, newImplementation); factory.setLBPairImplementation(address(newImplementation)); - assertEq(factory.LBPairImplementation(), address(newImplementation), "test_setLBPairImplementation:1"); + assertEq(factory.getLBPairImplementation(), address(newImplementation), "test_setLBPairImplementation:1"); } function test_reverts_SetLBPairImplementation() public { @@ -126,27 +126,29 @@ contract LiquidityBinFactoryTest is TestHelper { } function test_createLBPair() public { - address expectedPairAddress = Clones.predictDeterministicAddress( - address(pairImplementation), keccak256(abi.encode(usdc, usdt, DEFAULT_BIN_STEP, 1)), address(factory) + address expectedPairAddress = ImmutableClone.predictDeterministicAddress( + address(pairImplementation), + abi.encodePacked(usdt, usdc, DEFAULT_BIN_STEP), + keccak256(abi.encode(usdc, usdt, DEFAULT_BIN_STEP, 1)), + address(factory) ); // Check for the correct events vm.expectEmit(true, true, true, true); emit LBPairCreated(usdt, usdc, DEFAULT_BIN_STEP, ILBPair(expectedPairAddress), 0); - vm.expectEmit(true, true, true, true); - emit FeeParametersSet( - address(this), - ILBPair(expectedPairAddress), - DEFAULT_BIN_STEP, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED - ); + // TODO - Check if can get the event from the pair + // vm.expectEmit(true, true, true, true); + // emit StaticFeeParametersSet( + // address(factory), + // DEFAULT_BASE_FACTOR, + // DEFAULT_FILTER_PERIOD, + // DEFAULT_DECAY_PERIOD, + // DEFAULT_REDUCTION_FACTOR, + // DEFAULT_VARIABLE_FEE_CONTROL, + // DEFAULT_PROTOCOL_SHARE, + // DEFAULT_MAX_VOLATILITY_ACCUMULATED + // ); ILBPair pair = factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); @@ -250,8 +252,7 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME + DEFAULT_MAX_VOLATILITY_ACCUMULATED ); // Can't create the same pair twice (a revision should be created instead) @@ -269,27 +270,28 @@ contract LiquidityBinFactoryTest is TestHelper { pairImplementation = new LBPair(factory); factory.setLBPairImplementation(address(pairImplementation)); - address expectedPairAddress = Clones.predictDeterministicAddress( - address(pairImplementation), keccak256(abi.encode(usdc, usdt, DEFAULT_BIN_STEP, 2)), address(factory) + address expectedPairAddress = ImmutableClone.predictDeterministicAddress( + address(pairImplementation), + abi.encodePacked(usdt, usdc, DEFAULT_BIN_STEP), + keccak256(abi.encode(usdc, usdt, DEFAULT_BIN_STEP, 2)), + address(factory) ); // Check for the correct events vm.expectEmit(true, true, true, true); emit LBPairCreated(usdt, usdc, DEFAULT_BIN_STEP, ILBPair(expectedPairAddress), 1); - vm.expectEmit(true, true, true, true); - emit FeeParametersSet( - address(this), - ILBPair(expectedPairAddress), - DEFAULT_BIN_STEP, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED - ); + // vm.expectEmit(true, true, true, true, expectedPairAddress); + // emit StaticFeeParametersSet( + // address(factory), + // DEFAULT_BASE_FACTOR, + // DEFAULT_FILTER_PERIOD, + // DEFAULT_DECAY_PERIOD, + // DEFAULT_REDUCTION_FACTOR, + // DEFAULT_VARIABLE_FEE_CONTROL, + // DEFAULT_PROTOCOL_SHARE, + // DEFAULT_MAX_VOLATILITY_ACCUMULATED + // ); ILBPair revision = factory.createLBPairRevision(usdt, usdc, DEFAULT_BIN_STEP); @@ -401,22 +403,21 @@ contract LiquidityBinFactoryTest is TestHelper { factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 1, true); } - function testFuzz_setPreset( - uint16 binStep, + function todoTestFuzz_setPreset( + uint8 binStep, uint16 baseFactor, uint16 filterPeriod, uint16 decayPeriod, uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated, - uint16 sampleLifetime + uint24 maxVolatilityAccumulated ) public { - binStep = uint16(bound(binStep, factory.MIN_BIN_STEP(), factory.MAX_BIN_STEP())); + binStep = uint8(bound(binStep, factory.getMinBinStep(), factory.getMaxBinStep())); filterPeriod = uint16(bound(filterPeriod, 0, type(uint16).max - 1)); decayPeriod = uint16(bound(decayPeriod, filterPeriod + 1, type(uint16).max)); reductionFactor = uint16(bound(reductionFactor, 0, Constants.BASIS_POINT_MAX)); - protocolShare = uint16(bound(protocolShare, 0, factory.MAX_PROTOCOL_SHARE())); + protocolShare = uint16(bound(protocolShare, 0, factory.getMaxProtocolShare())); variableFeeControl = uint24(bound(variableFeeControl, 0, Constants.BASIS_POINT_MAX)); // TODO: maxVolatilityAccumulated should be bounded but that's quite hard to calculate @@ -428,7 +429,7 @@ contract LiquidityBinFactoryTest is TestHelper { totalFeesMax = baseFee + maxVariableFee; } - if (totalFeesMax > factory.MAX_FEE()) { + if (totalFeesMax > factory.getMaxFee()) { vm.expectRevert(); factory.setPreset( binStep, @@ -438,8 +439,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated, - sampleLifetime + maxVolatilityAccumulated ); } else { vm.expectEmit(true, true, true, true); @@ -451,8 +451,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated, - sampleLifetime + maxVolatilityAccumulated ); factory.setPreset( @@ -463,8 +462,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated, - sampleLifetime + maxVolatilityAccumulated ); // Bin step DEFAULT_BIN_STEP is already there @@ -489,7 +487,6 @@ contract LiquidityBinFactoryTest is TestHelper { uint256 reductionFactorView, , , - , ) = factory.getPreset(binStep); assertEq(baseFactorView, baseFactor); @@ -499,47 +496,38 @@ contract LiquidityBinFactoryTest is TestHelper { } { - ( - , - , - , - , - uint256 variableFeeControlView, - uint256 protocolShareView, - uint256 maxVolatilityAccumulatedView, - uint256 sampleLifetimeView - ) = factory.getPreset(binStep); + (,,,, uint256 variableFeeControlView, uint256 protocolShareView, uint256 maxVolatilityAccumulatedView) = + factory.getPreset(binStep); assertEq(variableFeeControlView, variableFeeControl); assertEq(protocolShareView, protocolShare); assertEq(maxVolatilityAccumulatedView, maxVolatilityAccumulated); - assertEq(sampleLifetimeView, sampleLifetime); } } } - function testFuzz_reverts_setPreset( - uint16 binStep, + // TODO - check after refactoring the checks on fee parameters + function todoTestFuzz_reverts_setPreset( + uint8 binStep, uint16 baseFactor, uint16 filterPeriod, uint16 decayPeriod, uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated, - uint16 sampleLifetime + uint24 maxVolatilityAccumulated ) public { uint256 baseFee = (uint256(baseFactor) * binStep) * 1e10; uint256 prod = uint256(maxVolatilityAccumulated) * binStep; uint256 maxVariableFee = (prod * prod * variableFeeControl) / 100; - if (binStep < factory.MIN_BIN_STEP() || binStep > factory.MAX_BIN_STEP()) { + if (binStep < factory.getMinBinStep() || binStep > factory.getMaxBinStep()) { vm.expectRevert( abi.encodeWithSelector( ILBFactory.LBFactory__BinStepRequirementsBreached.selector, - factory.MIN_BIN_STEP(), + factory.getMinBinStep(), binStep, - factory.MAX_BIN_STEP() + factory.getMaxBinStep() ) ); factory.setPreset( @@ -550,8 +538,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated, - sampleLifetime + maxVolatilityAccumulated ); } else if (filterPeriod >= decayPeriod) { vm.expectRevert( @@ -565,8 +552,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated, - sampleLifetime + maxVolatilityAccumulated ); } else if (reductionFactor > Constants.BASIS_POINT_MAX) { vm.expectRevert( @@ -582,13 +568,12 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated, - sampleLifetime + maxVolatilityAccumulated ); - } else if (protocolShare > factory.MAX_PROTOCOL_SHARE()) { + } else if (protocolShare > factory.getMaxProtocolShare()) { vm.expectRevert( abi.encodeWithSelector( - ILBFactory.LBFactory__ProtocolShareOverflows.selector, protocolShare, factory.MAX_PROTOCOL_SHARE() + ILBFactory.LBFactory__ProtocolShareOverflows.selector, protocolShare, factory.getMaxProtocolShare() ) ); factory.setPreset( @@ -599,10 +584,9 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated, - sampleLifetime + maxVolatilityAccumulated ); - } else if (baseFee + maxVariableFee > factory.MAX_FEE()) { + } else if (baseFee + maxVariableFee > factory.getMaxFee()) { vm.expectRevert(); factory.setPreset( binStep, @@ -612,8 +596,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated, - sampleLifetime + maxVolatilityAccumulated ); } else { factory.setPreset( @@ -624,8 +607,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated, - sampleLifetime + maxVolatilityAccumulated ); } } @@ -639,8 +621,7 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME + DEFAULT_MAX_VOLATILITY_ACCUMULATED ); factory.setPreset( @@ -651,8 +632,7 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME + DEFAULT_MAX_VOLATILITY_ACCUMULATED ); assertEq(factory.getAllBinSteps().length, 3); @@ -686,15 +666,13 @@ contract LiquidityBinFactoryTest is TestHelper { uint16 newProtocolShare = DEFAULT_PROTOCOL_SHARE * 2; uint24 newMaxVolatilityAccumulated = DEFAULT_MAX_VOLATILITY_ACCUMULATED * 2; - ILBPair pair = factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); // FeeHelper.FeeParameters memory oldFeeParameters = pair.feeParameters(); vm.expectEmit(true, true, true, true); - emit FeeParametersSet( - address(this), - pair, - DEFAULT_BIN_STEP, + emit StaticFeeParametersSet( + address(factory), newBaseFactor, newFilterPeriod, newDecayPeriod, @@ -775,7 +753,7 @@ contract LiquidityBinFactoryTest is TestHelper { emit FeeRecipientSet(address(this), ALICE); factory.setFeeRecipient(ALICE); - assertEq(factory.feeRecipient(), ALICE); + assertEq(factory.getFeeRecipient(), ALICE); // Can't set if not the owner vm.prank(BOB); @@ -797,7 +775,7 @@ contract LiquidityBinFactoryTest is TestHelper { emit FlashLoanFeeSet(DEFAULT_FLASHLOAN_FEE, newFlashLoanFee); factory.setFlashLoanFee(newFlashLoanFee); - assertEq(factory.flashLoanFee(), newFlashLoanFee); + assertEq(factory.getFlashloanFee(), newFlashLoanFee); // Can't set if not the owner vm.prank(ALICE); @@ -809,7 +787,7 @@ contract LiquidityBinFactoryTest is TestHelper { factory.setFlashLoanFee(newFlashLoanFee); // Can't set to a fee greater than the maximum - uint256 maxFlashLoanFee = factory.MAX_FEE(); + uint256 maxFlashLoanFee = factory.getMaxFee(); vm.expectRevert( abi.encodeWithSelector( ILBFactory.LBFactory__FlashLoanFeeAboveMax.selector, maxFlashLoanFee + 1, maxFlashLoanFee @@ -819,16 +797,16 @@ contract LiquidityBinFactoryTest is TestHelper { } function test_setFactoryLockedState() public { - assertEq(factory.creationUnlocked(), false); + assertEq(factory.isCreationUnlocked(), false); vm.expectEmit(true, true, true, true); emit FactoryLockedStatusUpdated(false); factory.setFactoryLockedState(false); - assertEq(factory.creationUnlocked(), true); + assertEq(factory.isCreationUnlocked(), true); factory.setFactoryLockedState(true); - assertEq(factory.creationUnlocked(), false); + assertEq(factory.isCreationUnlocked(), false); // Can't set if not the owner vm.prank(ALICE); @@ -914,8 +892,7 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME + DEFAULT_MAX_VOLATILITY_ACCUMULATED ); factory.setPreset( @@ -926,8 +903,7 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME + DEFAULT_MAX_VOLATILITY_ACCUMULATED ); ILBPair pair1 = factory.createLBPair(weth, usdc, ID_ONE, 5); @@ -957,6 +933,6 @@ contract LiquidityBinFactoryTest is TestHelper { assertEq(address(pair3Info.LBPair), address(pair3)); assertEq(pair3Info.binStep, 5); assertEq(pair3Info.revisionIndex, 2); - assertEq(pair3Info.implementation, address(factory.LBPairImplementation())); + assertEq(pair3Info.implementation, address(factory.getLBPairImplementation())); } } diff --git a/test/helpers/TestHelper.sol b/test/helpers/TestHelper.sol index 4a023dee..2e25d04f 100644 --- a/test/helpers/TestHelper.sol +++ b/test/helpers/TestHelper.sol @@ -46,7 +46,6 @@ abstract contract TestHelper is Test { uint24 internal constant DEFAULT_VARIABLE_FEE_CONTROL = 40_000; uint16 internal constant DEFAULT_PROTOCOL_SHARE = 1_000; uint24 internal constant DEFAULT_MAX_VOLATILITY_ACCUMULATED = 350_000; - uint16 internal constant DEFAULT_SAMPLE_LIFETIME = 120; uint256 internal constant DEFAULT_FLASHLOAN_FEE = 8e14; address payable immutable DEV = payable(address(this)); @@ -195,7 +194,7 @@ abstract contract TestHelper is Test { if (address(taxToken) != address(0)) factory.addQuoteAsset(taxToken); } - function setDefaultFactoryPresets(uint16 binStep) internal { + function setDefaultFactoryPresets(uint8 binStep) internal { factory.setPreset( binStep, DEFAULT_BASE_FACTOR, @@ -204,8 +203,7 @@ abstract contract TestHelper is Test { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME + DEFAULT_MAX_VOLATILITY_ACCUMULATED ); } @@ -217,7 +215,7 @@ abstract contract TestHelper is Test { newPair = createLBPairFromStartIdAndBinStep(tokenX, tokenY, startId, DEFAULT_BIN_STEP); } - function createLBPairFromStartIdAndBinStep(IERC20 tokenX, IERC20 tokenY, uint24 startId, uint16 binStep) + function createLBPairFromStartIdAndBinStep(IERC20 tokenX, IERC20 tokenY, uint24 startId, uint8 binStep) internal returns (LBPair newPair) { diff --git a/test/integration/LBQuoter.t.sol b/test/integration/LBQuoter.t.sol index a9ce68f0..b79fd8aa 100644 --- a/test/integration/LBQuoter.t.sol +++ b/test/integration/LBQuoter.t.sol @@ -88,9 +88,10 @@ contract LiquidityBinQuoterTest is TestHelper { } function test_Constructor() public { - assertEq(address(quoter.routerV2()), address(router)); - assertEq(address(quoter.factoryV1()), AvalancheAddresses.JOE_V1_FACTORY); - assertEq(address(quoter.factoryV2()), address(factory)); + assertEq(address(quoter.getRouterV2()), address(router)); + assertEq(address(quoter.getFactoryV1()), AvalancheAddresses.JOE_V1_FACTORY); + assertEq(address(quoter.getLegacyFactoryV2()), AvalancheAddresses.JOE_V2_FACTORY); + assertEq(address(quoter.getFactoryV2()), address(factory)); } function test_InvalidLength() public { From ab6403823dbf0105cb8cea412a362add196d6925 Mon Sep 17 00:00:00 2001 From: Mathieu <85969303+Mathieu-Be@users.noreply.github.com> Date: Wed, 1 Feb 2023 17:02:35 +0100 Subject: [PATCH 11/47] Add Quoter tests (#76) * cleanup * fix tests * fix max bin step * change to lbPair casing * fix pair getSwapOut * add quoter tests --- src/LBPair.sol | 7 +- src/LBQuoter.sol | 249 +++++++++++++++++----------- src/LBRouter.sol | 71 ++++---- src/interfaces/ILBLegacyFactory.sol | 16 +- src/interfaces/ILBLegacyPair.sol | 2 + src/interfaces/ILBLegacyRouter.sol | 33 +--- src/libraries/BinHelper.sol | 6 +- test/helpers/TestHelper.sol | 3 +- test/integration/LBQuoter.t.sol | 188 +++++++++++++++++++-- 9 files changed, 381 insertions(+), 194 deletions(-) diff --git a/src/LBPair.sol b/src/LBPair.sol index cc02d926..e24e6c44 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -378,7 +378,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { override returns (uint128 amountInLeft, uint128 amountOut, uint128 fee) { - bytes32 amountsInLeft = amountIn.encode(!swapForY); + bytes32 amountsInLeft = amountIn.encode(swapForY); bytes32 parameters = _parameters; uint8 binStep = _binStep(); @@ -397,9 +397,10 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { if (amountsInToBin > 0) { amountsInLeft = amountsInLeft.sub(amountsInToBin.add(totalFees)); - amountOut += amountsOutOfBin.decode(swapForY); - fee += totalFees.decode(!swapForY); + amountOut += amountsOutOfBin.decode(!swapForY); + + fee += totalFees.decode(swapForY); } } diff --git a/src/LBQuoter.sol b/src/LBQuoter.sol index 0bf6d6b5..eaf1752d 100644 --- a/src/LBQuoter.sol +++ b/src/LBQuoter.sol @@ -12,6 +12,7 @@ import {Uint256x256Math} from "./libraries/math/Uint256x256Math.sol"; import {IJoeFactory} from "./interfaces/IJoeFactory.sol"; import {ILBFactory} from "./interfaces/ILBFactory.sol"; import {ILBLegacyFactory} from "./interfaces/ILBLegacyFactory.sol"; +import {ILBLegacyRouter} from "./interfaces/ILBLegacyRouter.sol"; import {IJoePair} from "./interfaces/IJoePair.sol"; import {ILBLegacyPair} from "./interfaces/ILBLegacyPair.sol"; import {ILBPair} from "./interfaces/ILBPair.sol"; @@ -26,6 +27,8 @@ contract LBQuoter { error LBQuoter_InvalidLength(); /// @notice Dex V2 router address + address private immutable _legacyRouterV2; + /// @notice Dex V2.1 router address address private immutable _routerV2; /// @notice Dex V1 factory address address private immutable _factoryV1; @@ -48,12 +51,20 @@ contract LBQuoter { /// @param routerV2 Dex V2 router address /// @param factoryV1 Dex V1 factory address /// @param legacyFactoryV2 Dex V2 factory address + /// @param legacyRouterV2 Dex V2 router address /// @param factoryV2 Dex V2.1 factory address - constructor(address factoryV1, address legacyFactoryV2, address factoryV2, address routerV2) { + constructor( + address factoryV1, + address legacyFactoryV2, + address factoryV2, + address legacyRouterV2, + address routerV2 + ) { _factoryV1 = factoryV1; _legacyFactoryV2 = legacyFactoryV2; _factoryV2 = factoryV2; _routerV2 = routerV2; + _legacyRouterV2 = legacyRouterV2; } /// @notice Returns the Dex V1 factory address @@ -74,6 +85,12 @@ contract LBQuoter { factoryV2 = _factoryV2; } + /// @notice Returns the Dex V2 router address + /// @return legacyRouterV2 Dex V2 router address + function getLEgacyRouteractoryV2() public view returns (address legacyRouterV2) { + legacyRouterV2 = _legacyRouterV2; + } + /// @notice Returns the Dex V2 router address /// @return routerV2 Dex V2 router address function getRouterV2() public view returns (address routerV2) { @@ -122,61 +139,69 @@ contract LBQuoter { } } - // Fetch swaps for V2 - ILBFactory.LBPairInformation[] memory LBPairsAvailable; - - for (uint256 k = 0; k < 2; k++) { - if (k == 0) { - LBPairsAvailable = ILBFactory(_factoryV2).getAllLBPairs(IERC20(route[i]), IERC20(route[i + 1])); - } else { - ILBLegacyFactory.LBPairInformation[] memory LBPairsAvailableLegacy = - ILBLegacyFactory(_legacyFactoryV2).getAllLBPairs(IERC20(route[i]), IERC20(route[i + 1])); - - LBPairsAvailable = new ILBFactory.LBPairInformation[](LBPairsAvailableLegacy.length); - for (uint256 l = 0; l < LBPairsAvailableLegacy.length; l++) { - LBPairsAvailable[l] = ILBFactory.LBPairInformation( - uint8(LBPairsAvailableLegacy[l].binStep), - ILBPair(address(LBPairsAvailableLegacy[l].LBPair)), - LBPairsAvailableLegacy[l].createdByOwner, - LBPairsAvailableLegacy[l].ignoredForRouting, - 0, - address(0) - ); + // Fetch swap for V2 + ILBLegacyFactory.LBPairInformation[] memory legacyLBPairsAvailable = + ILBLegacyFactory(_legacyFactoryV2).getAllLBPairs(IERC20(route[i]), IERC20(route[i + 1])); + + if (legacyLBPairsAvailable.length > 0 && quote.amounts[i] > 0) { + for (uint256 j; j < legacyLBPairsAvailable.length; j++) { + if (!legacyLBPairsAvailable[j].ignoredForRouting) { + bool swapForY = address(legacyLBPairsAvailable[j].LBPair.tokenY()) == route[i + 1]; + try ILBLegacyRouter(_legacyRouterV2).getSwapOut( + legacyLBPairsAvailable[j].LBPair, quote.amounts[i], swapForY + ) returns (uint256 swapAmountOut, uint256 fees) { + if (swapAmountOut > quote.amounts[i + 1]) { + quote.amounts[i + 1] = uint128(swapAmountOut); + quote.pairs[i] = address(legacyLBPairsAvailable[j].LBPair); + quote.binSteps[i] = legacyLBPairsAvailable[j].binStep; + + // Getting current price + (,, uint256 activeId) = legacyLBPairsAvailable[j].LBPair.getReservesAndId(); + quote.virtualAmountsWithoutSlippage[i + 1] = _getV2Quote( + quote.virtualAmountsWithoutSlippage[i] - fees, + uint24(activeId), + quote.binSteps[i], + swapForY + ); + + quote.fees[i] = uint128((fees * 1e18) / quote.amounts[i]); // fee percentage in amountIn + } + } catch {} } } + } - if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { - for (uint256 j; j < LBPairsAvailable.length; j++) { - if (!LBPairsAvailable[j].ignoredForRouting) { - bool swapForY = address( - k == 0 - ? LBPairsAvailable[j].LBPair.getTokenY() - : ILBLegacyPair(address(LBPairsAvailable[j].LBPair)).tokenY() - ) == route[i + 1]; - - try ILBRouter(_routerV2).getSwapOut(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) - returns (uint128, uint128 swapAmountOut, uint128 fees) { - if (swapAmountOut > quote.amounts[i + 1]) { - quote.amounts[i + 1] = swapAmountOut; - quote.pairs[i] = address(LBPairsAvailable[j].LBPair); - quote.binSteps[i] = uint8(LBPairsAvailable[j].binStep); - quote.revisions[i] = LBPairsAvailable[j].revisionIndex; - - // Getting current price - uint24 activeId = LBPairsAvailable[j].LBPair.getActiveId(); - quote.virtualAmountsWithoutSlippage[i + 1] = uint128( - _getV2Quote( - quote.virtualAmountsWithoutSlippage[i] - fees, - activeId, - quote.binSteps[i], - swapForY - ) - ); - - quote.fees[i] = (fees * 1e18) / quote.amounts[i]; // fee percentage in amountIn - } - } catch {} - } + // Fetch swaps for V2.1 + ILBFactory.LBPairInformation[] memory LBPairsAvailable = + ILBFactory(_factoryV2).getAllLBPairs(IERC20(route[i]), IERC20(route[i + 1])); + + if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { + for (uint256 j; j < LBPairsAvailable.length; j++) { + if (!LBPairsAvailable[j].ignoredForRouting) { + bool swapForY = address(LBPairsAvailable[j].LBPair.getTokenY()) == route[i + 1]; + + try ILBRouter(_routerV2).getSwapOut(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) + returns (uint128 amountInLeft, uint128 swapAmountOut, uint128 fees) { + if (amountInLeft == 0 && swapAmountOut > quote.amounts[i + 1]) { + quote.amounts[i + 1] = swapAmountOut; + quote.pairs[i] = address(LBPairsAvailable[j].LBPair); + quote.binSteps[i] = uint8(LBPairsAvailable[j].binStep); + quote.revisions[i] = LBPairsAvailable[j].revisionIndex; + + // Getting current price + uint24 activeId = LBPairsAvailable[j].LBPair.getActiveId(); + quote.virtualAmountsWithoutSlippage[i + 1] = uint128( + _getV2Quote( + quote.virtualAmountsWithoutSlippage[i] - fees, + activeId, + quote.binSteps[i], + swapForY + ) + ); + + quote.fees[i] = (fees * 1e18) / quote.amounts[i]; // fee percentage in amountIn + } + } catch {} } } } @@ -225,50 +250,72 @@ contract LBQuoter { } // Fetch swaps for V2 - ILBFactory.LBPairInformation[] memory LBPairsAvailable; - - for (uint256 k = 0; k < 2; k++) { - if (k == 0) { - LBPairsAvailable = ILBFactory(_factoryV2).getAllLBPairs(IERC20(route[i - 1]), IERC20(route[i])); - } else { - LBPairsAvailable = - ILBFactory(_legacyFactoryV2).getAllLBPairs(IERC20(route[i - 1]), IERC20(route[i])); + ILBLegacyFactory.LBPairInformation[] memory legacyLBPairsAvailable = + ILBLegacyFactory(_legacyFactoryV2).getAllLBPairs(IERC20(route[i - 1]), IERC20(route[i])); + + if (legacyLBPairsAvailable.length > 0 && quote.amounts[i] > 0) { + for (uint256 j; j < legacyLBPairsAvailable.length; j++) { + if (!legacyLBPairsAvailable[j].ignoredForRouting) { + bool swapForY = address(legacyLBPairsAvailable[j].LBPair.tokenY()) == route[i]; + try ILBLegacyRouter(_legacyRouterV2).getSwapIn( + legacyLBPairsAvailable[j].LBPair, quote.amounts[i], swapForY + ) returns (uint256 swapAmountIn, uint256 fees) { + if (swapAmountIn != 0 && (swapAmountIn < quote.amounts[i - 1] || quote.amounts[i - 1] == 0)) + { + quote.amounts[i - 1] = uint128(swapAmountIn); + quote.pairs[i - 1] = address(legacyLBPairsAvailable[j].LBPair); + quote.binSteps[i - 1] = legacyLBPairsAvailable[j].binStep; + + // Getting current price + (,, uint256 activeId) = legacyLBPairsAvailable[j].LBPair.getReservesAndId(); + quote.virtualAmountsWithoutSlippage[i - 1] = _getV2Quote( + quote.virtualAmountsWithoutSlippage[i], + uint24(activeId), + quote.binSteps[i - 1], + !swapForY + ) + uint128(fees); + + quote.fees[i - 1] = uint128((fees * 1e18) / quote.amounts[i - 1]); // fee percentage in amountIn + } + } catch {} + } } + } + + // Fetch swaps for V2.1 + ILBFactory.LBPairInformation[] memory LBPairsAvailable; - if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { - for (uint256 j; j < LBPairsAvailable.length; j++) { - if (!LBPairsAvailable[j].ignoredForRouting) { - bool swapForY = address( - k == 0 - ? LBPairsAvailable[j].LBPair.getTokenY() - : ILBLegacyPair(address(LBPairsAvailable[j].LBPair)).tokenY() - ) == route[i]; - try ILBRouter(_routerV2).getSwapIn(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) - returns (uint128 swapAmountIn, uint128, uint128 fees) { - if ( - swapAmountIn != 0 - && (swapAmountIn < quote.amounts[i - 1] || quote.amounts[i - 1] == 0) - ) { - quote.amounts[i - 1] = swapAmountIn; - quote.pairs[i - 1] = address(LBPairsAvailable[j].LBPair); - quote.binSteps[i - 1] = uint8(LBPairsAvailable[j].binStep); - quote.revisions[i - 1] = LBPairsAvailable[j].revisionIndex; - - // Getting current price - uint24 activeId = LBPairsAvailable[j].LBPair.getActiveId(); - quote.virtualAmountsWithoutSlippage[i - 1] = uint128( - _getV2Quote( - quote.virtualAmountsWithoutSlippage[i], - activeId, - quote.binSteps[i - 1], - !swapForY - ) - ) + fees; - - quote.fees[i - 1] = (fees * 1e18) / quote.amounts[i - 1]; // fee percentage in amountIn - } - } catch {} - } + LBPairsAvailable = ILBFactory(_factoryV2).getAllLBPairs(IERC20(route[i - 1]), IERC20(route[i])); + + if (LBPairsAvailable.length > 0 && quote.amounts[i] > 0) { + for (uint256 j; j < LBPairsAvailable.length; j++) { + if (!LBPairsAvailable[j].ignoredForRouting) { + bool swapForY = address(LBPairsAvailable[j].LBPair.getTokenY()) == route[i]; + try ILBRouter(_routerV2).getSwapIn(LBPairsAvailable[j].LBPair, quote.amounts[i], swapForY) + returns (uint128 swapAmountIn, uint128 amountOutLeft, uint128 fees) { + if ( + amountOutLeft == 0 && swapAmountIn != 0 + && (swapAmountIn < quote.amounts[i - 1] || quote.amounts[i - 1] == 0) + ) { + quote.amounts[i - 1] = swapAmountIn; + quote.pairs[i - 1] = address(LBPairsAvailable[j].LBPair); + quote.binSteps[i - 1] = uint8(LBPairsAvailable[j].binStep); + quote.revisions[i - 1] = LBPairsAvailable[j].revisionIndex; + + // Getting current price + uint24 activeId = LBPairsAvailable[j].LBPair.getActiveId(); + quote.virtualAmountsWithoutSlippage[i - 1] = uint128( + _getV2Quote( + quote.virtualAmountsWithoutSlippage[i], + activeId, + quote.binSteps[i - 1], + !swapForY + ) + ) + fees; + + quote.fees[i - 1] = (fees * 1e18) / quote.amounts[i - 1]; // fee percentage in amountIn + } + } catch {} } } } @@ -301,14 +348,16 @@ contract LBQuoter { function _getV2Quote(uint256 amount, uint24 activeId, uint256 binStep, bool swapForY) internal pure - returns (uint256 quote) + returns (uint128 quote) { if (swapForY) { - quote = - PriceHelper.getPriceFromId(activeId, uint8(binStep)).mulShiftRoundDown(amount, Constants.SCALE_OFFSET); + quote = uint128( + PriceHelper.getPriceFromId(activeId, uint8(binStep)).mulShiftRoundDown(amount, Constants.SCALE_OFFSET) + ); } else { - quote = - amount.shiftDivRoundDown(Constants.SCALE_OFFSET, PriceHelper.getPriceFromId(activeId, uint8(binStep))); + quote = uint128( + amount.shiftDivRoundDown(Constants.SCALE_OFFSET, PriceHelper.getPriceFromId(activeId, uint8(binStep))) + ); } } } diff --git a/src/LBRouter.sol b/src/LBRouter.sol index 3e961908..052b3fe1 100644 --- a/src/LBRouter.sol +++ b/src/LBRouter.sol @@ -188,20 +188,20 @@ contract LBRouter is ILBRouter { override returns (bytes32[] memory depositIds, uint256[] memory liquidityMinted) { - ILBPair lbPair = _getLBPairInformation( + ILBPair _LBPair = _getLBPairInformation( liquidityParameters.tokenX, liquidityParameters.tokenY, liquidityParameters.binStep, liquidityParameters.revision ); - if (liquidityParameters.tokenX != lbPair.getTokenX()) revert LBRouter__WrongTokenOrder(); + if (liquidityParameters.tokenX != _LBPair.getTokenX()) revert LBRouter__WrongTokenOrder(); if (liquidityParameters.tokenX == _wavax && liquidityParameters.amountX == msg.value) { - _wavaxDepositAndTransfer(address(lbPair), msg.value); - liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(lbPair), liquidityParameters.amountY); + _wavaxDepositAndTransfer(address(_LBPair), msg.value); + liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(_LBPair), liquidityParameters.amountY); } else if (liquidityParameters.tokenY == _wavax && liquidityParameters.amountY == msg.value) { - liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(lbPair), liquidityParameters.amountX); - _wavaxDepositAndTransfer(address(lbPair), msg.value); + liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(_LBPair), liquidityParameters.amountX); + _wavaxDepositAndTransfer(address(_LBPair), msg.value); } else { revert LBRouter__WrongAvaxLiquidityParameters( address(liquidityParameters.tokenX), @@ -212,7 +212,7 @@ contract LBRouter is ILBRouter { ); } - (depositIds, liquidityMinted) = _addLiquidity(liquidityParameters, lbPair); + (depositIds, liquidityMinted) = _addLiquidity(liquidityParameters, _LBPair); } /// @notice Remove liquidity while performing safety checks @@ -223,7 +223,7 @@ contract LBRouter is ILBRouter { /// @param amountXMin The min amount to receive of token X /// @param amountYMin The min amount to receive of token Y /// @param ids The list of ids to burn - /// @param amounts The list of amounts to burn of each id in `ids` + /// @param amounts The list of amounts to burn of each id in `_ids` /// @param to The address of the recipient /// @param deadline The deadline of the tx /// @return amountX Amount of token X returned @@ -240,12 +240,12 @@ contract LBRouter is ILBRouter { address to, uint256 deadline ) external override ensure(deadline) returns (uint256 amountX, uint256 amountY) { - ILBPair lbPair = _getLBPairInformation(tokenX, tokenY, binStep, revision); - bool isWrongOrder = tokenX != lbPair.getTokenX(); + ILBPair _LBPair = _getLBPairInformation(tokenX, tokenY, binStep, revision); + bool isWrongOrder = tokenX != _LBPair.getTokenX(); if (isWrongOrder) (amountXMin, amountYMin) = (amountYMin, amountXMin); - (amountX, amountY) = _removeLiquidity(lbPair, amountXMin, amountYMin, ids, amounts, to); + (amountX, amountY) = _removeLiquidity(_LBPair, amountXMin, amountYMin, ids, amounts, to); if (isWrongOrder) (amountX, amountY) = (amountY, amountX); } @@ -259,7 +259,7 @@ contract LBRouter is ILBRouter { /// @param amountTokenMin The min amount to receive of token /// @param amountAVAXMin The min amount to receive of AVAX /// @param ids The list of ids to burn - /// @param amounts The list of amounts to burn of each id in `ids` + /// @param amounts The list of amounts to burn of each id in `_ids` /// @param to The address of the recipient /// @param deadline The deadline of the tx /// @return amountToken Amount of token returned @@ -391,9 +391,9 @@ contract LBRouter is ILBRouter { path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountsIn[0]); - uint256 amountOutReal = _swapTokensForExactTokens(pairs, path.pairBinSteps, path.tokenPath, amountsIn, to); + uint256 _amountOutReal = _swapTokensForExactTokens(pairs, path.pairBinSteps, path.tokenPath, amountsIn, to); - if (amountOutReal < amountOut) revert LBRouter__InsufficientAmountOut(amountOut, amountOutReal); + if (_amountOutReal < amountOut) revert LBRouter__InsufficientAmountOut(amountOut, _amountOutReal); } } @@ -421,13 +421,13 @@ contract LBRouter is ILBRouter { path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountsIn[0]); - uint256 amountOutReal = + uint256 _amountOutReal = _swapTokensForExactTokens(pairs, path.pairBinSteps, path.tokenPath, amountsIn, address(this)); - if (amountOutReal < amountAVAXOut) revert LBRouter__InsufficientAmountOut(amountAVAXOut, amountOutReal); + if (_amountOutReal < amountAVAXOut) revert LBRouter__InsufficientAmountOut(amountAVAXOut, _amountOutReal); - _wavax.withdraw(amountOutReal); - _safeTransferAVAX(to, amountOutReal); + _wavax.withdraw(_amountOutReal); + _safeTransferAVAX(to, _amountOutReal); } /// @notice Swaps AVAX for exact tokens while performing safety checks @@ -594,17 +594,17 @@ contract LBRouter is ILBRouter { revert LBRouter__IdDesiredOverflows(liq.activeIdDesired, liq.idSlippage); } - uint256 activeId = pair.getActiveId(); - if (liq.activeIdDesired + liq.idSlippage < activeId || activeId + liq.idSlippage < liq.activeIdDesired) { - revert LBRouter__IdSlippageCaught(liq.activeIdDesired, liq.idSlippage, activeId); + uint256 _activeId = pair.getActiveId(); + if (liq.activeIdDesired + liq.idSlippage < _activeId || _activeId + liq.idSlippage < liq.activeIdDesired) { + revert LBRouter__IdSlippageCaught(liq.activeIdDesired, liq.idSlippage, _activeId); } liquidityConfigs = new bytes32[](liq.deltaIds.length); for (uint256 i; i < liquidityConfigs.length; ++i) { - int256 id = int256(activeId) + liq.deltaIds[i]; - if (id < 0 || uint256(id) > type(uint24).max) revert LBRouter__IdOverflows(id); + int256 _id = int256(_activeId) + liq.deltaIds[i]; + if (_id < 0 || uint256(_id) > type(uint24).max) revert LBRouter__IdOverflows(_id); liquidityConfigs[i] = LiquidityConfigurations.encodeParams( - uint64(liq.distributionX[i]), uint64(liq.distributionY[i]), uint24(uint256(id)) + uint64(liq.distributionX[i]), uint64(liq.distributionY[i]), uint24(uint256(_id)) ); } @@ -662,7 +662,7 @@ contract LBRouter is ILBRouter { /// @param amountXMin The min amount to receive of token X /// @param amountYMin The min amount to receive of token Y /// @param ids The list of ids to burn - /// @param amounts The list of amounts to burn of each id in `ids` + /// @param amounts The list of amounts to burn of each id in `_ids` /// @param to The address of the recipient /// @return amountX The amount of token X sent by the pair /// @return amountY The amount of token Y sent by the pair @@ -820,15 +820,15 @@ contract LBRouter is ILBRouter { recipient = i + 1 == pairs.length ? to : pairs[i + 1]; if (binStep == 0) { - (uint256 reserve0, uint256 reserve1,) = IJoePair(pair).getReserves(); + (uint256 _reserve0, uint256 _reserve1,) = IJoePair(pair).getReserves(); if (token < tokenNext) { - uint256 amountIn = token.balanceOf(pair) - reserve0; - uint256 amountOut = amountIn.getAmountOut(reserve0, reserve1); + uint256 amountIn = token.balanceOf(pair) - _reserve0; + uint256 amountOut = amountIn.getAmountOut(_reserve0, _reserve1); IJoePair(pair).swap(0, amountOut, recipient, ""); } else { - uint256 amountIn = token.balanceOf(pair) - reserve1; - uint256 amountOut = amountIn.getAmountOut(reserve1, reserve0); + uint256 amountIn = token.balanceOf(pair) - _reserve1; + uint256 amountOut = amountIn.getAmountOut(_reserve1, _reserve0); IJoePair(pair).swap(amountOut, 0, recipient, ""); } @@ -850,17 +850,12 @@ contract LBRouter is ILBRouter { view returns (ILBPair) { - ILBPair pair; - if (revision == 0) { - pair = _legacyFactory.getLBPairInformation(tokenX, tokenY, binStep).LBPair; - } else { - pair = _factory.getLBPairInformation(tokenX, tokenY, binStep, revision).LBPair; - } + ILBPair lbPair = _factory.getLBPairInformation(tokenX, tokenY, binStep, revision).LBPair; - if (address(pair) == address(0)) { + if (address(lbPair) == address(0)) { revert LBRouter__PairNotCreated(address(tokenX), address(tokenY), binStep); } - return pair; + return lbPair; } /// @notice Helper function to return the address of the pair (v1 or v2, according to `binStep`) diff --git a/src/interfaces/ILBLegacyFactory.sol b/src/interfaces/ILBLegacyFactory.sol index 096bf3af..0934a2c9 100644 --- a/src/interfaces/ILBLegacyFactory.sol +++ b/src/interfaces/ILBLegacyFactory.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; -import {ILBPair} from "./ILBPair.sol"; +import {ILBLegacyPair} from "./ILBLegacyPair.sol"; import {IPendingOwnable} from "./IPendingOwnable.sol"; /// @title Liquidity Book Factory Interface @@ -18,13 +18,13 @@ interface ILBLegacyFactory is IPendingOwnable { /// - ignoredForRouting: Whether the pair is ignored for routing or not. An ignored pair will not be explored during routes finding struct LBPairInformation { uint16 binStep; - ILBPair LBPair; + ILBLegacyPair LBPair; bool createdByOwner; bool ignoredForRouting; } event LBPairCreated( - IERC20 indexed tokenX, IERC20 indexed tokenY, uint256 indexed binStep, ILBPair LBPair, uint256 pid + IERC20 indexed tokenX, IERC20 indexed tokenY, uint256 indexed binStep, ILBLegacyPair LBPair, uint256 pid ); event FeeRecipientSet(address oldRecipient, address newRecipient); @@ -33,7 +33,7 @@ interface ILBLegacyFactory is IPendingOwnable { event FeeParametersSet( address indexed sender, - ILBPair indexed LBPair, + ILBLegacyPair indexed LBPair, uint256 binStep, uint256 baseFactor, uint256 filterPeriod, @@ -48,7 +48,7 @@ interface ILBLegacyFactory is IPendingOwnable { event LBPairImplementationSet(address oldLBPairImplementation, address LBPairImplementation); - event LBPairIgnoredStateChanged(ILBPair indexed LBPair, bool ignored); + event LBPairIgnoredStateChanged(ILBLegacyPair indexed LBPair, bool ignored); event PresetSet( uint256 indexed binStep, @@ -90,7 +90,7 @@ interface ILBLegacyFactory is IPendingOwnable { function creationUnlocked() external view returns (bool); - function allLBPairs(uint256 id) external returns (ILBPair); + function allLBPairs(uint256 id) external returns (ILBLegacyPair); function getNumberOfLBPairs() external view returns (uint256); @@ -124,7 +124,7 @@ interface ILBLegacyFactory is IPendingOwnable { function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) external - returns (ILBPair pair); + returns (ILBLegacyPair pair); function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) external; @@ -165,5 +165,5 @@ interface ILBLegacyFactory is IPendingOwnable { function removeQuoteAsset(IERC20 quoteAsset) external; - function forceDecay(ILBPair LBPair) external; + function forceDecay(ILBLegacyPair LBPair) external; } diff --git a/src/interfaces/ILBLegacyPair.sol b/src/interfaces/ILBLegacyPair.sol index c25179da..60d88147 100644 --- a/src/interfaces/ILBLegacyPair.sol +++ b/src/interfaces/ILBLegacyPair.sol @@ -11,4 +11,6 @@ interface ILBLegacyPair { function tokenX() external view returns (IERC20); function tokenY() external view returns (IERC20); + + function getReservesAndId() external view returns (uint256 reserveX, uint256 reserveY, uint256 activeId); } diff --git a/src/interfaces/ILBLegacyRouter.sol b/src/interfaces/ILBLegacyRouter.sol index caae7910..26e1e59d 100644 --- a/src/interfaces/ILBLegacyRouter.sol +++ b/src/interfaces/ILBLegacyRouter.sol @@ -6,7 +6,7 @@ import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {ILBFactory} from "./ILBFactory.sol"; import {IJoeFactory} from "./IJoeFactory.sol"; -import {ILBPair} from "./ILBPair.sol"; +import {ILBLegacyPair} from "./ILBLegacyPair.sol"; import {ILBToken} from "./ILBToken.sol"; import {IWAVAX} from "./IWAVAX.sol"; @@ -14,21 +14,6 @@ import {IWAVAX} from "./IWAVAX.sol"; /// @author Trader Joe /// @notice Required interface of LBRouter contract interface ILBLegacyRouter { - /// @dev The liquidity parameters, such as: - /// - tokenX: The address of token X - /// - tokenY: The address of token Y - /// - binStep: The bin step of the pair - /// - amountX: The amount to send of token X - /// - amountY: The amount to send of token Y - /// - amountXMin: The min amount of token X added to liquidity - /// - amountYMin: The min amount of token Y added to liquidity - /// - activeIdDesired: The active id that user wants to add liquidity from - /// - idSlippage: The number of id that are allowed to slip - /// - deltaIds: The list of delta ids to add liquidity (`deltaId = activeId - desiredId`) - /// - distributionX: The distribution of tokenX with sum(distributionX) = 100e18 (100%) or 0 (0%) - /// - distributionY: The distribution of tokenY with sum(distributionY) = 100e18 (100%) or 0 (0%) - /// - to: The address of the recipient - /// - deadline: The deadline of the tx struct LiquidityParameters { IERC20 tokenX; IERC20 tokenY; @@ -46,29 +31,19 @@ interface ILBLegacyRouter { uint256 deadline; } - function factory() external view returns (ILBFactory); - - function oldFactory() external view returns (IJoeFactory); - - function wavax() external view returns (IWAVAX); - - function getIdFromPrice(ILBPair LBPair, uint256 price) external view returns (uint24); - - function getPriceFromId(ILBPair LBPair, uint24 id) external view returns (uint256); - - function getSwapIn(ILBPair LBPair, uint256 amountOut, bool swapForY) + function getSwapIn(ILBLegacyPair lbPair, uint256 amountOut, bool swapForY) external view returns (uint256 amountIn, uint256 feesIn); - function getSwapOut(ILBPair LBPair, uint256 amountIn, bool swapForY) + function getSwapOut(ILBLegacyPair lbPair, uint256 amountIn, bool swapForY) external view returns (uint256 amountOut, uint256 feesIn); function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) external - returns (ILBPair pair); + returns (ILBLegacyPair pair); function addLiquidity(LiquidityParameters calldata liquidityParameters) external diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index ab1667d4..f9ad739a 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -182,7 +182,7 @@ library BinHelper { ) internal pure returns (bytes32 amountsInToBin, bytes32 amountsOutOfBin, bytes32 totalFees) { uint256 price = activeId.getPriceFromId(binStep); - uint128 binReserveOut = binReserves.decode(swapForY); + uint128 binReserveOut = binReserves.decode(!swapForY); uint128 maxAmountIn = swapForY ? uint256(binReserveOut).shiftDivRoundUp(Constants.SCALE_OFFSET, price).safe128() @@ -195,7 +195,7 @@ library BinHelper { uint128 amountIn128; uint128 amountOut128; - uint128 amountIn = amountsLeft.decode(!swapForY); + uint128 amountIn = amountsLeft.decode(swapForY); if (amountIn >= maxAmountIn + maxFee) { fee128 = maxFee; @@ -205,9 +205,9 @@ library BinHelper { } else { fee128 = amountIn.getFeeAmountFrom(totalFee); + amountIn -= fee128; amountIn128 = amountIn; - amountIn -= fee128; amountOut128 = swapForY ? uint256(amountIn).mulShiftRoundDown(price, Constants.SCALE_OFFSET).safe128() : uint256(amountIn).shiftDivRoundDown(Constants.SCALE_OFFSET, price).safe128(); diff --git a/test/helpers/TestHelper.sol b/test/helpers/TestHelper.sol index 2e25d04f..b9632c71 100644 --- a/test/helpers/TestHelper.sol +++ b/test/helpers/TestHelper.sol @@ -128,7 +128,8 @@ abstract contract TestHelper is Test { router = new LBRouter(factory, legacyFactoryV2, factoryV1, IWAVAX(address(wavax))); // Create quoter - quoter = new LBQuoter( address(factoryV1), address(legacyFactoryV2), address(factory),address(router)); + quoter = + new LBQuoter( address(factoryV1), address(legacyFactoryV2), address(factory), address(legacyRouterV2), address(router)); // Label deployed contracts vm.label(address(router), "router"); diff --git a/test/integration/LBQuoter.t.sol b/test/integration/LBQuoter.t.sol index b79fd8aa..c69b28a7 100644 --- a/test/integration/LBQuoter.t.sol +++ b/test/integration/LBQuoter.t.sol @@ -41,8 +41,8 @@ contract LiquidityBinQuoterTest is TestHelper { address(usdc), lowLiquidityAmount / 2, // 1 USDT = 2 USDC lowLiquidityAmount, - lowLiquidityAmount, - lowLiquidityAmount, + 0, + 0, address(this), block.timestamp + 1 ); @@ -72,12 +72,12 @@ contract LiquidityBinQuoterTest is TestHelper { vm.startPrank(AvalancheAddresses.V2_FACTORY_OWNER); legacyFactoryV2.addQuoteAsset(usdc); legacyFactoryV2.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); // 1 USDT = 1 USDC - legacyFactoryV2.createLBPair(wavax, usdc, ID_ONE - 50, DEFAULT_BIN_STEP); // 1 AVAX > 1 USDC + legacyFactoryV2.createLBPair(wavax, usdc, ID_ONE + 50, DEFAULT_BIN_STEP); // 1 AVAX > 1 USDC legacyFactoryV2.createLBPair(bnb, usdc, ID_ONE, DEFAULT_BIN_STEP); // 1 BNB = 1 USDC vm.stopPrank(); factory.createLBPair(weth, usdc, ID_ONE, DEFAULT_BIN_STEP); // 1 WETH = 1 USDC - factory.createLBPair(bnb, usdc, ID_ONE - 50, DEFAULT_BIN_STEP); // 1 BNB > 1 USDC + factory.createLBPair(bnb, usdc, ID_ONE + 50, DEFAULT_BIN_STEP); // 1 BNB > 1 USDC factory.setLBPairImplementation(address(new LBPair(factory))); factory.createLBPairRevision(weth, usdc, DEFAULT_BIN_STEP); // 1 WETH = 1 USDC @@ -85,6 +85,22 @@ contract LiquidityBinQuoterTest is TestHelper { ILBRouter.LiquidityParameters memory liquidityParameters = getLiquidityParameters(usdt, usdc, highLiquidityAmount, ID_ONE, 7, 0); legacyRouterV2.addLiquidity(liquidityParameters.toLegacy()); + + liquidityParameters = getLiquidityParameters(wavax, usdc, lowLiquidityAmount, ID_ONE + 50, 7, 0); + legacyRouterV2.addLiquidity(liquidityParameters.toLegacy()); + + liquidityParameters = getLiquidityParameters(weth, usdc, lowLiquidityAmount, ID_ONE, 7, 0); + router.addLiquidity(liquidityParameters); + + liquidityParameters = getLiquidityParameters(weth, usdc, highLiquidityAmount, ID_ONE, 7, 0); + liquidityParameters.revision = 2; + router.addLiquidity(liquidityParameters); + + liquidityParameters = getLiquidityParameters(bnb, usdc, highLiquidityAmount, ID_ONE, 7, 0); + legacyRouterV2.addLiquidity(liquidityParameters.toLegacy()); + + liquidityParameters = getLiquidityParameters(bnb, usdc, lowLiquidityAmount, ID_ONE + 50, 7, 0); + router.addLiquidity(liquidityParameters); } function test_Constructor() public { @@ -113,18 +129,166 @@ contract LiquidityBinQuoterTest is TestHelper { uint128 amountIn = 1e16; LBQuoter.Quote memory quote = quoter.findBestPathFromAmountIn(route, amountIn); - assertEq(quote.amounts[0], amountIn); - assertApproxEqRel(quote.amounts[1], amountIn * 2, 5e16); - assertEq(quote.binSteps[0], 0); - assertEq(quote.revisions[0], 0); + assertEq(quote.amounts[0], amountIn, "test_Scenario1::1"); + assertApproxEqRel(quote.amounts[1], amountIn * 2, 5e16, "test_Scenario1::2"); + assertEq(quote.binSteps[0], 0, "test_Scenario1::3"); + assertEq(quote.revisions[0], 0, "test_Scenario1::4"); + + // Large amountIn + amountIn = 100e18; + quote = quoter.findBestPathFromAmountIn(route, amountIn); + + assertEq(quote.amounts[0], amountIn, "test_Scenario1::5"); + assertApproxEqRel(quote.amounts[1], amountIn, 5e16, "test_Scenario1::6"); + assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario1::7"); + assertEq(quote.revisions[0], 0, "test_Scenario1::8"); + + // Small amountOut + uint128 amountOut = 1e16; + quote = quoter.findBestPathFromAmountOut(route, amountOut); + + assertApproxEqRel(quote.amounts[0], amountOut / 2, 5e16, "test_Scenario1::9"); + assertEq(quote.amounts[1], amountOut, "test_Scenario1::10"); + assertEq(quote.binSteps[0], 0, "test_Scenario1::11"); + assertEq(quote.revisions[0], 0, "test_Scenario1::12"); + + // Large amountOut + amountOut = 100e18; + quote = quoter.findBestPathFromAmountOut(route, amountOut); + + assertApproxEqRel(quote.amounts[0], amountOut, 5e16, "test_Scenario1::13"); + assertEq(quote.amounts[1], amountOut, "test_Scenario1::14"); + assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario1::15"); + assertEq(quote.revisions[0], 0, "test_Scenario1::16"); + } + + function test_Scenario2() public { + // WAVAX/USDC, V1 with high liquidity, V2 with low liquidity + address[] memory route = new address[](2); + route[0] = address(wavax); + route[1] = address(usdc); + + // Small amountIn + uint128 amountIn = 1e16; + LBQuoter.Quote memory quote = quoter.findBestPathFromAmountIn(route, amountIn); + + assertEq(quote.amounts[0], amountIn, "test_Scenario2::1"); + assertGt(quote.amounts[1], amountIn, "test_Scenario2::2"); + assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario2::3"); + assertEq(quote.revisions[0], 0, "test_Scenario2::4"); + + // Large amountIn + amountIn = 100e18; + quote = quoter.findBestPathFromAmountIn(route, amountIn); + + assertEq(quote.amounts[0], amountIn, "test_Scenario2::5"); + assertApproxEqRel(quote.amounts[1], amountIn, 5e16, "test_Scenario2::6"); + assertEq(quote.binSteps[0], 0, "test_Scenario2::7"); + assertEq(quote.revisions[0], 0, "test_Scenario2::8"); + + // Small amountOut + uint128 amountOut = 1e16; + quote = quoter.findBestPathFromAmountOut(route, amountOut); + + assertLt(quote.amounts[0], amountOut, "test_Scenario2::9"); + assertEq(quote.amounts[1], amountOut, "test_Scenario2::10"); + assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario2::11"); + assertEq(quote.revisions[0], 0, "test_Scenario2::12"); + + // Large amountOut + amountOut = 100e18; + quote = quoter.findBestPathFromAmountOut(route, amountOut); + + assertApproxEqRel(quote.amounts[0], amountOut, 5e16, "test_Scenario2::13"); + assertEq(quote.amounts[1], amountOut, "test_Scenario2::14"); + assertEq(quote.binSteps[0], 0, "test_Scenario2::15"); + assertEq(quote.revisions[0], 0, "test_Scenario2::16"); + } + + function test_Scenario3() public { + // WETH/USDC, V1 with low liquidity, V2.1.rev1 with low liquidity, V2.1.rev2 with high liquidity + address[] memory route = new address[](2); + route[0] = address(weth); + route[1] = address(usdc); + + // Small amountIn + uint128 amountIn = 1e16; + LBQuoter.Quote memory quote = quoter.findBestPathFromAmountIn(route, amountIn); + + assertEq(quote.amounts[0], amountIn, "test_Scenario3::1"); + assertApproxEqRel(quote.amounts[1], amountIn * 2, 5e16, "test_Scenario3::2"); + assertEq(quote.binSteps[0], 0, "test_Scenario3::3"); + assertEq(quote.revisions[0], 0, "test_Scenario3::4"); // Large amountIn amountIn = 100e18; quote = quoter.findBestPathFromAmountIn(route, amountIn); - // assertEq(quote.amounts[0], amountIn); - // assertApproxEqRel(quote.amounts[1], amountIn * 2, 5e16); - // assertEq(quote.binSteps[0], DEFAULT_BIN_STEP); - // assertEq(quote.revisions[0], 0); + assertEq(quote.amounts[0], amountIn, "test_Scenario3::5"); + assertApproxEqRel(quote.amounts[1], amountIn, 5e16, "test_Scenario3::6"); + assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario3::7"); + assertEq(quote.revisions[0], 2, "test_Scenario3::8"); + + // Small amountOut + uint128 amountOut = 1e16; + quote = quoter.findBestPathFromAmountOut(route, amountOut); + + assertApproxEqRel(quote.amounts[0], amountOut / 2, 5e16, "test_Scenario3::9"); + assertEq(quote.amounts[1], amountOut, "test_Scenario3::10"); + assertEq(quote.binSteps[0], 0, "test_Scenario3::11"); + assertEq(quote.revisions[0], 0, "test_Scenario3::12"); + + // Large amountOut + amountOut = 100e18; + quote = quoter.findBestPathFromAmountOut(route, amountOut); + + assertApproxEqRel(quote.amounts[0], amountOut, 5e16, "test_Scenario3::13"); + assertEq(quote.amounts[1], amountOut, "test_Scenario3::14"); + assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario3::15"); + assertEq(quote.revisions[0], 2, "test_Scenario3::16"); + } + + function test_Scenario4() public { + // BNB/USDC, V2 with high liquidity, V2.1 with low liquidity + + address[] memory route = new address[](2); + route[0] = address(bnb); + route[1] = address(usdc); + + // Small amountIn + uint128 amountIn = 1e16; + LBQuoter.Quote memory quote = quoter.findBestPathFromAmountIn(route, amountIn); + + assertEq(quote.amounts[0], amountIn, "test_Scenario4::1"); + assertGt(quote.amounts[1], amountIn, "test_Scenario4::2"); + assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario4::3"); + assertEq(quote.revisions[0], 1, "test_Scenario4::4"); + + // Large amountIn + amountIn = 100e18; + quote = quoter.findBestPathFromAmountIn(route, amountIn); + + assertEq(quote.amounts[0], amountIn, "test_Scenario4::5"); + assertApproxEqRel(quote.amounts[1], amountIn, 5e16, "test_Scenario4::6"); + assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario4::7"); + assertEq(quote.revisions[0], 0, "test_Scenario4::8"); + + // Small amountOut + uint128 amountOut = 1e16; + quote = quoter.findBestPathFromAmountOut(route, amountOut); + + assertLt(quote.amounts[0], amountOut, "test_Scenario4::9"); + assertEq(quote.amounts[1], amountOut, "test_Scenario4::10"); + assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario4::11"); + assertEq(quote.revisions[0], 1, "test_Scenario4::12"); + + // Large amountOut + amountOut = 100e18; + quote = quoter.findBestPathFromAmountOut(route, amountOut); + + assertApproxEqRel(quote.amounts[0], amountOut, 5e16, "test_Scenario4::13"); + assertEq(quote.amounts[1], amountOut, "test_Scenario4::14"); + assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario4::15"); + assertEq(quote.revisions[0], 0, "test_Scenario4::16"); } } From 4fced214f7af823218601031d4162291b74f7345 Mon Sep 17 00:00:00 2001 From: Louis <107303182+0x0Louis@users.noreply.github.com> Date: Wed, 1 Feb 2023 17:36:40 +0100 Subject: [PATCH 12/47] New tests and fixes (#77) * small fix on pair * rename address helper library * fee helper and test * packed uint128 math and test * address helper test * clean up for sample math * clean up token helper and test * clean reentrancy guard and test * test encoded * test pending ownable * clean up pair parameters and test * clean up price helper and test * clean up, fix bin helper and test * clean up, fix oracle helper and test * clean up and fix lb pair and token * move libs test * remove unused console.log * last fixes and clean up * Add missing natspec --- src/LBPair.sol | 28 +- src/LBToken.sol | 2 +- src/interfaces/ILBLegacyPair.sol | 2 +- src/interfaces/ILBPair.sol | 2 + ...{AddressesHelper.sol => AddressHelper.sol} | 12 +- src/libraries/BinHelper.sol | 51 ++- src/libraries/Constants.sol | 6 + src/libraries/FeeHelper.sol | 71 ++- src/libraries/OracleHelper.sol | 112 +++-- src/libraries/PairParameterHelper.sol | 92 ++-- src/libraries/PriceHelper.sol | 26 +- ...ardUpgradeable.sol => ReentrancyGuard.sol} | 8 +- src/libraries/TokenHelper.sol | 4 +- src/libraries/math/PackedUint128Math.sol | 11 +- src/libraries/math/SampleMath.sol | 1 - test/BinHelper.T.sol | 21 - test/PriceHelper.t.sol | 12 - test/libraries/AddressHelper.t.sol | 62 +++ test/libraries/BinHelper.t.sol | 355 +++++++++++++++ test/libraries/FeeHelper.t.sol | 66 +++ .../ImmutableClone.t.sol} | 4 +- test/libraries/OracleHelper.t.sol | 411 ++++++++++++++++++ test/libraries/PairParameterHelper.t.sol | 317 ++++++++++++++ test/libraries/PendingOwnable.t.sol | 121 ++++++ test/libraries/PriceHelper.t.sol | 104 +++++ test/libraries/ReentrancyGuard.t.sol | 41 ++ test/libraries/TokenHelper.t.sol | 69 +++ test/{ => libraries/math}/BitMath.t.sol | 2 +- test/libraries/math/Encoded.t.sol | 83 ++++ .../math}/LiquidityConfigurations.t.sol | 2 +- .../math}/PackedUint128Math.t.sol | 21 +- test/{ => libraries/math}/SafeCast.t.sol | 2 +- test/{ => libraries/math}/SampleMath.t.sol | 2 +- test/{ => libraries/math}/TreeMath.t.sol | 2 +- .../math}/Uint128x128Math.t.sol | 2 +- .../math}/Uint256x256Math.t.sol | 2 +- 36 files changed, 1931 insertions(+), 198 deletions(-) rename src/libraries/{AddressesHelper.sol => AddressHelper.sol} (88%) rename src/libraries/{ReentrancyGuardUpgradeable.sol => ReentrancyGuard.sol} (88%) delete mode 100644 test/BinHelper.T.sol delete mode 100644 test/PriceHelper.t.sol create mode 100644 test/libraries/AddressHelper.t.sol create mode 100644 test/libraries/BinHelper.t.sol create mode 100644 test/libraries/FeeHelper.t.sol rename test/{TestImmutableClone.sol => libraries/ImmutableClone.t.sol} (97%) create mode 100644 test/libraries/OracleHelper.t.sol create mode 100644 test/libraries/PairParameterHelper.t.sol create mode 100644 test/libraries/PendingOwnable.t.sol create mode 100644 test/libraries/PriceHelper.t.sol create mode 100644 test/libraries/ReentrancyGuard.t.sol create mode 100644 test/libraries/TokenHelper.t.sol rename test/{ => libraries/math}/BitMath.t.sol (97%) create mode 100644 test/libraries/math/Encoded.t.sol rename test/{ => libraries/math}/LiquidityConfigurations.t.sol (97%) rename test/{ => libraries/math}/PackedUint128Math.t.sol (91%) rename test/{ => libraries/math}/SafeCast.t.sol (99%) rename test/{ => libraries/math}/SampleMath.t.sol (99%) rename test/{ => libraries/math}/TreeMath.t.sol (98%) rename test/{ => libraries/math}/Uint128x128Math.t.sol (94%) rename test/{ => libraries/math}/Uint256x256Math.t.sol (99%) diff --git a/src/LBPair.sol b/src/LBPair.sol index e24e6c44..0144b3b4 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -17,12 +17,17 @@ import {OracleHelper} from "./libraries/OracleHelper.sol"; import {PackedUint128Math} from "./libraries/math/PackedUint128Math.sol"; import {PairParameterHelper} from "./libraries/PairParameterHelper.sol"; import {PriceHelper} from "./libraries/PriceHelper.sol"; -import {ReentrancyGuardUpgradeable} from "./libraries/ReentrancyGuardUpgradeable.sol"; +import {ReentrancyGuard} from "./libraries/ReentrancyGuard.sol"; import {SafeCast} from "./libraries/math/SafeCast.sol"; import {TreeMath} from "./libraries/math/TreeMath.sol"; import {Uint256x256Math} from "./libraries/math/Uint256x256Math.sol"; -contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { +/** + * @title Liquidity Book Pair + * @author Trader Joe + * @notice The Liquidity Book Pair contract is the core contract of the Liquidity Book protocol + */ +contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { using BinHelper for bytes32; using FeeHelper for uint128; using LiquidityConfigurations for bytes32; @@ -491,7 +496,7 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { if (amountsOut == 0) revert LBPair__InsufficientAmountOut(); - _oracle.update(parameters, activeId); + parameters = _oracle.update(parameters, activeId); _reserves = reserves.sub(amountsOut); _parameters = parameters.setActiveId(activeId); @@ -698,8 +703,19 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { * @param newLength The new length of the oracle */ function increaseOracleLength(uint16 newLength) external override { - uint16 oracleId = _parameters.getOracleId(); - _oracle.inreaseLength(oracleId, newLength); + bytes32 parameters = _parameters; + + uint16 oracleId = parameters.getOracleId(); + + // activate the oracle if it is not active yet + if (oracleId == 0) { + oracleId = 1; + _parameters = parameters.setOracleId(oracleId); + } + + _oracle.increaseLength(oracleId, newLength); + + emit OracleLengthIncreased(msg.sender, newLength); } /** @@ -888,8 +904,8 @@ contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair { _protocolFees = _protocolFees.add(protocolCFees); } + parameters = _oracle.update(parameters, id); _parameters = parameters; - _oracle.update(parameters, id); emit CompositionFees(msg.sender, id, fees, protocolCFees); } diff --git a/src/LBToken.sol b/src/LBToken.sol index 6816f0ca..bc7fe862 100644 --- a/src/LBToken.sol +++ b/src/LBToken.sol @@ -125,7 +125,7 @@ contract LBToken is ILBToken { } /** - * @notice Grants or revokes permission to `spender` to transfer the caller's tokens, according to `approved`. + * @notice Grants or revokes permission to `spender` to transfer the caller's lbTokens, according to `approved`. * @param spender The address of the spender. * @param approved The boolean value to grant or revoke permission. */ diff --git a/src/interfaces/ILBLegacyPair.sol b/src/interfaces/ILBLegacyPair.sol index 60d88147..622e7828 100644 --- a/src/interfaces/ILBLegacyPair.sol +++ b/src/interfaces/ILBLegacyPair.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.10; -import "openzeppelin/token/ERC20/IERC20.sol"; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; /// @title Liquidity Book Pair V2 Interface /// @author Trader Joe diff --git a/src/interfaces/ILBPair.sol b/src/interfaces/ILBPair.sol index 48c5bdb1..516db1f3 100644 --- a/src/interfaces/ILBPair.sol +++ b/src/interfaces/ILBPair.sol @@ -65,6 +65,8 @@ interface ILBPair is ILBToken { bytes32 protocolFees ); + event OracleLengthIncreased(address indexed sender, uint16 oracleLength); + event ForcedDecay(address indexed sender, uint24 idReference, uint24 volatilityReference); function initialize( diff --git a/src/libraries/AddressesHelper.sol b/src/libraries/AddressHelper.sol similarity index 88% rename from src/libraries/AddressesHelper.sol rename to src/libraries/AddressHelper.sol index fa8847c4..f2ec73ec 100644 --- a/src/libraries/AddressesHelper.sol +++ b/src/libraries/AddressHelper.sol @@ -3,14 +3,14 @@ pragma solidity 0.8.10; /** - * @title Liquidity Book Addresses Helper Library + * @title Liquidity Book Address Helper Library * @author Trader Joe * @notice This library contains functions to check if an address is a contract and * catch low level calls errors */ -library AddressesHelper { - error AddressesHelper__NonContract(); - error AddressesHelper__CallFailed(); +library AddressHelper { + error AddressHelper__NonContract(); + error AddressHelper__CallFailed(); /** * @notice Private view function to perform a low level call on `target` @@ -23,10 +23,10 @@ library AddressesHelper { (bool success, bytes memory returnData) = target.call(data); if (success) { - if (returnData.length == 0 && !isContract(target)) revert AddressesHelper__NonContract(); + if (returnData.length == 0 && !isContract(target)) revert AddressHelper__NonContract(); } else { if (returnData.length == 0) { - revert AddressesHelper__CallFailed(); + revert AddressHelper__CallFailed(); } else { // Look for revert reason and bubble it up if present assembly { diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index f9ad739a..fd151b8c 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -76,14 +76,28 @@ library BinHelper { { uint256 userLiquidity = getLiquidity(amountsIn, price); if (totalSupply == 0) return (userLiquidity, amountsIn); + if (userLiquidity == 0) return (0, 0); uint256 binLiquidity = getLiquidity(binReserves, price); + if (binLiquidity == 0) return (userLiquidity, amountsIn); shares = userLiquidity.mulDivRoundDown(totalSupply, binLiquidity); - uint256 effectiveLiquidity = shares.mulDivRoundDown(binLiquidity, totalSupply); + uint256 effectiveLiquidity = shares.mulDivRoundUp(binLiquidity, totalSupply); - uint256 ratioLiquidity = effectiveLiquidity.shiftDivRoundUp(Constants.SCALE_OFFSET, userLiquidity); - effectiveAmountsIn = amountsIn.scalarMulShift128RoundUp(ratioLiquidity.safe128()); + if (userLiquidity == effectiveLiquidity) return (shares, amountsIn); + + uint256 deltaLiquidity = userLiquidity - effectiveLiquidity; + + (uint128 amountX, uint128 amountY) = amountsIn.decode(); + + if (amountY > deltaLiquidity) { + amountY -= uint128(deltaLiquidity); + } else { + amountY = 0; + amountX = effectiveLiquidity.shiftDivRoundUp(Constants.SCALE_OFFSET, price).safe128(); + } + + effectiveAmountsIn = amountX.encode(amountY); } /** @@ -109,10 +123,9 @@ library BinHelper { * @param id The id of the bin */ function verifyAmounts(bytes32 amounts, uint24 activeId, uint24 id) internal pure { - if ( - uint256(amounts) <= type(uint128).max && id < activeId - || uint256(amounts) > type(uint128).max && id > activeId - ) revert BinMath__CompositionFactorFlawed(id); + if (id < activeId && (amounts << 128) > 0 || id > activeId && uint256(amounts) > type(uint128).max) { + revert BinMath__CompositionFactorFlawed(id); + } } /** @@ -134,6 +147,8 @@ library BinHelper { uint256 totalSupply, uint256 shares ) internal pure returns (bytes32 fees) { + if (shares == 0) return 0; + (uint128 amountX, uint128 amountY) = amountsIn.decode(); (uint128 receivedAmountX, uint128 receivedAmountY) = getAmountOutOfBin(binReserves.add(amountsIn), shares, totalSupply + shares).decode(); @@ -167,7 +182,7 @@ library BinHelper { * @param binStep The step of the bin * @param swapForY Whether the swap is for Y (true) or for X (false) * @param activeId The id of the active bin - * @param amountsLeft The amounts of tokens left to swap + * @param amountsInLeft The amounts of tokens left to swap * @return amountsInToBin The encoded amounts of tokens that will be added to the bin * @return amountsOutOfBin The encoded amounts of tokens that will be removed from the bin * @return totalFees The encoded fees that will be charged @@ -178,7 +193,7 @@ library BinHelper { uint8 binStep, bool swapForY, // swap `swapForY` and `activeId` to avoid stack too deep uint24 activeId, - bytes32 amountsLeft + bytes32 amountsInLeft ) internal pure returns (bytes32 amountsInToBin, bytes32 amountsOutOfBin, bytes32 totalFees) { uint256 price = activeId.getPriceFromId(binStep); @@ -191,26 +206,22 @@ library BinHelper { uint128 totalFee = parameters.getTotalFee(binStep); uint128 maxFee = maxAmountIn.getFeeAmount(totalFee); + uint128 amountIn128 = amountsInLeft.decode(swapForY); uint128 fee128; - uint128 amountIn128; uint128 amountOut128; - uint128 amountIn = amountsLeft.decode(swapForY); - - if (amountIn >= maxAmountIn + maxFee) { + if (amountIn128 >= maxAmountIn + maxFee) { fee128 = maxFee; - amountIn128 = maxAmountIn + maxFee; + amountIn128 = maxAmountIn; amountOut128 = binReserveOut; } else { - fee128 = amountIn.getFeeAmountFrom(totalFee); - - amountIn -= fee128; - amountIn128 = amountIn; + fee128 = amountIn128.getFeeAmountFrom(totalFee); + amountIn128 -= fee128; amountOut128 = swapForY - ? uint256(amountIn).mulShiftRoundDown(price, Constants.SCALE_OFFSET).safe128() - : uint256(amountIn).shiftDivRoundDown(Constants.SCALE_OFFSET, price).safe128(); + ? uint256(amountIn128).mulShiftRoundDown(price, Constants.SCALE_OFFSET).safe128() + : uint256(amountIn128).shiftDivRoundDown(Constants.SCALE_OFFSET, price).safe128(); if (amountOut128 > binReserveOut) amountOut128 = binReserveOut; } diff --git a/src/libraries/Constants.sol b/src/libraries/Constants.sol index 3b3a5e86..3ab5e3c0 100644 --- a/src/libraries/Constants.sol +++ b/src/libraries/Constants.sol @@ -12,7 +12,13 @@ library Constants { uint256 internal constant SCALE = 1 << SCALE_OFFSET; uint256 internal constant PRECISION = 1e18; + uint256 internal constant SQUARED_PRECISION = PRECISION * PRECISION; + + uint256 internal constant MAX_FEE = 0.1e18; // 10% + uint256 internal constant MAX_PROTOCOL_SHARE = 2_500; // 25% of the fee + uint256 internal constant BASIS_POINT_MAX = 10_000; + uint256 internal constant TWO_BASIS_POINT_MAX = 2 * BASIS_POINT_MAX; /// @dev The expected return after a successful flash loan bytes32 internal constant CALLBACK_SUCCESS = keccak256("LBPair.onFlashLoan"); diff --git a/src/libraries/FeeHelper.sol b/src/libraries/FeeHelper.sol index 3c6be67e..16ef7a97 100644 --- a/src/libraries/FeeHelper.sol +++ b/src/libraries/FeeHelper.sol @@ -13,54 +13,97 @@ import {SafeCast} from "./math/SafeCast.sol"; library FeeHelper { using SafeCast for uint256; + error FeeHelper__FeeOverflow(); + error FeeHelper__ProtocolShareOverflow(); + + /** + * @dev Modifier to check that the fee does not overflow + * @param fee The fee + */ + modifier checkFeeOverflow(uint128 fee) { + if (fee > Constants.MAX_FEE) revert FeeHelper__FeeOverflow(); + _; + } + + /** + * @dev Modifier to check that the protocol share does not overflow + * @param protocolShare The protocol share + */ + modifier checkProtocolShareOverflow(uint128 protocolShare) { + if (protocolShare > Constants.MAX_PROTOCOL_SHARE) revert FeeHelper__ProtocolShareOverflow(); + _; + } + /** - * @dev Calculates the fee amount from the amount with fees + * @dev Calculates the fee amount from the amount with fees, rounding up * @param amounWithFees The amount with fees * @param totalFee The total fee * @return feeAmount The fee amount */ - function getFeeAmountFrom(uint128 amounWithFees, uint128 totalFee) internal pure returns (uint128) { + function getFeeAmountFrom(uint128 amounWithFees, uint128 totalFee) + internal + pure + checkFeeOverflow(totalFee) + returns (uint128) + { unchecked { - return ((uint256(amounWithFees) * totalFee + Constants.PRECISION - 1) / Constants.PRECISION).safe128(); + // Can't overflow, max(result) = (type(uint128).max * 0.1e18 + 1e18 - 1) / 1e18 < 2^128 + return uint128((uint256(amounWithFees) * totalFee + Constants.PRECISION - 1) / Constants.PRECISION); } } /** - * @dev Calculates the fee amount that will be charged + * @dev Calculates the fee amount that will be charged, rounding up * @param amount The amount * @param totalFee The total fee * @return feeAmount The fee amount */ - function getFeeAmount(uint128 amount, uint128 totalFee) internal pure returns (uint128) { + function getFeeAmount(uint128 amount, uint128 totalFee) + internal + pure + checkFeeOverflow(totalFee) + returns (uint128) + { unchecked { uint256 denominator = Constants.PRECISION - totalFee; - return ((uint256(amount) * totalFee + denominator - 1) / denominator).safe128(); + // Can't overflow, max(result) = (type(uint128).max * 0.1e18 + (1e18 - 1)) / 0.9e18 < 2^128 + return uint128((uint256(amount) * totalFee + denominator - 1) / denominator); } } /** - * @dev Calculates the composition fee amount from the amount with fees + * @dev Calculates the composition fee amount from the amount with fees, rounding down * @param amountWithFees The amount with fees * @param totalFee The total fee * @return The amount with fees */ - function getCompositionFee(uint128 amountWithFees, uint128 totalFee) internal pure returns (uint128) { + function getCompositionFee(uint128 amountWithFees, uint128 totalFee) + internal + pure + checkFeeOverflow(totalFee) + returns (uint128) + { unchecked { - uint256 denominator = Constants.PRECISION * Constants.PRECISION; - return - (uint256(amountWithFees) * totalFee * (uint256(totalFee) + Constants.PRECISION) / denominator).safe128(); + uint256 denominator = Constants.SQUARED_PRECISION; + // Can't overflow, max(result) = type(uint128).max * 0.1e18 * 1.1e18 / 1e36 <= 2^128 * 0.11e36 / 1e36 < 2^128 + return uint128(uint256(amountWithFees) * totalFee * (uint256(totalFee) + Constants.PRECISION) / denominator); } } /** - * @dev Calculates the protocol fee amount from the fee amount and the protocol share + * @dev Calculates the protocol fee amount from the fee amount and the protocol share, rounding down * @param feeAmount The fee amount * @param protocolShare The protocol share * @return protocolFeeAmount The protocol fee amount */ - function getProtocolFeeAmount(uint128 feeAmount, uint128 protocolShare) internal pure returns (uint128) { + function getProtocolFeeAmount(uint128 feeAmount, uint128 protocolShare) + internal + pure + checkProtocolShareOverflow(protocolShare) + returns (uint128) + { unchecked { - return (uint256(feeAmount) * protocolShare / Constants.BASIS_POINT_MAX).safe128(); + return uint128(uint256(feeAmount) * protocolShare / Constants.BASIS_POINT_MAX); } } } diff --git a/src/libraries/OracleHelper.sol b/src/libraries/OracleHelper.sol index dc3941a5..3951d58e 100644 --- a/src/libraries/OracleHelper.sol +++ b/src/libraries/OracleHelper.sol @@ -34,20 +34,53 @@ library OracleHelper { uint256 internal constant _MAX_SAMPLE_LIFETIME = 120 seconds; + /** + * @dev Modifier to check that the oracle id is valid + * @param oracleId The oracle id + */ + modifier checkOracleId(uint16 oracleId) { + if (oracleId == 0) revert OracleHelper__InvalidOracleId(); + _; + } + /** * @dev Returns the sample at the given oracleId * @param oracle The oracle * @param oracleId The oracle id * @return sample The sample */ - function getSample(Oracle storage oracle, uint16 oracleId) internal view returns (bytes32 sample) { - if (oracleId == 0) revert OracleHelper__InvalidOracleId(); - + function getSample(Oracle storage oracle, uint16 oracleId) + internal + view + checkOracleId(oracleId) + returns (bytes32 sample) + { unchecked { sample = oracle.samples[oracleId - 1]; } } + /** + * @dev Returns the active sample and the active size of the oracle + * @param oracle The oracle + * @param oracleId The oracle id + * @return activeSample The active sample + * @return activeSize The active size of the oracle + */ + function getActiveSampleAndSize(Oracle storage oracle, uint16 oracleId) + internal + view + returns (bytes32 activeSample, uint16 activeSize) + { + activeSample = getSample(oracle, oracleId); + activeSize = activeSample.getOracleLength(); + + if (oracleId != activeSize) { + activeSize = getSample(oracle, activeSize).getOracleLength(); + activeSize = oracleId > activeSize ? oracleId : activeSize; + } + } + /** * @dev Returns the sample at the given timestamp. If the timestamp is not in the oracle, it returns the closest sample * @param oracle The oracle @@ -63,38 +96,31 @@ library OracleHelper { view returns (uint40 lastUpdate, uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) { - bytes32 sample = getSample(oracle, oracleId); - uint16 length = sample.getOracleLength(); - - assembly { - oracleId := mod(oracleId, length) - } - bytes32 oldestSample = oracle.samples[oracleId]; + (bytes32 activeSample, uint16 activeSize) = getActiveSampleAndSize(oracle, oracleId); - // Oreacle is not fully initialized yet - if (oldestSample >> SampleMath.OFFSET_CUMULATIVE_ID == 0) { - length = oracleId; - oldestSample = oracle.samples[0]; + if (oracle.samples[oracleId % activeSize].getSampleLastUpdate() > lookUpTimestamp) { + revert OracleHelper__LookUpTimestampTooOld(); } - if (oldestSample.getSampleLastUpdate() > lookUpTimestamp) revert OracleHelper__LookUpTimestampTooOld(); - - lastUpdate = sample.getSampleLastUpdate(); + lastUpdate = activeSample.getSampleLastUpdate(); if (lastUpdate <= lookUpTimestamp) { return ( - lastUpdate, sample.getCumulativeId(), sample.getCumulativeVolatility(), sample.getCumulativeBinCrossed() + lastUpdate, + activeSample.getCumulativeId(), + activeSample.getCumulativeVolatility(), + activeSample.getCumulativeBinCrossed() ); } else { lastUpdate = lookUpTimestamp; } - (bytes32 prevSample, bytes32 nextSample) = binarySearch(oracle, oracleId, lookUpTimestamp, length); + (bytes32 prevSample, bytes32 nextSample) = binarySearch(oracle, oracleId, lookUpTimestamp, activeSize); uint40 weightPrev = nextSample.getSampleLastUpdate() - lookUpTimestamp; - uint40 weightNext = lookUpTimestamp - sample.getSampleLastUpdate(); + uint40 weightNext = lookUpTimestamp - prevSample.getSampleLastUpdate(); (cumulativeId, cumulativeVolatility, cumulativeBinCrossed) = - sample.getWeightedAverage(prevSample, weightPrev, weightNext); + prevSample.getWeightedAverage(nextSample, weightPrev, weightNext); } /** @@ -117,15 +143,16 @@ library OracleHelper { bytes32 sample; uint40 sampleLastUpdate; - while (low < high) { - uint16 mid = (low + high) / 2; + uint256 startId = oracleId; // oracleId is 1-based + while (low <= high) { + uint16 mid = (low + high) >> 1; assembly { - oracleId := addmod(oracleId, mid, length) + oracleId := addmod(startId, mid, length) } sample = oracle.samples[oracleId]; - sampleLastUpdate = sample.getSampleCreation(); + sampleLastUpdate = sample.getSampleLastUpdate(); if (sampleLastUpdate > lookUpTimestamp) { high = mid - 1; @@ -159,9 +186,7 @@ library OracleHelper { * @param oracleId The oracle id * @param sample The sample */ - function setSample(Oracle storage oracle, uint16 oracleId, bytes32 sample) internal { - if (oracleId == 0) revert OracleHelper__InvalidOracleId(); - + function setSample(Oracle storage oracle, uint16 oracleId, bytes32 sample) internal checkOracleId(oracleId) { unchecked { oracle.samples[oracleId - 1] = sample; } @@ -172,10 +197,11 @@ library OracleHelper { * @param oracle The oracle * @param parameters The parameters * @param activeId The active id + * @return The updated parameters */ - function update(Oracle storage oracle, bytes32 parameters, uint24 activeId) internal { + function update(Oracle storage oracle, bytes32 parameters, uint24 activeId) internal returns (bytes32) { uint16 oracleId = parameters.getOracleId(); - if (oracleId == 0) return; + if (oracleId == 0) return parameters; bytes32 sample = getSample(oracle, oracleId); @@ -190,19 +216,24 @@ library OracleHelper { uint16 length = sample.getOracleLength(); if (deltaTime > _MAX_SAMPLE_LIFETIME) { - deltaTime = 0; - createdAt = uint40(block.timestamp); assembly { oracleId := add(mod(oracleId, length), 1) } + + deltaTime = 0; + createdAt = uint40(block.timestamp); + + parameters = parameters.setOracleId(oracleId); } sample = SampleMath.encode( length, cumulativeId, cumulativeVolatility, cumulativeBinCrossed, uint8(deltaTime), createdAt ); - - setSample(oracle, oracleId, sample); } + + setSample(oracle, oracleId, sample); + + return parameters; } /** @@ -211,22 +242,25 @@ library OracleHelper { * @param oracleId The oracle id * @param newLength The new length */ - function inreaseLength(Oracle storage oracle, uint16 oracleId, uint16 newLength) internal { + function increaseLength(Oracle storage oracle, uint16 oracleId, uint16 newLength) internal { bytes32 sample = getSample(oracle, oracleId); uint16 length = sample.getOracleLength(); if (length >= newLength) revert OracleHelper__NewLengthTooSmall(); + bytes32 lastSample = length == oracleId ? sample : length == 0 ? bytes32(0) : getSample(oracle, length); + + uint256 activeSize = lastSample.getOracleLength(); + activeSize = oracleId > activeSize ? oracleId : activeSize; + for (uint256 i = length; i < newLength;) { - oracle.samples[i] = bytes32(uint256(newLength)); + oracle.samples[i] = bytes32(uint256(activeSize)); unchecked { ++i; } } - if (oracleId != length) { - setSample(oracle, oracleId, (sample ^ bytes32(uint256(length))) | bytes32(uint256(newLength))); - } + setSample(oracle, oracleId, (sample ^ bytes32(uint256(length))) | bytes32(uint256(newLength))); } } diff --git a/src/libraries/PairParameterHelper.sol b/src/libraries/PairParameterHelper.sol index ba0dd225..8a11e367 100644 --- a/src/libraries/PairParameterHelper.sol +++ b/src/libraries/PairParameterHelper.sol @@ -46,7 +46,7 @@ library PairParameterHelper { uint256 internal constant MASK_STATIC_PARAMETER = 0xffffffffffffffffffffffffffff; - uint256 internal constant MAX_OFFSET_PROTOCOL_SHARE = 2_500; + uint256 internal constant MAX_PROTOCOL_SHARE = 2_500; /** * @dev Get the base factor from the encoded pair parameters @@ -217,6 +217,50 @@ library PairParameterHelper { } } + /** + * @dev Calculates the base fee, with 18 decimals + * @param params The encoded pair parameters + * @param binStep The bin step (in 20_000th) + * @return baseFee The base fee + */ + function getBaseFee(bytes32 params, uint8 binStep) internal pure returns (uint256) { + unchecked { + // Base factor is in basis points, binStep is in 20_000th, so we multiply by 5e9 + return uint256(getBaseFactor(params)) * binStep * 5e9; + } + } + + /** + * @dev Calculates the variable fee + * @param params The encoded pair parameters + * @param binStep The bin step (in 20_000th) + * @return variableFee The variable fee + */ + function getVariableFee(bytes32 params, uint8 binStep) internal pure returns (uint256 variableFee) { + uint256 variableFeeControl = getVariableFeeControl(params); + + if (variableFeeControl != 0) { + unchecked { + // The volatility accumulated is in basis points, binStep is in 20_000th, + // and the variable fee control is in basis points, so the result is in 400e18th + uint256 prod = uint256(getVolatilityAccumulated(params)) * binStep; + variableFee = (prod * prod * variableFeeControl + 399) / 400; + } + } + } + + /** + * @dev Calculates the total fee, which is the sum of the base fee and the variable fee + * @param params The encoded pair parameters + * @param binStep The bin step (in 20_000th) + * @return totalFee The total fee + */ + function getTotalFee(bytes32 params, uint8 binStep) internal pure returns (uint128) { + unchecked { + return (getBaseFee(params, binStep) + getVariableFee(params, binStep)).safe128(); + } + } + /** * @dev Set the oracle id in the encoded pair parameters * @param params The encoded pair parameters @@ -273,7 +317,7 @@ library PairParameterHelper { ) internal pure returns (bytes32) { if ( filterPeriod > decayPeriod || decayPeriod > Encoded.MASK_UINT12 - || reductionFactor > Constants.BASIS_POINT_MAX || protocolShare > MAX_OFFSET_PROTOCOL_SHARE + || reductionFactor > Constants.BASIS_POINT_MAX || protocolShare > MAX_PROTOCOL_SHARE || maxVolatilityAccumulated > Encoded.MASK_UINT20 ) revert PairParametersHelper__InvalidParameter(); @@ -328,50 +372,6 @@ library PairParameterHelper { return setVolatilityReference(params, volRef); } - /** - * @dev Calculates the base fee, with 18 decimals - * @param params The encoded pair parameters - * @param binStep The bin step (in 20_000th) - * @return baseFee The base fee - */ - function getBaseFee(bytes32 params, uint8 binStep) internal pure returns (uint256) { - unchecked { - // Base factor is in basis points, binStep is in 20_000th, so we multiply by 5e9 - return uint256(getBaseFactor(params)) * binStep * 5e9; - } - } - - /** - * @dev Calculates the variable fee - * @param params The encoded pair parameters - * @param binStep The bin step (in 20_000th) - * @return variableFee The variable fee - */ - function getVariableFee(bytes32 params, uint8 binStep) internal pure returns (uint256 variableFee) { - uint256 variableFeeControl = getVariableFeeControl(params); - - if (variableFeeControl != 0) { - unchecked { - // The volatility accumulated is in basis points, binStep is in 20_000th, - // and the variable fee control is in basis points, so the result is in 400e18th - uint256 prod = uint256(getVolatilityAccumulated(params)) * binStep; - variableFee = (prod * prod * variableFeeControl + 399) / 400; - } - } - } - - /** - * @dev Calculates the total fee, which is the sum of the base fee and the variable fee - * @param params The encoded pair parameters - * @param binStep The bin step (in 20_000th) - * @return totalFee The total fee - */ - function getTotalFee(bytes32 params, uint8 binStep) internal pure returns (uint128) { - unchecked { - return (getBaseFee(params, binStep) + getVariableFee(params, binStep)).safe128(); - } - } - /** * @dev Updates the volatility accumulated in the encoded pair parameters * @param params The encoded pair parameters diff --git a/src/libraries/PriceHelper.sol b/src/libraries/PriceHelper.sol index f47b82f6..f54f695b 100644 --- a/src/libraries/PriceHelper.sol +++ b/src/libraries/PriceHelper.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.10; import {Uint128x128Math} from "./math/Uint128x128Math.sol"; +import {Uint256x256Math} from "./math/Uint256x256Math.sol"; import {SafeCast} from "./math/SafeCast.sol"; import {Constants} from "./Constants.sol"; @@ -13,6 +14,7 @@ import {Constants} from "./Constants.sol"; */ library PriceHelper { using Uint128x128Math for uint256; + using Uint256x256Math for uint256; using SafeCast for uint256; int256 private constant REAL_ID_SHIFT = 1 << 23; @@ -21,7 +23,7 @@ library PriceHelper { * @dev Calculates the price from the id and the bin step * @param id The id * @param binStep The bin step - * @return price The price + * @return price The price as a 128.128-binary fixed-point number */ function getPriceFromId(uint24 id, uint8 binStep) internal pure returns (uint256 price) { uint256 base = getBase(binStep); @@ -32,7 +34,7 @@ library PriceHelper { /** * @dev Calculates the id from the price and the bin step - * @param price The price + * @param price The price as a 128.128-binary fixed-point number * @param binStep The bin step * @return id The id */ @@ -52,7 +54,7 @@ library PriceHelper { */ function getBase(uint8 binStep) internal pure returns (uint256) { unchecked { - return Constants.SCALE + (uint256(binStep) << Constants.SCALE_OFFSET) / Constants.BASIS_POINT_MAX; + return Constants.SCALE + (uint256(binStep) << Constants.SCALE_OFFSET) / Constants.TWO_BASIS_POINT_MAX; } } @@ -66,4 +68,22 @@ library PriceHelper { return int256(uint256(id)) - REAL_ID_SHIFT; } } + + /** + * @dev Converts a price with 18 decimals to a 128.128-binary fixed-point number + * @param price The price with 18 decimals + * @return price128x128 The 128.128-binary fixed-point number + */ + function convertDecimalPriceTo128x128(uint256 price) internal pure returns (uint256) { + return price.shiftDivRoundDown(Constants.SCALE_OFFSET, Constants.PRECISION); + } + + /** + * @dev Converts a 128.128-binary fixed-point number to a price with 18 decimals + * @param price128x128 The 128.128-binary fixed-point number + * @return price The price with 18 decimals + */ + function convert128x128PriceToDecimal(uint256 price128x128) internal pure returns (uint256) { + return price128x128.mulShiftRoundDown(Constants.PRECISION, Constants.SCALE_OFFSET); + } } diff --git a/src/libraries/ReentrancyGuardUpgradeable.sol b/src/libraries/ReentrancyGuard.sol similarity index 88% rename from src/libraries/ReentrancyGuardUpgradeable.sol rename to src/libraries/ReentrancyGuard.sol index ba25d918..9b783e7f 100644 --- a/src/libraries/ReentrancyGuardUpgradeable.sol +++ b/src/libraries/ReentrancyGuard.sol @@ -3,12 +3,12 @@ pragma solidity 0.8.10; /** - * @title Liquidity Book Reentrancy Guard Upgradeable Library + * @title Liquidity Book Reentrancy Guard Library * @author Trader Joe * @notice This library contains functions to prevent reentrant calls to a function */ -abstract contract ReentrancyGuardUpgradeable { - error ReentrancyGuardUpgradeable__ReentrantCall(); +abstract contract ReentrancyGuard { + error ReentrancyGuard__ReentrantCall(); /** * Booleans are more expensive than uint256 or any type that takes up a full @@ -46,7 +46,7 @@ abstract contract ReentrancyGuardUpgradeable { */ modifier nonReentrant() { // On the first call to nonReentrant, _notEntered will be true - if (_status != _NOT_ENTERED) revert ReentrancyGuardUpgradeable__ReentrantCall(); + if (_status != _NOT_ENTERED) revert ReentrancyGuard__ReentrantCall(); // Any calls to nonReentrant after this point will fail _status = _ENTERED; diff --git a/src/libraries/TokenHelper.sol b/src/libraries/TokenHelper.sol index 15651889..0b0cc3ad 100644 --- a/src/libraries/TokenHelper.sol +++ b/src/libraries/TokenHelper.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; -import {AddressesHelper} from "./AddressesHelper.sol"; +import {AddressHelper} from "./AddressHelper.sol"; /** * @title Liquidity Book Token Helper Library @@ -17,7 +17,7 @@ import {AddressesHelper} from "./AddressesHelper.sol"; * which allows you to call the safe operation as `token.safeTransfer(...)` */ library TokenHelper { - using AddressesHelper for address; + using AddressHelper for address; error TokenHelper__TransferFailed(); diff --git a/src/libraries/math/PackedUint128Math.sol b/src/libraries/math/PackedUint128Math.sol index 9e78fe1b..a3935c7a 100644 --- a/src/libraries/math/PackedUint128Math.sol +++ b/src/libraries/math/PackedUint128Math.sol @@ -17,10 +17,11 @@ library PackedUint128Math { error PackedUint128Math__AddOverflow(); error PackedUint128Math__SubUnderflow(); error PackedUint128Math__AddFirstSubSecondOverflow(); - error PackedUint128Math__MultiplierBiggerThanMax(); + error PackedUint128Math__MultiplierTooLarge(); uint256 private constant OFFSET = 128; uint256 private constant MASK_128 = 0xffffffffffffffffffffffffffffffff; + uint256 private constant MASK_128_PLUS_ONE = MASK_128 + 1; /** * @dev Encodes two uint128 into a single bytes32 @@ -244,8 +245,7 @@ library PackedUint128Math { } /** - * @dev Multiplies an encoded bytes32 by a uint128 then shifts the result 128 bits to the right, rounding up - * The result can't overflow + * @dev Multiplies an encoded bytes32 by a uint256 then shifts the result 128 bits to the right, rounding up * @param x The bytes32 encoded as follows: * [0 - 128[: x1 * [128 - 256[: x2 @@ -254,8 +254,9 @@ library PackedUint128Math { * [0 - 128[: ceil((x1 * multiplier) / 2**128) * [128 - 256[: ceil((x2 * multiplier) / 2**128) */ - function scalarMulShift128RoundUp(bytes32 x, uint128 multiplier) internal pure returns (bytes32 z) { + function scalarMulShiftRoundUp(bytes32 x, uint256 multiplier) internal pure returns (bytes32 z) { if (multiplier == 0) return 0; + if (multiplier > MASK_128_PLUS_ONE) revert PackedUint128Math__MultiplierTooLarge(); (uint128 x1, uint128 x2) = decode(x); @@ -288,7 +289,7 @@ library PackedUint128Math { if (multiplier == 0) return 0; uint256 BASIS_POINT_MAX = Constants.BASIS_POINT_MAX; - if (multiplier > BASIS_POINT_MAX) revert PackedUint128Math__MultiplierBiggerThanMax(); + if (multiplier > BASIS_POINT_MAX) revert PackedUint128Math__MultiplierTooLarge(); (uint128 x1, uint128 x2) = decode(x); diff --git a/src/libraries/math/SampleMath.sol b/src/libraries/math/SampleMath.sol index 383dbf63..a84c822f 100644 --- a/src/libraries/math/SampleMath.sol +++ b/src/libraries/math/SampleMath.sol @@ -130,7 +130,6 @@ library SampleMath { * [216 - 256[: sample creation timestamp (40 bits) * @return lastUpdate The sample last update timestamp */ - //TODO lastupdate 48 bits? function getSampleLastUpdate(bytes32 sample) internal pure returns (uint40 lastUpdate) { lastUpdate = getSampleCreation(sample) + getSampleLifetime(sample); } diff --git a/test/BinHelper.T.sol b/test/BinHelper.T.sol deleted file mode 100644 index 45c4aa96..00000000 --- a/test/BinHelper.T.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "./helpers/TestHelper.sol"; - -contract BinHelperTest is TestHelper { - function testInversePriceForOppositeBins() public { - assertApproxEqAbs( - (getPriceFromId(ID_ONE + 10) * getPriceFromId(ID_ONE - 10)) / Constants.SCALE, Constants.SCALE, 1 - ); - - assertApproxEqAbs( - (getPriceFromId(ID_ONE + 1_000) * getPriceFromId(ID_ONE - 1_000)) / Constants.SCALE, Constants.SCALE, 1 - ); - - assertApproxEqAbs( - (getPriceFromId(ID_ONE + 10_000) * getPriceFromId(ID_ONE - 10_000)) / Constants.SCALE, Constants.SCALE, 1 - ); - } -} diff --git a/test/PriceHelper.t.sol b/test/PriceHelper.t.sol deleted file mode 100644 index bdb821ae..00000000 --- a/test/PriceHelper.t.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import "forge-std/Test.sol"; - -import "../src/libraries/PriceHelper.sol"; - -contract PriceHelperTest is Test { - using PriceHelper for bytes32; - //TODO -} diff --git a/test/libraries/AddressHelper.t.sol b/test/libraries/AddressHelper.t.sol new file mode 100644 index 00000000..62472d23 --- /dev/null +++ b/test/libraries/AddressHelper.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../../src/libraries/AddressHelper.sol"; + +contract AddressHelperTest is Test { + using AddressHelper for address; + + RandomContract immutable randomContract = new RandomContract(); + + function test_IsContract() public { + assertTrue(address(randomContract).isContract(), "isContract::1"); + assertTrue(!address(0).isContract(), "isContract::2"); + } + + function test_CallAndCatchSuccessfull() public { + bytes memory data = abi.encodeWithSignature("return1()"); + bytes memory returnData = address(randomContract).callAndCatch(data); + assertEq(returnData.length, 32, "callAndCatch::1"); + assertEq(uint256(abi.decode(returnData, (uint256))), 1, "callAndCatch::2"); + + data = abi.encodeWithSignature("returnNothing()"); + returnData = address(randomContract).callAndCatch(data); + assertEq(returnData.length, 0, "callAndCatch::2"); + } + + function test_CallAndCatchFail() public { + vm.expectRevert("AddressHelperTest: fail"); + address(randomContract).callAndCatch(abi.encodeWithSignature("revertWithString()")); + + vm.expectRevert(AddressHelper.AddressHelper__CallFailed.selector); + // can't call the library directly orelse foundry expect the revert to be the first revert (L23) + randomContract.callAndCatch(address(this), abi.encodeWithSignature("UndefindedFunction()")); + } + + function testFuzz_CallAndCatchNonContract(bytes memory data) public { + vm.expectRevert(AddressHelper.AddressHelper__NonContract.selector); + // same reason as above + randomContract.callAndCatch(address(0), data); + } +} + +contract RandomContract { + using AddressHelper for address; + + function return1() public pure returns (uint256) { + return 1; + } + + function returnNothing() public pure {} + + function revertWithString() public pure { + revert("AddressHelperTest: fail"); + } + + function callAndCatch(address target, bytes memory data) public returns (bytes memory) { + return target.callAndCatch(data); + } +} diff --git a/test/libraries/BinHelper.t.sol b/test/libraries/BinHelper.t.sol new file mode 100644 index 00000000..db4b5bd0 --- /dev/null +++ b/test/libraries/BinHelper.t.sol @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.10; + +import "../helpers/TestHelper.sol"; + +import "../../src/libraries/BinHelper.sol"; +import "../../src/libraries/math/PackedUint128Math.sol"; +import "../../src/libraries/math/Uint256x256Math.sol"; +import "../../src/libraries/math/Uint128x128Math.sol"; +import "../../src/libraries/PairParameterHelper.sol"; + +contract BinHelperTest is TestHelper { + using BinHelper for bytes32; + using PackedUint128Math for bytes32; + using PackedUint128Math for uint128; + using Uint128x128Math for uint256; + using Uint256x256Math for uint256; + using PairParameterHelper for bytes32; + + function testFuzz_GetAmountOutOfBin( + uint128 binReserveX, + uint128 binReserveY, + uint256 amountToBurn, + uint256 totalSupply + ) external { + vm.assume(totalSupply > 0 && totalSupply >= amountToBurn); + + bytes32 binReserves = binReserveX.encode(binReserveY); + + bytes32 amountOut = binReserves.getAmountOutOfBin(amountToBurn, totalSupply); + (uint128 amountOutX, uint128 amountOutY) = amountOut.decode(); + + assertEq(amountOutX, amountToBurn.mulDivRoundDown(binReserveX, totalSupply), "test_GetAmountOutOfBin::1"); + assertEq(amountOutY, amountToBurn.mulDivRoundDown(binReserveY, totalSupply), "test_GetAmountOutOfBin::2"); + } + + function testFuzz_GetLiquidity(uint128 amountInX, uint128 amountInY, uint256 price) external { + uint256 px = price.mulShiftRoundDown(amountInX, Constants.SCALE_OFFSET); + + bytes32 amountIn = amountInX.encode(amountInY); + if (px > type(uint256).max - amountInY) { + vm.expectRevert(); + amountIn.getLiquidity(price); + } else { + uint256 liquidity = amountIn.getLiquidity(price); + assertEq(liquidity, px + amountInY, "test_GetLiquidity::1"); + } + } + + function testFuzz_GetShareAndEffectiveAmountsIn( + uint128 binReserveX, + uint128 binReserveY, + uint128 amountInX, + uint128 amountInY, + uint256 price, + uint256 totalSupply + ) external { + bytes32 binReserves = binReserveX.encode(binReserveY); + uint256 binLiquidity = binReserves.getLiquidity(price); + + vm.assume(price > 0 && totalSupply <= binLiquidity); + + bytes32 amountsIn = amountInX.encode(amountInY); + + (uint256 shares, bytes32 effectiveAmountsIn) = + binReserves.getShareAndEffectiveAmountsIn(amountsIn, price, totalSupply); + + assertLe(uint256(effectiveAmountsIn), uint256(amountsIn), "test_GetShareAndEffectiveAmountsIn::1"); + + uint256 userLiquidity = amountsIn.getLiquidity(price); + uint256 expectedShares = binLiquidity == 0 || totalSupply == 0 + ? userLiquidity + : userLiquidity.mulDivRoundDown(totalSupply, binLiquidity); + + assertEq(shares, expectedShares, "test_GetShareAndEffectiveAmountsIn::2"); + } + + function testFuzz_TryExploitShares(uint128 amountX, uint128 amountY, uint256 price) external { + vm.assume(price > 0 && amountX > 0 && amountX < type(uint128).max && amountY > 0 && amountY < type(uint128).max); + + // exploiter front run the tx and mint 1 of liquidity + uint256 totalSupply = 1; + + // exploiter increase the reserve to amounts added by user + 1 + bytes32 binReserves = uint128(amountX + 1).encode(uint128(amountY + 1)); + + // user add liquidity + (uint256 shares, bytes32 effectiveAmountsIn) = + binReserves.getShareAndEffectiveAmountsIn(amountX.encode(amountY), price, totalSupply); + + assertEq(shares, 0, "test_TryExploitShares::1"); + + (uint128 effectiveX, uint128 effectiveY) = effectiveAmountsIn.decode(); + assertEq(effectiveX, 0, "test_TryExploitShares::2"); + assertEq(effectiveY, 0, "test_TryExploitShares::3"); + + // If user added liquidity with 1 wei more, he will get 1 share + (shares, effectiveAmountsIn) = + binReserves.getShareAndEffectiveAmountsIn((amountX + 1).encode(amountY + 1), price, totalSupply); + + assertEq(shares, 1, "test_TryExploitShares::3"); + assertEq(effectiveAmountsIn, (amountX + 1).encode(amountY + 1), "test_TryExploitShares::4"); + } + + function testFuzz_VerifyAmountsNeqIds(uint128 amountX, uint128 amountY, uint24 activeId, uint24 id) external { + vm.assume(activeId != id); + + bytes32 amounts = amountX.encode(amountY); + + if (id < activeId && amountX > 0 || id > activeId && amountY > 0) { + vm.expectRevert(abi.encodeWithSelector(BinHelper.BinMath__CompositionFactorFlawed.selector, id)); + } + + amounts.verifyAmounts(activeId, id); + } + + function testFuzz_VerifyAmountsOnActiveId(uint128 amountX, uint128 amountY, uint24 activeId) external pure { + bytes32 amounts = amountX.encode(amountY); + amounts.verifyAmounts(activeId, activeId); + } + + function testFuzz_GetCompositionFees( + uint128 reserveX, + uint128 reserveY, + uint8 binStep, + uint128 amountXIn, + uint128 amountYIn, + uint256 price, + uint256 totalSupply + ) external { + bytes32 binReserves = reserveX.encode(reserveY); + uint256 binLiquidity = binReserves.getLiquidity(price); + + vm.assume( + binStep <= 200 && price > 0 && totalSupply <= binLiquidity + && (totalSupply == 0 && binReserves == 0 || totalSupply > 0 && binReserves > 0) + ); + + (uint256 shares, bytes32 amountsIn) = + binReserves.getShareAndEffectiveAmountsIn(amountXIn.encode(amountYIn), price, totalSupply); + + vm.assume( + !binReserves.gt(bytes32(type(uint256).max).sub(amountsIn)) && totalSupply <= type(uint256).max - shares + ); + + (amountXIn, amountYIn) = amountsIn.decode(); + + bytes32 parameters = bytes32(0).setStaticFeeParameters( + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATED + ); + + bytes32 compositionFees = binReserves.getCompositionFees(parameters, binStep, amountsIn, totalSupply, shares); + + uint256 binC = reserveX | reserveY == 0 ? 0 : (uint256(reserveY) << 128) / (uint256(reserveX) + reserveY); + uint256 userC = amountXIn | amountYIn == 0 ? 0 : (uint256(amountYIn) << 128) / (uint256(amountXIn) + amountYIn); + + if (binC > userC) { + assertGe(uint256(compositionFees) << 128, 0, "test_GetCompositionFees::1"); + } else { + assertGe(uint128(uint256(compositionFees)), 0, "test_GetCompositionFees::2"); + } + } + + function testFuzz_BinIsEmpty(uint128 binReserveX, uint128 binReserveY) external { + bytes32 binReserves = binReserveX.encode(binReserveY); + + assertEq(binReserves.isEmpty(true), binReserveX == 0, "test_BinIsEmpty::1"); + assertEq(binReserves.isEmpty(false), binReserveY == 0, "test_BinIsEmpty::2"); + } + + function testFuzz_GetAmountsLessThanBin( + uint128 binReserveX, + uint128 binReserveY, + bool swapForY, + int16 deltaId, + uint128 amountIn + ) external { + bytes32 parameters = bytes32(0).setStaticFeeParameters( + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATED + ); + + uint24 activeId = uint24(uint256(int256(uint256(ID_ONE)) + deltaId)); + uint256 price = PriceHelper.getPriceFromId(activeId, DEFAULT_BIN_STEP); + + { + uint256 maxAmountIn = swapForY + ? uint256(binReserveY).shiftDivRoundUp(Constants.SCALE_OFFSET, price) + : uint256(binReserveX).mulShiftRoundUp(price, Constants.SCALE_OFFSET); + vm.assume(maxAmountIn <= type(uint128).max); + + uint128 maxFee = FeeHelper.getFeeAmount(uint128(maxAmountIn), parameters.getTotalFee(DEFAULT_BIN_STEP)); + vm.assume(maxAmountIn <= type(uint128).max - maxFee && amountIn < maxAmountIn + maxFee); + } + + bytes32 reserves = binReserveX.encode(binReserveY); + + (bytes32 amountsInToBin, bytes32 amountsOutOfBin, bytes32 totalFees) = + reserves.getAmounts(parameters, DEFAULT_BIN_STEP, swapForY, activeId, amountIn.encode(swapForY)); + + assertLe(amountsInToBin.add(totalFees).decode(swapForY), amountIn, "test_GetAmounts::1"); + + uint256 amountInForSwap = amountsInToBin.add(totalFees).decode(swapForY); + + (uint256 amountOutWithNoFees, uint256 amountOut) = swapForY + ? (price.mulShiftRoundDown(amountInForSwap, Constants.SCALE_OFFSET), amountsOutOfBin.decodeSecond()) + : (uint256(amountInForSwap).shiftDivRoundDown(Constants.SCALE_OFFSET, price), amountsOutOfBin.decodeFirst()); + + assertGe(amountOutWithNoFees, amountOut, "test_GetAmounts::2"); + + uint256 amountOutWithFees = swapForY + ? price.mulShiftRoundDown(amountsInToBin.decodeFirst(), Constants.SCALE_OFFSET) + : uint256(amountsInToBin.decodeSecond()).shiftDivRoundDown(Constants.SCALE_OFFSET, price); + + assertEq(amountOut, amountOutWithFees, "test_GetAmounts::3"); + } + + function testFuzz_getAmountsFullBin( + uint128 binReserveX, + uint128 binReserveY, + bool swapForY, + int16 deltaId, + uint128 amountIn + ) external { + bytes32 parameters = bytes32(0).setStaticFeeParameters( + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATED + ); + + uint24 activeId = uint24(uint256(int256(uint256(ID_ONE)) + deltaId)); + uint256 price = PriceHelper.getPriceFromId(activeId, DEFAULT_BIN_STEP); + + { + uint256 maxAmountIn = swapForY + ? uint256(binReserveY).shiftDivRoundUp(Constants.SCALE_OFFSET, price) + : uint256(binReserveX).mulShiftRoundUp(price, Constants.SCALE_OFFSET); + vm.assume(maxAmountIn <= type(uint128).max); + + uint128 maxFee = FeeHelper.getFeeAmount(uint128(maxAmountIn), parameters.getTotalFee(DEFAULT_BIN_STEP)); + vm.assume(maxAmountIn <= type(uint128).max - maxFee && amountIn >= maxAmountIn + maxFee); + } + + bytes32 reserves = binReserveX.encode(binReserveY); + + (bytes32 amountsInToBin, bytes32 amountsOutOfBin, bytes32 totalFees) = + reserves.getAmounts(parameters, DEFAULT_BIN_STEP, swapForY, activeId, amountIn.encode(swapForY)); + + assertLe(amountsInToBin.add(totalFees).decode(swapForY), amountIn, "test_GetAmounts::1"); + + { + uint256 amountInForSwap = amountsInToBin.add(totalFees).decode(swapForY); + + (uint256 amountOutWithNoFees, uint256 amountOut) = swapForY + ? (price.mulShiftRoundDown(amountInForSwap, Constants.SCALE_OFFSET), amountsOutOfBin.decodeSecond()) + : (uint256(amountInForSwap).shiftDivRoundDown(Constants.SCALE_OFFSET, price), amountsOutOfBin.decodeFirst()); + + assertGe(amountOutWithNoFees, amountOut, "test_GetAmounts::2"); + } + + uint128 amountInToBin = amountsInToBin.decode(swapForY); + + (uint256 amountOutWithFees, uint256 amountOutWithFeesAmountInSub1) = amountInToBin == 0 + ? (0, 0) + : swapForY + ? ( + price.mulShiftRoundDown(amountInToBin, Constants.SCALE_OFFSET), + price.mulShiftRoundDown(amountInToBin - 1, Constants.SCALE_OFFSET) + ) + : ( + uint256(amountInToBin).shiftDivRoundDown(Constants.SCALE_OFFSET, price), + uint256(amountInToBin - 1).shiftDivRoundDown(Constants.SCALE_OFFSET, price) + ); + + assertLe(amountsOutOfBin.decode(!swapForY), amountOutWithFees, "test_GetAmounts::3"); + assertGe(amountsOutOfBin.decode(!swapForY), amountOutWithFeesAmountInSub1, "test_GetAmounts::4"); + } + + function testFuzz_Received(uint128 reserveX, uint128 reserveY, uint128 sentX, uint128 sentY) external { + vm.assume(reserveX < type(uint128).max - sentX && reserveY < type(uint128).max - sentY); + + address pair = address(this); + + deal(address(usdc), pair, reserveX + sentX); + deal(address(wavax), pair, reserveY + sentY); + + bytes32 reserves = reserveX.encode(reserveY); + + bytes32 received = reserves.received(IERC20(address(usdc)), IERC20(address(wavax))); + + (uint256 receivedX, uint256 receivedY) = received.decode(); + + assertEq(receivedX, sentX, "test_Received::1"); + assertEq(receivedY, sentY, "test_Received::2"); + + received = reserves.receivedX(IERC20(address(usdc))); + receivedX = received.decodeFirst(); + + assertEq(receivedX, sentX, "test_Received::3"); + + received = reserves.receivedY(IERC20(address(wavax))); + receivedY = received.decodeSecond(); + + assertEq(receivedY, sentY, "test_Received::4"); + } + + function testFuzz_Transfer(uint128 amountX, uint128 amountY) external { + address recipient = address(1); + + deal(address(usdc), address(this), amountX); + deal(address(wavax), address(this), amountY); + + bytes32 amounts = amountX.encode(amountY); + + bytes32 firstHalf = amounts.sub((amountX / 2).encode((amountY / 2))); + bytes32 secondHalf = amounts.sub(firstHalf); + + firstHalf.transfer(IERC20(address(usdc)), IERC20(address(wavax)), recipient); + + assertEq(usdc.balanceOf(recipient), firstHalf.decodeFirst(), "test_Transfer::1"); + assertEq(wavax.balanceOf(recipient), firstHalf.decodeSecond(), "test_Transfer::2"); + assertEq(usdc.balanceOf(address(this)), secondHalf.decodeFirst(), "test_Transfer::3"); + assertEq(wavax.balanceOf(address(this)), secondHalf.decodeSecond(), "test_Transfer::4"); + + secondHalf.transferX(IERC20(address(usdc)), recipient); + + assertEq(usdc.balanceOf(recipient), amounts.decodeFirst(), "test_Transfer::5"); + assertEq(wavax.balanceOf(recipient), firstHalf.decodeSecond(), "test_Transfer::6"); + assertEq(usdc.balanceOf(address(this)), 0, "test_Transfer::7"); + assertEq(wavax.balanceOf(address(this)), secondHalf.decodeSecond(), "test_Transfer::8"); + + secondHalf.transferY(IERC20(address(wavax)), recipient); + + assertEq(usdc.balanceOf(recipient), amounts.decodeFirst(), "test_Transfer::9"); + assertEq(wavax.balanceOf(recipient), amounts.decodeSecond(), "test_Transfer::10"); + assertEq(usdc.balanceOf(address(this)), 0, "test_Transfer::11"); + assertEq(wavax.balanceOf(address(this)), 0, "test_Transfer::12"); + } +} diff --git a/test/libraries/FeeHelper.t.sol b/test/libraries/FeeHelper.t.sol new file mode 100644 index 00000000..44682064 --- /dev/null +++ b/test/libraries/FeeHelper.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../../src/libraries/FeeHelper.sol"; +import "../../src/libraries/math/Uint256x256Math.sol"; + +contract FeeHelperTest is Test { + using FeeHelper for uint128; + using Uint256x256Math for uint256; + + function testFuzz_GetFeeAmountFrom(uint128 amountWithFee, uint128 fee) external { + if (fee > Constants.MAX_FEE) { + vm.expectRevert(FeeHelper.FeeHelper__FeeOverflow.selector); + amountWithFee.getFeeAmountFrom(fee); + } else { + uint256 expectedFeeAmount = (uint256(amountWithFee) * fee + 1e18 - 1) / 1e18; + uint128 feeAmount = amountWithFee.getFeeAmountFrom(fee); + + assertEq(feeAmount, expectedFeeAmount, "testFuzz_GetFeeAmountFrom::1"); + } + } + + function testFuzz_GetFeeAmount(uint128 amount, uint128 fee) external { + if (fee > Constants.MAX_FEE) { + vm.expectRevert(FeeHelper.FeeHelper__FeeOverflow.selector); + amount.getFeeAmount(fee); + } else { + uint128 denominator = 1e18 - fee; + uint256 expectedFeeAmount = (uint256(amount) * fee + denominator - 1) / denominator; + + uint128 feeAmount = amount.getFeeAmount(fee); + + assertEq(feeAmount, expectedFeeAmount, "testFuzz_GetFeeAmount::1"); + } + } + + function testFuzz_GetCompositionFee(uint128 amountWithFee, uint128 fee) external { + if (fee > Constants.MAX_FEE) { + vm.expectRevert(FeeHelper.FeeHelper__FeeOverflow.selector); + amountWithFee.getCompositionFee(fee); + } + + uint256 denominator = 1e36; + uint256 expectedCompositionFee = + (uint256(amountWithFee) * fee).mulDivRoundDown(uint256(fee) + 1e18, denominator); + + uint128 compositionFee = amountWithFee.getCompositionFee(fee); + + assertEq(compositionFee, expectedCompositionFee, "testFuzz_GetCompositionFee::1"); + } + + function testFuzz_GetProtocolFeeAmount(uint128 amount, uint128 fee) external { + if (fee > Constants.MAX_PROTOCOL_SHARE) { + vm.expectRevert(FeeHelper.FeeHelper__ProtocolShareOverflow.selector); + amount.getProtocolFeeAmount(fee); + } else { + uint256 expectedProtocolFeeAmount = (uint256(amount) * fee) / 1e4; + uint128 protocolFeeAmount = amount.getProtocolFeeAmount(fee); + + assertEq(protocolFeeAmount, expectedProtocolFeeAmount, "testFuzz_GetProtocolFeeAmount::1"); + } + } +} diff --git a/test/TestImmutableClone.sol b/test/libraries/ImmutableClone.t.sol similarity index 97% rename from test/TestImmutableClone.sol rename to test/libraries/ImmutableClone.t.sol index f27c7e62..11530ff9 100644 --- a/test/TestImmutableClone.sol +++ b/test/libraries/ImmutableClone.t.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; -import "../src/libraries/Clone.sol"; -import "../src/libraries/ImmutableClone.sol"; +import "../../src/libraries/Clone.sol"; +import "../../src/libraries/ImmutableClone.sol"; contract TestImmutableClone is Test { function testFuzz_CloneDeterministic(bytes32 salt) public { diff --git a/test/libraries/OracleHelper.t.sol b/test/libraries/OracleHelper.t.sol new file mode 100644 index 00000000..6ec647db --- /dev/null +++ b/test/libraries/OracleHelper.t.sol @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../../src/libraries/OracleHelper.sol"; +import "../../src/libraries/math/Encoded.sol"; + +contract OracleHelperTest is Test { + using OracleHelper for OracleHelper.Oracle; + using SampleMath for bytes32; + using PairParameterHelper for bytes32; + using Encoded for bytes32; + + OracleHelper.Oracle private oracle; + + function testFuzz_SetAndGetSample(uint16 oracleId, bytes32 sample) external { + vm.assume(oracleId > 0); + + oracle.setSample(oracleId, sample); + assertEq(oracle.getSample(oracleId), sample, "test_SetSample::1"); + assertEq(oracle.samples[oracleId - 1], sample, "test_SetSample::2"); + } + + function testFuzz_revert_SetAndGetSample(bytes32 sample) external { + uint16 oracleId = 0; + + vm.expectRevert(OracleHelper.OracleHelper__InvalidOracleId.selector); + oracle.setSample(oracleId, sample); + + vm.expectRevert(OracleHelper.OracleHelper__InvalidOracleId.selector); + oracle.getSample(oracleId); + } + + function test_BinarySearchSimple() external { + bytes32 sample1 = SampleMath.encode(3, 1, 2, 3, 0, 0); + bytes32 sample2 = SampleMath.encode(3, 2, 3, 4, 0, 10); + bytes32 sample3 = SampleMath.encode(3, 3, 4, 5, 0, 20); + + oracle.setSample(1, sample1); + oracle.setSample(2, sample2); + oracle.setSample(3, sample3); + + (bytes32 previous, bytes32 next) = oracle.binarySearch(3, 0, 3); + + assertEq(previous, sample1, "test_binarySearch::1"); + assertEq(next, sample1, "test_binarySearch::2"); + + (previous, next) = oracle.binarySearch(3, 1, 3); + + assertEq(previous, sample1, "test_binarySearch::3"); + assertEq(next, sample2, "test_binarySearch::4"); + + (previous, next) = oracle.binarySearch(3, 9, 3); + + assertEq(previous, sample1, "test_binarySearch::5"); + assertEq(next, sample2, "test_binarySearch::6"); + + (previous, next) = oracle.binarySearch(3, 10, 3); + + assertEq(previous, sample2, "test_binarySearch::7"); + assertEq(next, sample2, "test_binarySearch::8"); + + (previous, next) = oracle.binarySearch(3, 11, 3); + + assertEq(previous, sample2, "test_binarySearch::9"); + assertEq(next, sample3, "test_binarySearch::10"); + + (previous, next) = oracle.binarySearch(3, 20, 3); + + assertEq(previous, sample3, "test_binarySearch::11"); + assertEq(next, sample3, "test_binarySearch::12"); + } + + function test_BinarySearchCircular() external { + bytes32 sample1 = SampleMath.encode(3, 1, 2, 3, 3, 30); // sample at timestamp 0 got overriden + bytes32 sample2 = SampleMath.encode(3, 2, 3, 4, 9, 10); + bytes32 sample3 = SampleMath.encode(3, 3, 4, 5, 9, 20); + + oracle.setSample(1, sample1); + oracle.setSample(2, sample2); + oracle.setSample(3, sample3); + + (bytes32 previous, bytes32 next) = oracle.binarySearch(1, 19, 3); + + assertEq(previous, sample2, "test_binarySearch::1"); + assertEq(next, sample2, "test_binarySearch::2"); + + (previous, next) = oracle.binarySearch(1, 24, 3); + + assertEq(previous, sample2, "test_binarySearch::3"); + assertEq(next, sample3, "test_binarySearch::4"); + + (previous, next) = oracle.binarySearch(1, 29, 3); + + assertEq(previous, sample3, "test_binarySearch::5"); + assertEq(next, sample3, "test_binarySearch::6"); + + (previous, next) = oracle.binarySearch(1, 30, 3); + + assertEq(previous, sample3, "test_binarySearch::7"); + assertEq(next, sample1, "test_binarySearch::8"); + + (previous, next) = oracle.binarySearch(1, 33, 3); + + assertEq(previous, sample1, "test_binarySearch::9"); + assertEq(next, sample1, "test_binarySearch::10"); + } + + function test_revert_BinarySearch() external { + bytes32 sample1 = SampleMath.encode(3, 1, 2, 3, 0, 30); // sample at timestamp 0 got overriden + bytes32 sample2 = SampleMath.encode(3, 2, 3, 4, 5, 10); + + vm.expectRevert(); + oracle.binarySearch(0, 20, 3); // invalid oracleId + + vm.expectRevert(); + oracle.binarySearch(1, 20, 0); // invalid length + + oracle.setSample(1, sample1); + oracle.setSample(2, sample2); + + vm.expectRevert(); + oracle.binarySearch(0, 20, 3); // invalid oracleId + + vm.expectRevert(); + oracle.binarySearch(1, 20, 0); // invalid length + + vm.expectRevert(); + oracle.binarySearch(1, 9, 2); // invalid timestamp + + vm.expectRevert(); + oracle.binarySearch(1, 31, 2); // invalid timestamp + } + + function test_GetSampleAtFullyInitialized() external { + bytes32 sample1 = SampleMath.encode(3, 40, 50, 60, 3, 30); // sample at timestamp 0 got overriden + bytes32 sample2 = SampleMath.encode(3, 20, 30, 40, 5, 10); + bytes32 sample3 = SampleMath.encode(3, 30, 40, 50, 5, 20); + + oracle.setSample(1, sample1); + oracle.setSample(2, sample2); + oracle.setSample(3, sample3); + + (uint40 lastUpdate, uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = + oracle.getSampleAt(1, 15); + + assertEq(lastUpdate, 15, "test_GetSampleAt::1"); + assertEq(cumulativeId, 20, "test_GetSampleAt::2"); + assertEq(cumulativeVolatility, 30, "test_GetSampleAt::3"); + assertEq(cumulativeBinCrossed, 40, "test_GetSampleAt::4"); + + (lastUpdate, cumulativeId, cumulativeVolatility, cumulativeBinCrossed) = oracle.getSampleAt(1, 20); + + assertEq(lastUpdate, 20, "test_GetSampleAt::5"); + assertEq(cumulativeId, 25, "test_GetSampleAt::6"); + assertEq(cumulativeVolatility, 35, "test_GetSampleAt::7"); + assertEq(cumulativeBinCrossed, 45, "test_GetSampleAt::8"); + + (lastUpdate, cumulativeId, cumulativeVolatility, cumulativeBinCrossed) = oracle.getSampleAt(1, 25); + + assertEq(lastUpdate, 25, "test_GetSampleAt::9"); + assertEq(cumulativeId, 30, "test_GetSampleAt::10"); + assertEq(cumulativeVolatility, 40, "test_GetSampleAt::11"); + assertEq(cumulativeBinCrossed, 50, "test_GetSampleAt::12"); + + (lastUpdate, cumulativeId, cumulativeVolatility, cumulativeBinCrossed) = oracle.getSampleAt(1, 30); + + assertEq(lastUpdate, 30, "test_GetSampleAt::13"); + assertEq(cumulativeId, 36, "test_GetSampleAt::14"); + assertEq(cumulativeVolatility, 46, "test_GetSampleAt::15"); + assertEq(cumulativeBinCrossed, 56, "test_GetSampleAt::16"); + + (lastUpdate, cumulativeId, cumulativeVolatility, cumulativeBinCrossed) = oracle.getSampleAt(1, 40); + + assertEq(lastUpdate, 33, "test_GetSampleAt::17"); + assertEq(cumulativeId, 40, "test_GetSampleAt::18"); + assertEq(cumulativeVolatility, 50, "test_GetSampleAt::19"); + assertEq(cumulativeBinCrossed, 60, "test_GetSampleAt::20"); + } + + struct Updateinputs { + uint16 oracleLength; + uint16 oracleId; + uint24 previousActiveId; + uint24 activeId; + uint24 previousVolatility; + uint24 volatility; + uint24 previousBinCrossed; + uint40 createdAt; + uint40 timestamp; + } + + function testFuzz_UpdateDeltaTsLowerThan2Minutes(Updateinputs memory inputs) external { + vm.assume( + inputs.oracleId > 0 && inputs.oracleLength >= inputs.oracleId && inputs.createdAt <= inputs.timestamp + && inputs.timestamp - inputs.createdAt <= 120 && inputs.volatility <= Encoded.MASK_UINT20 + && inputs.previousVolatility <= Encoded.MASK_UINT20 + ); + + vm.warp(inputs.createdAt); + + bytes32 sample = SampleMath.encode( + inputs.oracleLength, + uint64(inputs.previousActiveId) * inputs.createdAt, + uint64(inputs.previousVolatility) * inputs.createdAt, + uint64(inputs.previousBinCrossed) * inputs.createdAt, + 0, + inputs.createdAt + ); + + oracle.setSample(inputs.oracleId, sample); + + bytes32 parameters = bytes32(0).setOracleId(inputs.oracleId).setActiveId(inputs.previousActiveId).set( + inputs.volatility, Encoded.MASK_UINT20, PairParameterHelper.OFFSET_VOL_ACC + ); + + vm.warp(inputs.timestamp); + + bytes32 newParams = oracle.update(parameters, inputs.activeId); + + assertEq(newParams, parameters, "test_Update::1"); + + sample = oracle.getSample(inputs.oracleId); + + uint64 dt = uint64(inputs.timestamp - inputs.createdAt); + + uint24 dId = inputs.activeId > inputs.previousActiveId + ? inputs.activeId - inputs.previousActiveId + : inputs.previousActiveId - inputs.activeId; + + uint64 cumulativeId = uint64(inputs.previousActiveId) * inputs.createdAt + uint64(inputs.activeId) * dt; + uint64 cumulativeVolatility = + uint64(inputs.previousVolatility) * inputs.createdAt + uint64(inputs.volatility) * dt; + uint64 cumulativeBinCrossed = uint64(inputs.previousBinCrossed) * inputs.createdAt + uint64(dId) * dt; + + assertEq(sample.getOracleLength(), inputs.oracleLength, "test_Update::3"); + assertEq(sample.getCumulativeId(), cumulativeId, "test_Update::4"); + assertEq(sample.getCumulativeVolatility(), cumulativeVolatility, "test_Update::5"); + assertEq(sample.getCumulativeBinCrossed(), cumulativeBinCrossed, "test_Update::6"); + } + + function testFuzz_UpdateDeltaTsGreaterThan2Minutes(Updateinputs memory inputs) external { + vm.assume( + inputs.oracleId > 0 && inputs.oracleLength >= inputs.oracleId && inputs.createdAt <= inputs.timestamp + && inputs.timestamp - inputs.createdAt > 120 && inputs.volatility <= Encoded.MASK_UINT20 + && inputs.previousVolatility <= Encoded.MASK_UINT20 + ); + + vm.warp(inputs.createdAt); + + bytes32 sample = SampleMath.encode( + inputs.oracleLength, + uint64(inputs.previousActiveId) * inputs.createdAt, + uint64(inputs.previousVolatility) * inputs.createdAt, + uint64(inputs.previousBinCrossed) * inputs.createdAt, + 0, + inputs.createdAt + ); + + oracle.setSample(inputs.oracleId, sample); + + bytes32 parameters = bytes32(0).setOracleId(inputs.oracleId).setActiveId(inputs.previousActiveId).set( + inputs.volatility, Encoded.MASK_UINT20, PairParameterHelper.OFFSET_VOL_ACC + ); + + vm.warp(inputs.timestamp); + + bytes32 newParameters = oracle.update(parameters, inputs.activeId); + + uint16 nextId = uint16(uint256(inputs.oracleId % inputs.oracleLength) + 1); + + assertEq(newParameters, parameters.setOracleId(nextId), "test_Update::1"); + if (inputs.oracleLength > 1) assertEq(oracle.getSample(inputs.oracleId), sample, "test_Update::2"); + + sample = oracle.getSample(nextId); + + uint64 dt = uint64(inputs.timestamp - inputs.createdAt); + + uint24 dId = inputs.activeId > inputs.previousActiveId + ? inputs.activeId - inputs.previousActiveId + : inputs.previousActiveId - inputs.activeId; + + uint64 cumulativeId = uint64(inputs.previousActiveId) * inputs.createdAt + uint64(inputs.activeId) * dt; + uint64 cumulativeVolatility = + uint64(inputs.previousVolatility) * inputs.createdAt + uint64(inputs.volatility) * dt; + uint64 cumulativeBinCrossed = uint64(inputs.previousBinCrossed) * inputs.createdAt + uint64(dId) * dt; + + assertEq(sample.getOracleLength(), inputs.oracleLength, "test_Update::3"); + assertEq(sample.getCumulativeId(), cumulativeId, "test_Update::4"); + assertEq(sample.getCumulativeVolatility(), cumulativeVolatility, "test_Update::5"); + assertEq(sample.getCumulativeBinCrossed(), cumulativeBinCrossed, "test_Update::6"); + } + + function testFuzz_IncreaseOracleLength(uint16 length, uint16 newLength) external { + vm.assume(length > 0 && newLength > length); + + uint16 oracleId = 1; + + oracle.increaseLength(oracleId, length); + + oracle.increaseLength(oracleId, newLength); + + assertEq(oracle.getSample(oracleId).getOracleLength(), newLength, "test_IncreaseOracleLength::1"); + } + + function testFuzz_revert_IncreaseOracleLength(uint16 length, uint16 newLength) external { + vm.assume(newLength <= length && length > 0); + + oracle.increaseLength(1, length); + + vm.expectRevert(OracleHelper.OracleHelper__NewLengthTooSmall.selector); + oracle.increaseLength(1, newLength); + } + + function test_revert_IncreaseOracleLength() external { + vm.expectRevert(OracleHelper.OracleHelper__InvalidOracleId.selector); + oracle.increaseLength(0, 10); + } + + function test_GetSampleAtNotFullyInitialized() external { + bytes32 parameters = bytes32(0).setOracleId(1).setActiveId(1000).set( + 1000, Encoded.MASK_UINT20, PairParameterHelper.OFFSET_VOL_ACC + ); + oracle.increaseLength(parameters.getOracleId(), 3); + _verifyTimestampsIdsAndSize(parameters, 1, hex"030101", 1); // id : 1, oracle: 3 1 1, activeSize: 1 + + vm.warp(100); + + parameters = oracle.update(parameters, 1000); + _verifyTimestampsIdsAndSize(parameters, 1, hex"030101", 1); // id : 1, oracle: 3 1 1, activeSize: 1 + + vm.warp(221); + + parameters = oracle.update(parameters, 1000); + _verifyTimestampsIdsAndSize(parameters, 2, hex"030301", 2); // id : 2, oracle: 3 3 1, activeSize: 2 + + oracle.increaseLength(parameters.getOracleId(), 4); + _verifyTimestampsIdsAndSize(parameters, 2, hex"03040102", 2); // id : 2, oracle: 3 4 1 2, activeSize: 2 + + vm.warp(222); + + parameters = oracle.update(parameters, 1000); + _verifyTimestampsIdsAndSize(parameters, 2, hex"03040102", 2); // id : 2, oracle: 3 4 1 2, activeSize: 2 + + vm.warp(342); + + parameters = oracle.update(parameters, 1000); + _verifyTimestampsIdsAndSize(parameters, 3, hex"03040402", 3); // id : 3, oracle: 3 4 4 2, activeSize: 3 + + oracle.increaseLength(parameters.getOracleId(), 5); + _verifyTimestampsIdsAndSize(parameters, 3, hex"0304050203", 3); // id : 3, oracle: 3 4 5 2 3, activeSize: 3 + + vm.warp(463); + + parameters = oracle.update(parameters, 1000); + _verifyTimestampsIdsAndSize(parameters, 4, hex"0304050503", 4); // id : 4, oracle: 3 4 5 5 3, activeSize: 4 + + vm.warp(584); + + parameters = oracle.update(parameters, 1000); + _verifyTimestampsIdsAndSize(parameters, 5, hex"0304050505", 5); // id : 5, oracle: 3 4 5 5 5, activeSize: 5 + + oracle.increaseLength(parameters.getOracleId(), 7); + _verifyTimestampsIdsAndSize(parameters, 5, hex"03040505070505", 5); // id : 5, oracle: 3 4 5 5 7 5 5, activeSize: 5 + + vm.warp(705); + + parameters = oracle.update(parameters, 1000); + _verifyTimestampsIdsAndSize(parameters, 6, hex"03040505070705", 6); // id : 6, oracle: 3 4 5 5 7 7 5, activeSize: 6 + + vm.warp(826); + + parameters = oracle.update(parameters, 1000); + _verifyTimestampsIdsAndSize(parameters, 7, hex"03040505070707", 7); // id : 7, oracle: 3 4 5 5 7 7 7, activeSize: 7 + + vm.warp(947); + + parameters = oracle.update(parameters, 1000); + _verifyTimestampsIdsAndSize(parameters, 1, hex"07040505070707", 7); // id : 1, oracle: 7 4 5 5 7 7 7, activeSize: 7 + + oracle.increaseLength(parameters.getOracleId(), 8); + _verifyTimestampsIdsAndSize(parameters, 1, hex"0804050507070707", 7); // id : 1, oracle: 8 4 5 5 7 7 7 7, activeSize: 7 + + vm.warp(1068); + + parameters = oracle.update(parameters, 1000); + _verifyTimestampsIdsAndSize(parameters, 2, hex"0808050507070707", 7); // id : 2, oracle: 8 8 5 5 7 7 7 7, activeSize: 7 + } + + function _verifyTimestampsIdsAndSize(bytes32 parameters, uint16 oracleId, bytes memory times, uint16 activeSize) + internal + { + assertEq(parameters.getOracleId(), oracleId, "_verifyTimestampsIdsAndSize::id"); + + for (uint16 i = 0; i < times.length; i++) { + bytes32 sample = oracle.getSample(i + 1); + + assertEq( + sample.getOracleLength(), + uint256(uint8(times[i])), + string(abi.encodePacked("_verifyTimestampsIdsAndSize::", vm.toString(i))) + ); + } + + (, uint16 aSize) = oracle.getActiveSampleAndSize(oracleId); + + assertEq(aSize, activeSize, "_verifyTimestampsIdsAndSize::activeSize"); + } +} diff --git a/test/libraries/PairParameterHelper.t.sol b/test/libraries/PairParameterHelper.t.sol new file mode 100644 index 00000000..c7509f3e --- /dev/null +++ b/test/libraries/PairParameterHelper.t.sol @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../../src/libraries/PairParameterHelper.sol"; + +contract PairParameterHelperTest is Test { + using PairParameterHelper for bytes32; + + struct StaticFeeParameters { + uint16 baseFactor; + uint16 filterPeriod; + uint16 decayPeriod; + uint16 reductionFactor; + uint24 variableFeeControl; + uint16 protocolShare; + uint24 maxVolatilityAccumulated; + } + + function testFuzz_StaticFeeParameters(bytes32 params, StaticFeeParameters memory sfp) external { + vm.assume( + sfp.filterPeriod <= sfp.decayPeriod && sfp.decayPeriod <= Encoded.MASK_UINT12 + && sfp.reductionFactor <= Constants.BASIS_POINT_MAX + && sfp.protocolShare <= PairParameterHelper.MAX_PROTOCOL_SHARE + && sfp.maxVolatilityAccumulated <= Encoded.MASK_UINT20 + ); + + bytes32 newParams = params.setStaticFeeParameters( + sfp.baseFactor, + sfp.filterPeriod, + sfp.decayPeriod, + sfp.reductionFactor, + sfp.variableFeeControl, + sfp.protocolShare, + sfp.maxVolatilityAccumulated + ); + + assertEq( + newParams >> PairParameterHelper.OFFSET_VOL_ACC, + params >> PairParameterHelper.OFFSET_VOL_ACC, + "testFuzz_StaticFeeParameters::1" + ); + + assertEq(newParams.getBaseFactor(), sfp.baseFactor, "testFuzz_StaticFeeParameters::2"); + assertEq(newParams.getFilterPeriod(), sfp.filterPeriod, "testFuzz_StaticFeeParameters::3"); + assertEq(newParams.getDecayPeriod(), sfp.decayPeriod, "testFuzz_StaticFeeParameters::4"); + assertEq(newParams.getReductionFactor(), sfp.reductionFactor, "testFuzz_StaticFeeParameters::5"); + assertEq(newParams.getVariableFeeControl(), sfp.variableFeeControl, "testFuzz_StaticFeeParameters::6"); + assertEq(newParams.getProtocolShare(), sfp.protocolShare, "testFuzz_StaticFeeParameters::7"); + assertEq( + newParams.getMaxVolatilityAccumulated(), sfp.maxVolatilityAccumulated, "testFuzz_StaticFeeParameters::8" + ); + } + + function testFuzz_revert_StaticFeeParameters(bytes32 params, StaticFeeParameters memory sfp) external { + vm.assume( + sfp.filterPeriod > sfp.decayPeriod || sfp.decayPeriod > Encoded.MASK_UINT12 + || sfp.reductionFactor > Constants.BASIS_POINT_MAX + || sfp.protocolShare > PairParameterHelper.MAX_PROTOCOL_SHARE + || sfp.maxVolatilityAccumulated > Encoded.MASK_UINT20 + ); + + vm.expectRevert(PairParameterHelper.PairParametersHelper__InvalidParameter.selector); + params.setStaticFeeParameters( + sfp.baseFactor, + sfp.filterPeriod, + sfp.decayPeriod, + sfp.reductionFactor, + sfp.variableFeeControl, + sfp.protocolShare, + sfp.maxVolatilityAccumulated + ); + } + + function testFuzz_SetOracleId(bytes32 params, uint16 oracleId) external { + bytes32 newParams = params.setOracleId(oracleId); + + assertEq(newParams.getOracleId(), oracleId, "testFuzz_SetOracleId::1"); + assertEq( + newParams & bytes32(~Encoded.MASK_UINT16 << PairParameterHelper.OFFSET_ORACLE_ID), + params & bytes32(~Encoded.MASK_UINT16 << PairParameterHelper.OFFSET_ORACLE_ID), + "testFuzz_SetOracleId::2" + ); + } + + function testFuzz_SetVolatilityReference(bytes32 params, uint24 volatilityReference) external { + vm.assume(volatilityReference <= Encoded.MASK_UINT20); + + bytes32 newParams = params.setVolatilityReference(volatilityReference); + + assertEq(newParams.getVolatilityReference(), volatilityReference, "testFuzz_SetVolatilityReference::1"); + assertEq( + newParams & bytes32(~Encoded.MASK_UINT20 << PairParameterHelper.OFFSET_VOL_REF), + params & bytes32(~Encoded.MASK_UINT20 << PairParameterHelper.OFFSET_VOL_REF), + "testFuzz_SetVolatilityReference::2" + ); + } + + function testFuzz_revert_SetVolatilityReference(bytes32 params, uint24 volatilityReference) external { + vm.assume(volatilityReference > Encoded.MASK_UINT20); + + vm.expectRevert(PairParameterHelper.PairParametersHelper__InvalidParameter.selector); + params.setVolatilityReference(volatilityReference); + } + + function testFuzz_SetActiveId(bytes32 params, uint24 activeId) external { + uint24 previousActiveId = params.getActiveId(); + uint24 deltaId = previousActiveId > activeId ? previousActiveId - activeId : activeId - previousActiveId; + assertEq(params.getDeltaId(activeId), deltaId, "testFuzz_SetActiveId::1"); + + bytes32 newParams = params.setActiveId(activeId); + + assertEq(newParams.getActiveId(), activeId, "testFuzz_SetActiveId::2"); + assertEq(newParams.getDeltaId(activeId), 0, "testFuzz_SetActiveId::3"); + assertEq(newParams.getDeltaId(previousActiveId), deltaId, "testFuzz_SetActiveId::4"); + assertEq( + newParams & bytes32(~Encoded.MASK_UINT24 << PairParameterHelper.OFFSET_ACTIVE_ID), + params & bytes32(~Encoded.MASK_UINT24 << PairParameterHelper.OFFSET_ACTIVE_ID), + "testFuzz_SetActiveId::5" + ); + } + + function testFuzz_getBaseAndVariableFees(bytes32 params, uint8 binStep) external { + uint256 baseFee = params.getBaseFee(binStep); + uint256 variableFee = params.getVariableFee(binStep); + + assertEq(baseFee, uint256(params.getBaseFactor()) * binStep * 5e9, "test_getBaseAndVariableFees::1"); + + uint256 prod = uint256(params.getVolatilityAccumulated()) * binStep; + assertEq( + variableFee, (prod * prod * params.getVariableFeeControl() + 399) / 400, "test_getBaseAndVariableFees::2" + ); + + if (baseFee + variableFee < type(uint128).max) { + assertEq(params.getTotalFee(binStep), baseFee + variableFee, "test_getBaseAndVariableFees::3"); + } else { + vm.expectRevert(SafeCast.SafeCast__Exceeds128Bits.selector); + params.getTotalFee(binStep); + } + } + + function testFuzz_UpdateIdReference(bytes32 params) external { + uint24 activeId = params.getActiveId(); + + bytes32 newParams = params.updateIdReference(); + + assertEq(newParams.getIdReference(), activeId, "test_UpdateIdReference::1"); + assertEq( + newParams & bytes32(~Encoded.MASK_UINT24 << PairParameterHelper.OFFSET_ACTIVE_ID), + params & bytes32(~Encoded.MASK_UINT24 << PairParameterHelper.OFFSET_ACTIVE_ID), + "test_UpdateIdReference::2" + ); + } + + function testFuzz_UpdateTimeOfLastUpdate(bytes32 params) external { + bytes32 newParams = params.updateTimeOfLastUpdate(); + + assertEq(newParams.getTimeOfLastUpdate(), block.timestamp, "test_UpdateTimeOfLastUpdate::1"); + assertEq( + newParams & bytes32(~Encoded.MASK_UINT40 << PairParameterHelper.OFFSET_TIME_LAST_UPDATE), + params & bytes32(~Encoded.MASK_UINT40 << PairParameterHelper.OFFSET_TIME_LAST_UPDATE), + "test_UpdateTimeOfLastUpdate::2" + ); + } + + function testFuzz_UpdateVolatilityReference(bytes32 params) external { + uint256 volAccumulated = params.getVolatilityAccumulated(); + uint256 reductionFactor = params.getReductionFactor(); + + uint256 newVolAccumulated = volAccumulated * reductionFactor / Constants.BASIS_POINT_MAX; + + if (newVolAccumulated > Encoded.MASK_UINT20) { + vm.expectRevert(PairParameterHelper.PairParametersHelper__InvalidParameter.selector); + params.updateVolatilityReference(); + } else { + bytes32 newParams = params.updateVolatilityReference(); + + assertEq(newParams.getVolatilityReference(), newVolAccumulated, "test_UpdateVolatilityReference::1"); + assertEq( + newParams & bytes32(~Encoded.MASK_UINT20 << PairParameterHelper.OFFSET_VOL_REF), + params & bytes32(~Encoded.MASK_UINT20 << PairParameterHelper.OFFSET_VOL_REF), + "test_UpdateVolatilityReference::2" + ); + } + } + + function testFuzz_UpdateVolatilityAccumulated(bytes32 params, uint24 activeId) external { + uint256 deltaId = params.getDeltaId(activeId); + + uint256 volAccumulated = params.getVolatilityAccumulated() + deltaId * Constants.BASIS_POINT_MAX; + volAccumulated = volAccumulated > params.getMaxVolatilityAccumulated() + ? params.getMaxVolatilityAccumulated() + : volAccumulated; + + bytes32 newParams = params.updateVolatilityAccumulated(activeId); + + assertEq(newParams.getVolatilityAccumulated(), volAccumulated, "test_UpdateVolatilityAccumulated::1"); + assertEq( + newParams & bytes32(~Encoded.MASK_UINT20 << PairParameterHelper.OFFSET_VOL_ACC), + params & bytes32(~Encoded.MASK_UINT20 << PairParameterHelper.OFFSET_VOL_ACC), + "test_UpdateVolatilityAccumulated::2" + ); + } + + function testFuzz_UpdateReferences(bytes32 params, StaticFeeParameters memory sfp, uint40 previousTime, uint40 time) + external + { + vm.assume( + previousTime <= time && sfp.filterPeriod <= sfp.decayPeriod && sfp.decayPeriod <= Encoded.MASK_UINT12 + && sfp.reductionFactor <= Constants.BASIS_POINT_MAX + && sfp.protocolShare <= PairParameterHelper.MAX_PROTOCOL_SHARE + && sfp.maxVolatilityAccumulated <= Encoded.MASK_UINT20 + ); + + vm.warp(previousTime); + + params = params.setStaticFeeParameters( + sfp.baseFactor, + sfp.filterPeriod, + sfp.decayPeriod, + sfp.reductionFactor, + sfp.variableFeeControl, + sfp.protocolShare, + sfp.maxVolatilityAccumulated + ).updateTimeOfLastUpdate(); + + vm.warp(time); + + uint256 deltaTime = time - previousTime; + + uint256 idReference = deltaTime >= sfp.filterPeriod ? params.getActiveId() : params.getIdReference(); + + uint256 volReference = params.getVolatilityReference(); + if (deltaTime >= sfp.filterPeriod) { + volReference = + deltaTime >= sfp.decayPeriod ? 0 : params.updateVolatilityReference().getVolatilityReference(); + } + + bytes32 newParams = params.updateReferences(); + + assertEq(newParams.getIdReference(), idReference, "test_UpdateReferences::1"); + assertEq(newParams.getVolatilityReference(), volReference, "test_UpdateReferences::2"); + assertEq(newParams.getTimeOfLastUpdate(), time, "test_UpdateReferences::3"); + + assertEq( + newParams & bytes32(~(uint256(1 << 84) - 1) << PairParameterHelper.OFFSET_VOL_REF), + params & bytes32(~(uint256(1 << 84) - 1) << PairParameterHelper.OFFSET_VOL_REF), + "test_UpdateReferences::4" + ); + } + + function testFuzz_revert_UpdateReferences(uint40 previousTime, uint40 time) external { + vm.assume(previousTime > time); + + vm.warp(previousTime); + + bytes32 params = bytes32(0).updateTimeOfLastUpdate(); + + vm.warp(time); + + vm.expectRevert(); + params.updateReferences(); + } + + function testFuzz_UpdateVolatilityParameters( + bytes32 params, + StaticFeeParameters memory sfp, + uint40 previousTime, + uint40 time, + uint24 activeId + ) external { + vm.assume( + previousTime <= time && sfp.filterPeriod <= sfp.decayPeriod && sfp.decayPeriod <= Encoded.MASK_UINT12 + && sfp.reductionFactor <= Constants.BASIS_POINT_MAX + && sfp.protocolShare <= PairParameterHelper.MAX_PROTOCOL_SHARE + && sfp.maxVolatilityAccumulated <= Encoded.MASK_UINT20 + ); + + vm.warp(previousTime); + + params = params.setStaticFeeParameters( + sfp.baseFactor, + sfp.filterPeriod, + sfp.decayPeriod, + sfp.reductionFactor, + sfp.variableFeeControl, + sfp.protocolShare, + sfp.maxVolatilityAccumulated + ).updateTimeOfLastUpdate(); + + vm.warp(time); + + bytes32 trustedParams = params.updateReferences().updateVolatilityAccumulated(activeId); + bytes32 newParams = params.updateVolatilityParameters(activeId); + + assertEq(newParams.getIdReference(), trustedParams.getIdReference(), "test_UpdateVolatilityParameters::1"); + assertEq( + newParams.getVolatilityReference(), + trustedParams.getVolatilityReference(), + "test_UpdateVolatilityParameters::2" + ); + assertEq( + newParams.getVolatilityAccumulated(), + trustedParams.getVolatilityAccumulated(), + "test_UpdateVolatilityParameters::3" + ); + assertEq(newParams.getTimeOfLastUpdate(), time, "test_UpdateVolatilityParameters::4"); + + assertEq( + newParams & bytes32(~uint256(type(uint104).max) << PairParameterHelper.OFFSET_VOL_ACC), + params & bytes32(~uint256(type(uint104).max) << PairParameterHelper.OFFSET_VOL_ACC), + "test_UpdateVolatilityParameters::5" + ); + } +} diff --git a/test/libraries/PendingOwnable.t.sol b/test/libraries/PendingOwnable.t.sol new file mode 100644 index 00000000..8a726c5d --- /dev/null +++ b/test/libraries/PendingOwnable.t.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../../src/libraries/PendingOwnable.sol"; + +contract PendingOwnableTest is Test { + Foo foo; + + address owner = makeAddr("owner"); + address pendingOwner = makeAddr("pendingOwner"); + address bob = makeAddr("bob"); + + function setUp() public { + vm.prank(owner); + foo = new Foo(); + } + + function test_PendingOwner() public { + assertEq(foo.owner(), owner, "test_PendingOwner::1"); + assertEq(foo.pendingOwner(), address(0), "test_PendingOwner::2"); + + vm.prank(owner); + foo.setPendingOwner(pendingOwner); + + assertEq(foo.owner(), owner, "test_PendingOwner::3"); + assertEq(foo.pendingOwner(), pendingOwner, "test_PendingOwner::4"); + + vm.prank(pendingOwner); + foo.becomeOwner(); + + assertEq(foo.owner(), pendingOwner, "test_PendingOwner::5"); + assertEq(foo.pendingOwner(), address(0), "test_PendingOwner::6"); + } + + function test_RestrictedToOwner() public { + vm.prank(owner); + foo.restrictedToOwner(); + + vm.expectRevert(IPendingOwnable.PendingOwnable__NotOwner.selector); + vm.prank(pendingOwner); + foo.restrictedToOwner(); + + vm.startPrank(bob); + + vm.expectRevert(IPendingOwnable.PendingOwnable__NotOwner.selector); + foo.setPendingOwner(pendingOwner); + + vm.expectRevert(IPendingOwnable.PendingOwnable__NotOwner.selector); + foo.revokePendingOwner(); + + vm.expectRevert(IPendingOwnable.PendingOwnable__NotOwner.selector); + foo.renounceOwnership(); + + vm.stopPrank(); + } + + function test_RestrictedToPendingOwner() public { + vm.prank(owner); + foo.setPendingOwner(pendingOwner); + + vm.expectRevert(IPendingOwnable.PendingOwnable__NotPendingOwner.selector); + vm.prank(owner); + foo.restrictedToPendingOwner(); + + vm.prank(pendingOwner); + foo.restrictedToPendingOwner(); + + vm.expectRevert(IPendingOwnable.PendingOwnable__NotPendingOwner.selector); + vm.prank(bob); + foo.becomeOwner(); + } + + function test_RevokePendingOwner() public { + vm.startPrank(owner); + + vm.expectRevert(IPendingOwnable.PendingOwnable__NoPendingOwner.selector); + foo.revokePendingOwner(); + + foo.setPendingOwner(pendingOwner); + + foo.revokePendingOwner(); + vm.stopPrank(); + + assertEq(foo.owner(), owner, "test_RevokePendingOwner::1"); + assertEq(foo.pendingOwner(), address(0), "test_RevokePendingOwner::2"); + } + + function test_RenounceOwnership() public { + vm.expectRevert(IPendingOwnable.PendingOwnable__NotOwner.selector); + vm.prank(pendingOwner); + foo.renounceOwnership(); + + vm.prank(owner); + foo.renounceOwnership(); + + assertEq(foo.owner(), address(0), "test_RenounceOwnership::1"); + assertEq(foo.pendingOwner(), address(0), "test_RenounceOwnership::2"); + } + + function test_revert_BecomeOwnerForAddressZero() public { + vm.startPrank(foo.pendingOwner()); + vm.expectRevert(IPendingOwnable.PendingOwnable__NotPendingOwner.selector); + foo.becomeOwner(); + vm.stopPrank(); + } + + function test_revert_SetPendingOwnerForAddressZero() public { + vm.expectRevert(IPendingOwnable.PendingOwnable__AddressZero.selector); + vm.prank(owner); + foo.setPendingOwner(address(0)); + } +} + +contract Foo is PendingOwnable { + function restrictedToOwner() public view onlyOwner {} + + function restrictedToPendingOwner() public view onlyPendingOwner {} +} diff --git a/test/libraries/PriceHelper.t.sol b/test/libraries/PriceHelper.t.sol new file mode 100644 index 00000000..e1669633 --- /dev/null +++ b/test/libraries/PriceHelper.t.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../../src/libraries/PriceHelper.sol"; + +contract PriceHelperTest is Test { + using Uint256x256Math for uint256; + + Math immutable math = new Math(); + + function testFuzz_GetBase(uint8 binStep) external { + uint256 base128x128 = PriceHelper.getBase(binStep); + uint256 expectedBase128x128 = (1 << 128) + (uint256(binStep) << 128) / 20_000; + + assertEq(base128x128, expectedBase128x128, "test_GetBase::1"); + } + + function testFuzz_GetExponent(uint24 id) external { + int256 exponent128x128 = PriceHelper.getExponent(id); + int256 expectedExponent128x128 = int256(uint256(id)) - (1 << 23); + + assertEq(exponent128x128, expectedExponent128x128, "test_GetExponent::1"); + } + + function testFuzz_ConvertDecimalPriceTo128x128(uint256 price) external { + // result of `type(uint256).max * 1e18 >> 128`, this is the largest number before the result overflows + vm.assume(price <= 340282366920938463463374607431768211455999999999999999999); + uint256 price128x128 = PriceHelper.convertDecimalPriceTo128x128(price); + uint256 expectedPrice128x128 = price.shiftDivRoundDown(128, 1e18); + + assertEq(price128x128, expectedPrice128x128, "test_ConvertDecimalPriceTo128x128::1"); + } + + function testFuzz_revert_ConvertDecimalPriceTo128x128(uint256 price) external { + // result of `type(uint256).max * 1e18 >> 128`, this is the largest number before the result overflows + vm.assume(price > 340282366920938463463374607431768211455999999999999999999); + + vm.expectRevert(Uint256x256Math.Uint256x256Math__MulDivOverflow.selector); + PriceHelper.convertDecimalPriceTo128x128(price); + } + + function testFuzz_Convert128x128PriceToDecimal(uint256 price128x128) external { + uint256 priceDecimal = PriceHelper.convert128x128PriceToDecimal(price128x128); + uint256 expectedPriceDecimal = price128x128.mulShiftRoundDown(1e18, 128); + + assertEq(priceDecimal, expectedPriceDecimal, "test_Convert128x128PriceToDecimal::1"); + } + + function testFuzz_Price(uint256 price, uint8 binStep) external { + // test that all prices from 1e64 to 1e192 are valid, includes 1e-18 to 1e18 in decimal. + vm.assume(price > 1 << 64 && price < 1 << 192 && binStep > 0 && binStep < 200); + + (bool s, bytes memory data) = + address(math).staticcall(abi.encodeWithSelector(math.idFromPrice.selector, price, binStep)); + + if (s) { + uint24 id = abi.decode(data, (uint24)); + uint256 calculatedPrice = PriceHelper.getPriceFromId(id, binStep); + + // Can't use `assertApproxEqRel` as it overflow when multiplying by 1e18 + // Assert that price is at most `binStep`% away from the calculated price + assertLe( + price * (Constants.TWO_BASIS_POINT_MAX - binStep) / Constants.TWO_BASIS_POINT_MAX, + calculatedPrice, + "test_Price::1" + ); + assertGe( + price * (Constants.TWO_BASIS_POINT_MAX + binStep) / Constants.TWO_BASIS_POINT_MAX, + calculatedPrice, + "test_Price::2" + ); + } + } + + function test_Price() external { + uint24 id = 8761245; // result of `log2(123456789) / log2(1.00005) + 2**23` + uint256 expectedPrice = PriceHelper.convertDecimalPriceTo128x128(123456789e18); + assertLe(PriceHelper.getPriceFromId(id - 1, 1), expectedPrice, "test_Price::1"); + assertGe(PriceHelper.getPriceFromId(id + 1, 1), expectedPrice, "test_Price::2"); + + id = 8116506; // result of `log2(0.00000123456789) / log2(1.00005) + 2**23` + expectedPrice = PriceHelper.convertDecimalPriceTo128x128(0.00000123456789e18); + assertLe(PriceHelper.getPriceFromId(id - 1, 1), expectedPrice, "test_Price::3"); + assertGe(PriceHelper.getPriceFromId(id + 1, 1), expectedPrice, "test_Price::4"); + + id = 8392773; // result of `log2(10**18)/log2(1.01) + 2**23` + expectedPrice = PriceHelper.convertDecimalPriceTo128x128(1e36); + assertLe(PriceHelper.getPriceFromId(id - 1, 200), expectedPrice, "test_Price::5"); + assertGe(PriceHelper.getPriceFromId(id + 1, 200), expectedPrice, "test_Price::6"); + } +} + +contract Math { + function priceFromId(uint24 id, uint8 binStep) external pure returns (uint256) { + return PriceHelper.getPriceFromId(id, binStep); + } + + function idFromPrice(uint256 price, uint8 binStep) external pure returns (uint24) { + return PriceHelper.getIdFromPrice(price, binStep); + } +} diff --git a/test/libraries/ReentrancyGuard.t.sol b/test/libraries/ReentrancyGuard.t.sol new file mode 100644 index 00000000..c610b45d --- /dev/null +++ b/test/libraries/ReentrancyGuard.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../../src/libraries/ReentrancyGuard.sol"; +import "../../src/libraries/AddressHelper.sol"; + +contract ReentrancyGuardTest is Test { + Foo foo; + + function setUp() public { + foo = new Foo(); + } + + function testReentrancyGuard() public { + vm.expectRevert(ReentrancyGuard.ReentrancyGuard__ReentrantCall.selector); + foo.callSelf(abi.encodeWithSignature("reentrancyGuarded()")); + } + + function testNoReentrancyGuard() public { + foo.callSelf(abi.encodeWithSignature("noReentrancyGuard()")); + } +} + +contract Foo is ReentrancyGuard { + using AddressHelper for address; + + constructor() { + __ReentrancyGuard_init(); + } + + function callSelf(bytes memory data) external nonReentrant { + address(this).callAndCatch(data); + } + + function reentrancyGuarded() external nonReentrant {} + + function noReentrancyGuard() external pure {} +} diff --git a/test/libraries/TokenHelper.t.sol b/test/libraries/TokenHelper.t.sol new file mode 100644 index 00000000..316dfc42 --- /dev/null +++ b/test/libraries/TokenHelper.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../../src/libraries/TokenHelper.sol"; + +contract TokenHelperTest is Test { + using TokenHelper for IERC20; + + Vault vault; + + function setUp() public { + vault = new Vault(); + } + + function test_revert_TransferOnFalse() public { + IERC20 badToken = IERC20(address(new ERC20ReturnsFalse())); + + vm.expectRevert(TokenHelper.TokenHelper__TransferFailed.selector); + vault.deposit(badToken, 1); + + vm.expectRevert(TokenHelper.TokenHelper__TransferFailed.selector); + vault.withdraw(badToken, 1); + } + + function test_revert_TransferOnCustom() public { + IERC20 badToken = IERC20(address(new ERC20ReturnsCustom())); + + vm.expectRevert(); + vault.deposit(badToken, 1); + + vm.expectRevert(); + vault.withdraw(badToken, 1); + } +} + +contract Vault { + using TokenHelper for IERC20; + + function deposit(IERC20 token, uint256 amount) external { + token.safeTransferFrom(msg.sender, address(this), amount); + } + + function withdraw(IERC20 token, uint256 amount) external { + token.safeTransfer(msg.sender, amount); + } +} + +contract ERC20ReturnsFalse { + function transfer(address, uint256) external pure returns (bool) { + return false; + } + + function transferFrom(address, address, uint256) external pure returns (bool) { + return false; + } +} + +contract ERC20ReturnsCustom { + function transfer(address, uint256) external pure returns (bytes32) { + return keccak256("fail"); + } + + function transferFrom(address, address, uint256) external pure returns (bytes32) { + return keccak256("fail"); + } +} diff --git a/test/BitMath.t.sol b/test/libraries/math/BitMath.t.sol similarity index 97% rename from test/BitMath.t.sol rename to test/libraries/math/BitMath.t.sol index 416f5d7d..03cea3fc 100644 --- a/test/BitMath.t.sol +++ b/test/libraries/math/BitMath.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; -import "../src/libraries/math/BitMath.sol"; +import "../../../src/libraries/math/BitMath.sol"; contract BitMathTest is Test { using BitMath for uint256; diff --git a/test/libraries/math/Encoded.t.sol b/test/libraries/math/Encoded.t.sol new file mode 100644 index 00000000..fead524c --- /dev/null +++ b/test/libraries/math/Encoded.t.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../../../src/libraries/math/Encoded.sol"; + +contract EncodedTest is Test { + using Encoded for bytes32; + + function testFuzz_Set(bytes32 x, uint256 v, uint256 mask, uint256 offset) external { + bytes32 y = x.set(v, mask, offset); + + bytes32 expected = x; + expected &= bytes32(~(mask << offset)); + expected |= bytes32((v & mask) << offset); + + assertEq(y, expected, "test_Set::1"); + } + + function testFuzz_Decode(bytes32 x, uint256 mask, uint256 offset) external { + uint256 v = x.decode(mask, offset); + assertEq(v, (uint256(x) >> offset) & mask, "test_Decode::1"); + } + + function testFuzz_SetAndDecode(bytes32 x, uint256 v, uint256 mask, uint256 offset) external { + bytes32 y = x.set(v, mask, offset); + uint256 v2 = y.decode(mask, offset); + + assertEq(v2, ((v << offset) >> offset) & mask, "test_SetAndDecode::1"); + } + + function testFuzz_decodeUint1(bytes32 x, uint256 offset) external { + uint256 v = x.decodeUint1(offset); + assertEq(v, (uint256(x) >> offset) & 1, "test_decodeUint1::1"); + } + + function testFuzz_decodeUint8(bytes32 x, uint256 offset) external { + uint256 v = x.decodeUint8(offset); + assertEq(v, (uint256(x) >> offset) & 0xff, "test_decodeUint8::1"); + } + + function testFuzz_decodeUint12(bytes32 x, uint256 offset) external { + uint256 v = x.decodeUint12(offset); + assertEq(v, (uint256(x) >> offset) & 0xfff, "test_decodeUint12::1"); + } + + function testFuzz_decodeUint14(bytes32 x, uint256 offset) external { + uint256 v = x.decodeUint14(offset); + assertEq(v, (uint256(x) >> offset) & 0x3fff, "test_decodeUint14::1"); + } + + function testFuzz_decodeUint16(bytes32 x, uint256 offset) external { + uint256 v = x.decodeUint16(offset); + assertEq(v, (uint256(x) >> offset) & 0xffff, "test_decodeUint16::1"); + } + + function testFuzz_decodeUint20(bytes32 x, uint256 offset) external { + uint256 v = x.decodeUint20(offset); + assertEq(v, (uint256(x) >> offset) & 0xfffff, "test_decodeUint20::1"); + } + + function testFuzz_decodeUint24(bytes32 x, uint256 offset) external { + uint256 v = x.decodeUint24(offset); + assertEq(v, (uint256(x) >> offset) & 0xffffff, "test_decodeUint24::1"); + } + + function testFuzz_decodeUint40(bytes32 x, uint256 offset) external { + uint256 v = x.decodeUint40(offset); + assertEq(v, (uint256(x) >> offset) & 0xffffffffff, "test_decodeUint40::1"); + } + + function testFuzz_decodeUint64(bytes32 x, uint256 offset) external { + uint256 v = x.decodeUint64(offset); + assertEq(v, (uint256(x) >> offset) & 0xffffffffffffffff, "test_decodeUint64::1"); + } + + function testFuzz_decodeUint128(bytes32 x, uint256 offset) external { + uint256 v = x.decodeUint128(offset); + assertEq(v, (uint256(x) >> offset) & 0xffffffffffffffffffffffffffffffff, "test_decodeUint128::1"); + } +} diff --git a/test/LiquidityConfigurations.t.sol b/test/libraries/math/LiquidityConfigurations.t.sol similarity index 97% rename from test/LiquidityConfigurations.t.sol rename to test/libraries/math/LiquidityConfigurations.t.sol index 6ed7fe1d..e26298ee 100644 --- a/test/LiquidityConfigurations.t.sol +++ b/test/libraries/math/LiquidityConfigurations.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; -import "../src/libraries/math/LiquidityConfigurations.sol"; +import "../../../src/libraries/math/LiquidityConfigurations.sol"; contract LiquidityConfigurationsTest is Test { using PackedUint128Math for bytes32; diff --git a/test/PackedUint128Math.t.sol b/test/libraries/math/PackedUint128Math.t.sol similarity index 91% rename from test/PackedUint128Math.t.sol rename to test/libraries/math/PackedUint128Math.t.sol index 599e297d..94891c72 100644 --- a/test/PackedUint128Math.t.sol +++ b/test/libraries/math/PackedUint128Math.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; -import "../src/libraries/math/PackedUint128Math.sol"; +import "../../../src/libraries/math/PackedUint128Math.sol"; contract PackedUint128MathTest is Test { using PackedUint128Math for bytes32; @@ -148,7 +148,7 @@ contract PackedUint128MathTest is Test { assertEq(x.gt(y), x1 > y1 || x2 > y2, "testFuzz_GreaterThan::1"); } - function testFuzz_ScalarMulShift128RoundUp(bytes32 x, uint128 multiplier) external { + function testFuzz_ScalarMulShiftRoundUp(bytes32 x, uint128 multiplier) external { (uint128 x1, uint128 x2) = x.decode(); uint256 y1 = uint256(x1) * multiplier; @@ -157,16 +157,21 @@ contract PackedUint128MathTest is Test { uint256 z1 = y1 == 0 ? 0 : ((y1 - 1) >> 128) + 1; uint256 z2 = y2 == 0 ? 0 : ((y2 - 1) >> 128) + 1; - assertLe(z1, type(uint128).max, "testFuzz_ScalarMulShift128RoundUp::1"); - assertLe(z2, type(uint128).max, "testFuzz_ScalarMulShift128RoundUp::2"); + assertLe(z1, type(uint128).max, "testFuzz_ScalarMulShiftRoundUp::1"); + assertLe(z2, type(uint128).max, "testFuzz_ScalarMulShiftRoundUp::2"); assertEq( - x.scalarMulShift128RoundUp(multiplier), - uint128(z1).encode(uint128(z2)), - "testFuzz_ScalarMulShift128RoundUp::3" + x.scalarMulShiftRoundUp(multiplier), uint128(z1).encode(uint128(z2)), "testFuzz_ScalarMulShiftRoundUp::3" ); } + function testFuzz_revert_ScalarMulShiftRoundUp(bytes32 x, uint256 multiplier) external { + vm.assume(multiplier > uint256(type(uint128).max) + 1); + + vm.expectRevert(PackedUint128Math.PackedUint128Math__MultiplierTooLarge.selector); + x.scalarMulShiftRoundUp(multiplier); + } + function testFuzz_ScalarMulDivBasisPointRoundDown(bytes32 x, uint128 multipilier) external { (uint128 x1, uint128 x2) = x.decode(); @@ -177,7 +182,7 @@ contract PackedUint128MathTest is Test { uint256 z2 = y2 / Constants.BASIS_POINT_MAX; if (multipilier > Constants.BASIS_POINT_MAX) { - vm.expectRevert(PackedUint128Math.PackedUint128Math__MultiplierBiggerThanMax.selector); + vm.expectRevert(PackedUint128Math.PackedUint128Math__MultiplierTooLarge.selector); x.scalarMulDivBasisPointRoundDown(multipilier); } else { assertLe(z1, type(uint128).max, "testFuzz_ScalarMulDivBasisPointRoundDown::1"); diff --git a/test/SafeCast.t.sol b/test/libraries/math/SafeCast.t.sol similarity index 99% rename from test/SafeCast.t.sol rename to test/libraries/math/SafeCast.t.sol index 2d3474b9..bdca6c33 100644 --- a/test/SafeCast.t.sol +++ b/test/libraries/math/SafeCast.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; -import "../src/libraries/math/SafeCast.sol"; +import "../../../src/libraries/math/SafeCast.sol"; contract SafeCastTest is Test { using SafeCast for uint256; diff --git a/test/SampleMath.t.sol b/test/libraries/math/SampleMath.t.sol similarity index 99% rename from test/SampleMath.t.sol rename to test/libraries/math/SampleMath.t.sol index 06961493..2135e826 100644 --- a/test/SampleMath.t.sol +++ b/test/libraries/math/SampleMath.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; -import "../src/libraries/math/SampleMath.sol"; +import "../../../src/libraries/math/SampleMath.sol"; contract SampleMathTest is Test { using SampleMath for bytes32; diff --git a/test/TreeMath.t.sol b/test/libraries/math/TreeMath.t.sol similarity index 98% rename from test/TreeMath.t.sol rename to test/libraries/math/TreeMath.t.sol index 25863f3a..64afff84 100644 --- a/test/TreeMath.t.sol +++ b/test/libraries/math/TreeMath.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; -import "../src/libraries/math/TreeMath.sol"; +import "../../../src/libraries/math/TreeMath.sol"; contract TreeMathTest is Test { using TreeMath for TreeMath.TreeUint24; diff --git a/test/Uint128x128Math.t.sol b/test/libraries/math/Uint128x128Math.t.sol similarity index 94% rename from test/Uint128x128Math.t.sol rename to test/libraries/math/Uint128x128Math.t.sol index 8fe888fb..3afa3738 100644 --- a/test/Uint128x128Math.t.sol +++ b/test/libraries/math/Uint128x128Math.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; -import "../src/libraries/math/Uint128x128Math.sol"; +import "../../../src/libraries/math/Uint128x128Math.sol"; contract Uint128x128MathTest is Test { using Uint128x128Math for uint256; diff --git a/test/Uint256x256Math.t.sol b/test/libraries/math/Uint256x256Math.t.sol similarity index 99% rename from test/Uint256x256Math.t.sol rename to test/libraries/math/Uint256x256Math.t.sol index 0429872a..237537d1 100644 --- a/test/Uint256x256Math.t.sol +++ b/test/libraries/math/Uint256x256Math.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; import "forge-std/Test.sol"; -import "../src/libraries/math/Uint256x256Math.sol"; +import "../../../src/libraries/math/Uint256x256Math.sol"; contract Uint256x256MathTest is Test { using Uint256x256Math for uint256; From a5290980696d1209da01cce251b14093fe691252 Mon Sep 17 00:00:00 2001 From: Louis <107303182+0x0Louis@users.noreply.github.com> Date: Sat, 4 Feb 2023 14:34:49 +0100 Subject: [PATCH 13/47] Pair test and fixes (#78) * fix typo for volatility accumulator * add getter for oracle parameter * test implementation * fix edge case on oracle * fix getSwapOut * test lbPair initial state * fix typos * test and fix for flasloan * fix lbfactory test * fix and test binHelper * test and fix oracle helper * test and fix pair parameter helper * test and fix lbpair * add slither files --- .gitignore | 1 + .vscode/settings.json | 5 +- slither.config.json | 8 + src/LBFactory.sol | 38 +- src/LBPair.sol | 155 ++++--- src/interfaces/ILBFactory.sol | 10 +- src/interfaces/ILBLegacyFactory.sol | 8 +- src/interfaces/ILBPair.sol | 20 +- src/libraries/BinHelper.sol | 17 +- src/libraries/OracleHelper.sol | 52 ++- src/libraries/PairParameterHelper.sol | 55 +-- src/libraries/math/SampleMath.sol | 16 +- test/LBFactory.t.sol | 76 ++-- test/LBPairFees.t.sol | 546 +++++++++++++++++++++++ test/LBPairFlashloan.t.sol | 105 +++++ test/LBPairImplementation.t.sol | 38 ++ test/LBPairInitialState.t.sol | 226 ++++++++++ test/LBPairLiquidity.t.sol | 335 ++++++++++++++ test/LBPairOracle.t.sol | 265 +++++++++++ test/LBPairSwap.t.sol | 135 ++++++ test/helpers/TestHelper.sol | 83 +++- test/libraries/BinHelper.t.sol | 30 +- test/libraries/OracleHelper.t.sol | 2 +- test/libraries/PairParameterHelper.t.sol | 55 +-- test/libraries/math/SampleMath.t.sol | 18 +- test/mocks/FlashBorrower.sol | 66 +++ test/mocks/FlashloanBorrower.sol | 82 ---- 27 files changed, 2118 insertions(+), 329 deletions(-) create mode 100644 slither.config.json create mode 100644 test/LBPairFees.t.sol create mode 100644 test/LBPairFlashloan.t.sol create mode 100644 test/LBPairImplementation.t.sol create mode 100644 test/LBPairInitialState.t.sol create mode 100644 test/LBPairLiquidity.t.sol create mode 100644 test/LBPairOracle.t.sol create mode 100644 test/LBPairSwap.t.sol create mode 100644 test/mocks/FlashBorrower.sol delete mode 100644 test/mocks/FlashloanBorrower.sol diff --git a/.gitignore b/.gitignore index 6aa8a770..0906d6fa 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ cache out broadcast +slither-results.json diff --git a/.vscode/settings.json b/.vscode/settings.json index b7e2454b..89ef3c0c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "solidity.formatter": "forge" + "solidity.formatter": "forge", + "slither.solcPath": "", + "slither.hiddenDetectors": [], + "coverage-gutters.showLineCoverage": true } \ No newline at end of file diff --git a/slither.config.json b/slither.config.json new file mode 100644 index 00000000..fe1f1bbc --- /dev/null +++ b/slither.config.json @@ -0,0 +1,8 @@ +{ + "filter_paths": "(lib/|test/|script/)", + "solc_remaps": [ + "ds-test/=lib/ds-test/src/", + "forge-std/=lib/forge-std/src/", + "openzeppelin/=lib/openzeppelin-contracts/contracts/" + ] +} diff --git a/src/LBFactory.sol b/src/LBFactory.sol index f1916f45..0c7ba4ab 100644 --- a/src/LBFactory.sol +++ b/src/LBFactory.sol @@ -98,7 +98,7 @@ contract LBFactory is PendingOwnable, ILBFactory { return _MAX_PROTOCOL_SHARE; } - function getFlashloanFee() external view returns (uint256 flashloanFee) { + function getFlashLoanFee() external view returns (uint256 flashloanFee) { return _flashLoanFee; } @@ -174,7 +174,7 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @return reductionFactor The reduction factor of the preset /// @return variableFeeControl The variable fee control of the preset /// @return protocolShare The protocol share of the preset - /// @return maxVolatilityAccumulated The max volatility accumulated of the preset + /// @return maxVolatilityAccumulator The max volatility accumulator of the preset function getPreset(uint256 binStep) external view @@ -186,7 +186,7 @@ contract LBFactory is PendingOwnable, ILBFactory { uint256 reductionFactor, uint256 variableFeeControl, uint256 protocolShare, - uint256 maxVolatilityAccumulated + uint256 maxVolatilityAccumulator ) { bytes32 preset = _presets[binStep]; @@ -198,7 +198,7 @@ contract LBFactory is PendingOwnable, ILBFactory { reductionFactor = preset.getReductionFactor(); variableFeeControl = preset.getVariableFeeControl(); protocolShare = preset.getProtocolShare(); - maxVolatilityAccumulated = preset.getMaxVolatilityAccumulated(); + maxVolatilityAccumulator = preset.getMaxVolatilityAccumulator(); } /// @notice View function to return the list of available binStep with a preset @@ -350,7 +350,7 @@ contract LBFactory is PendingOwnable, ILBFactory { preset.getReductionFactor(), preset.getVariableFeeControl(), preset.getProtocolShare(), - preset.getMaxVolatilityAccumulated(), + preset.getMaxVolatilityAccumulator(), activeId ); } @@ -426,7 +426,7 @@ contract LBFactory is PendingOwnable, ILBFactory { preset.getReductionFactor(), preset.getVariableFeeControl(), preset.getProtocolShare(), - preset.getMaxVolatilityAccumulated(), + preset.getMaxVolatilityAccumulator(), oldLBPair.getActiveId() ); @@ -483,7 +483,7 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @param reductionFactor The reduction factor, used to calculate the reduction of the accumulator /// @param variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it /// @param protocolShare The share of the fees received by the protocol - /// @param maxVolatilityAccumulated The max value of the volatility accumulated + /// @param maxVolatilityAccumulator The max value of the volatility accumulator function setPreset( uint8 binStep, uint16 baseFactor, @@ -492,7 +492,7 @@ contract LBFactory is PendingOwnable, ILBFactory { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ) external override onlyOwner { bytes32 packedFeeParameters = _getPackedFeeParameters( binStep, @@ -502,7 +502,7 @@ contract LBFactory is PendingOwnable, ILBFactory { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); bytes32 preset = bytes32((uint256(packedFeeParameters))); @@ -529,7 +529,7 @@ contract LBFactory is PendingOwnable, ILBFactory { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); } @@ -562,7 +562,7 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @param reductionFactor The reduction factor, used to calculate the reduction of the accumulator /// @param variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it /// @param protocolShare The share of the fees received by the protocol - /// @param maxVolatilityAccumulated The max value of volatility accumulated + /// @param maxVolatilityAccumulator The max value of volatility accumulator function setFeesParametersOnPair( IERC20 tokenX, IERC20 tokenY, @@ -574,7 +574,7 @@ contract LBFactory is PendingOwnable, ILBFactory { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ) external override onlyOwner { ILBPair lbPair = _getLBPairInformation(tokenX, tokenY, binStep, revision).LBPair; @@ -585,7 +585,7 @@ contract LBFactory is PendingOwnable, ILBFactory { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); emit FeeParametersSet( @@ -598,7 +598,7 @@ contract LBFactory is PendingOwnable, ILBFactory { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); } @@ -670,7 +670,7 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @param reductionFactor The reduction factor, used to calculate the reduction of the accumulator /// @param variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it /// @param protocolShare The share of the fees received by the protocol - /// @param maxVolatilityAccumulated The max value of volatility accumulated + /// @param maxVolatilityAccumulator The max value of volatility accumulator function _getPackedFeeParameters( uint8 binStep, uint16 baseFactor, @@ -679,7 +679,7 @@ contract LBFactory is PendingOwnable, ILBFactory { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ) private pure returns (bytes32 preset) { if (binStep < _MIN_BIN_STEP || binStep > _MAX_BIN_STEP) { revert LBFactory__BinStepRequirementsBreached(_MIN_BIN_STEP, binStep, _MAX_BIN_STEP); @@ -700,8 +700,8 @@ contract LBFactory is PendingOwnable, ILBFactory { // Can't overflow as the max value is `max(uint24) * (max(uint24) * max(uint16)) ** 2 < max(uint104)` // It returns 18 decimals as: - // decimals(variableFeeControl * (volatilityAccumulated * binStep)**2 / 100) = 4 + (4 + 4) * 2 - 2 = 18 - uint256 prod = uint256(maxVolatilityAccumulated) * binStep; + // decimals(variableFeeControl * (volatilityAccumulator * binStep)**2 / 100) = 4 + (4 + 4) * 2 - 2 = 18 + uint256 prod = uint256(maxVolatilityAccumulator) * binStep; uint256 maxVariableFee = (prod * prod * variableFeeControl) / 100; if (baseFee + maxVariableFee > _MAX_FEE) { @@ -716,7 +716,7 @@ contract LBFactory is PendingOwnable, ILBFactory { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); } diff --git a/src/LBPair.sol b/src/LBPair.sol index 0144b3b4..9d522fb0 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -19,6 +19,7 @@ import {PairParameterHelper} from "./libraries/PairParameterHelper.sol"; import {PriceHelper} from "./libraries/PriceHelper.sol"; import {ReentrancyGuard} from "./libraries/ReentrancyGuard.sol"; import {SafeCast} from "./libraries/math/SafeCast.sol"; +import {SampleMath} from "./libraries/math/SampleMath.sol"; import {TreeMath} from "./libraries/math/TreeMath.sol"; import {Uint256x256Math} from "./libraries/math/Uint256x256Math.sol"; @@ -38,6 +39,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { using PriceHelper for uint256; using PriceHelper for uint24; using SafeCast for uint256; + using SampleMath for bytes32; using TreeMath for TreeMath.TreeUint24; using Uint256x256Math for uint256; @@ -46,8 +48,8 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { _; } - modifier onlyProtocolFeeReceiver() { - if (msg.sender != _factory.getFeeRecipient()) revert LBPair__OnlyProtocolFeeReceiver(); + modifier onlyProtocolFeeRecipient() { + if (msg.sender != _factory.getFeeRecipient()) revert LBPair__OnlyProtocolFeeRecipient(); _; } @@ -69,6 +71,9 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { */ constructor(ILBFactory factory_) { _factory = factory_; + + // Disable the initialize function + _parameters = bytes32(uint256(1)); } /** @@ -80,7 +85,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { * @param reductionFactor The reduction factor for the static fee * @param variableFeeControl The variable fee control for the static fee * @param protocolShare The protocol share for the static fee - * @param maxVolatilityAccumulated The max volatility accumulated for the static fee + * @param maxVolatilityAccumulator The max volatility accumulator for the static fee * @param activeId The active id of the Liquidity Book Pair */ function initialize( @@ -90,7 +95,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated, + uint24 maxVolatilityAccumulator, uint24 activeId ) external override onlyFactory { bytes32 parameters = _parameters; @@ -99,14 +104,14 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { __ReentrancyGuard_init(); _setStaticFeeParameters( - parameters.setActiveId(activeId), + parameters.setActiveId(activeId).updateIdReference(), baseFactor, filterPeriod, decayPeriod, reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); } @@ -205,7 +210,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { * @return reductionFactor The reduction factor for the static fee * @return variableFeeControl The variable fee control for the static fee * @return protocolShare The protocol share for the static fee - * @return maxVolatilityAccumulated The maximum volatility accumulated for the static fee + * @return maxVolatilityAccumulator The maximum volatility accumulator for the static fee */ function getStaticFeeParameters() external @@ -218,7 +223,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ) { bytes32 parameters = _parameters; @@ -229,12 +234,12 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { reductionFactor = parameters.getReductionFactor(); variableFeeControl = parameters.getVariableFeeControl(); protocolShare = parameters.getProtocolShare(); - maxVolatilityAccumulated = parameters.getMaxVolatilityAccumulated(); + maxVolatilityAccumulator = parameters.getMaxVolatilityAccumulator(); } /** * @notice Returns the variable fee parameters of the Liquidity Book Pair - * @return volatilityAccumulated The volatility accumulated for the variable fee + * @return volatilityAccumulator The volatility accumulator for the variable fee * @return volatilityReference The volatility reference for the variable fee * @return idReference The id reference for the variable fee * @return timeOfLastUpdate The time of last update for the variable fee @@ -243,16 +248,53 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { external view override - returns (uint24 volatilityAccumulated, uint24 volatilityReference, uint24 idReference, uint40 timeOfLastUpdate) + returns (uint24 volatilityAccumulator, uint24 volatilityReference, uint24 idReference, uint40 timeOfLastUpdate) { bytes32 parameters = _parameters; - volatilityAccumulated = parameters.getVolatilityAccumulated(); + volatilityAccumulator = parameters.getVolatilityAccumulator(); volatilityReference = parameters.getVolatilityReference(); idReference = parameters.getIdReference(); timeOfLastUpdate = parameters.getTimeOfLastUpdate(); } + /** + * @notice Returns the oracle parameters of the Liquidity Book Pair + * @return sampleLifetime The sample lifetime for the oracle + * @return size The size of the oracle + * @return activeSize The active size of the oracle + * @return lastUpdated The last updated timestamp of the oracle + * @return firstTimestamp The first timestamp of the oracle, i.e. the timestamp of the oldest sample + */ + function getOracleParameters() + external + view + override + returns (uint8 sampleLifetime, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp) + { + bytes32 parameters = _parameters; + + sampleLifetime = uint8(OracleHelper._MAX_SAMPLE_LIFETIME); + + uint16 oracleId = parameters.getOracleId(); + if (oracleId > 0) { + bytes32 sample; + (sample, activeSize) = _oracle.getActiveSampleAndSize(oracleId); + + size = sample.getOracleLength(); + lastUpdated = sample.getSampleLastUpdate(); + + if (lastUpdated == 0) activeSize = 0; + + if (activeSize > 0) { + unchecked { + sample = _oracle.getSample(1 + (oracleId % activeSize)); + } + firstTimestamp = sample.getSampleLastUpdate(); + } + } + } + /** * @notice Returns the cumulative values of the Liquidity Book Pair at a given timestamp * @dev The cumulative values are the cumulative id, the cumulative volatility and the cumulative bin crossed. @@ -268,12 +310,13 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { returns (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) { bytes32 parameters = _parameters; + uint16 oracleId = parameters.getOracleId(); - if (lookupTimestamp > block.timestamp) return (0, 0, 0); + if (oracleId == 0 || lookupTimestamp > block.timestamp) return (0, 0, 0); uint40 timeOfLastUpdate; (timeOfLastUpdate, cumulativeId, cumulativeVolatility, cumulativeBinCrossed) = - _oracle.getSampleAt(parameters.getOracleId(), lookupTimestamp); + _oracle.getSampleAt(oracleId, lookupTimestamp); if (timeOfLastUpdate < lookupTimestamp) { parameters.updateVolatilityParameters(parameters.getActiveId()); @@ -281,7 +324,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { uint40 deltaTime = lookupTimestamp - timeOfLastUpdate; cumulativeId += uint64(parameters.getIdReference()) * deltaTime; - cumulativeVolatility += uint64(parameters.getVolatilityAccumulated()) * deltaTime; + cumulativeVolatility += uint64(parameters.getVolatilityAccumulator()) * deltaTime; } } @@ -312,7 +355,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { * and the maximum amount that can be swapped from `amountIn` is `amountOut - amountOutLeft`. * @param amountOut The amount of token X or Y to swap in * @param swapForY Whether the swap is for token Y (true) or token X (false) - * @return amountIn The amount of token X or Y that can be swapped in + * @return amountIn The amount of token X or Y that can be swapped in, including the fee * @return amountOutLeft The amount of token Y or X that cannot be swapped out * @return fee The fee of the swap */ @@ -332,24 +375,24 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { parameters = parameters.updateReferences(); while (true) { - uint128 binReserves = _bins[id].decode(swapForY); + uint128 binReserves = _bins[id].decode(!swapForY); if (binReserves > 0) { uint256 price = id.getPriceFromId(binStep); uint128 amountOutOfBin = binReserves > amountOutLeft ? amountOutLeft : binReserves; - parameters.updateVolatilityParameters(id); + parameters = parameters.updateVolatilityParameters(id); - uint128 amountInToBin = uint128( + uint128 amountInWithoutFee = uint128( swapForY ? uint256(amountOutOfBin).shiftDivRoundUp(Constants.SCALE_OFFSET, price) : uint256(amountOutOfBin).mulShiftRoundUp(price, Constants.SCALE_OFFSET) ); uint128 totalFee = parameters.getTotalFee(binStep); - uint128 feeAmount = amountOutOfBin.getFeeAmount(totalFee); + uint128 feeAmount = amountInWithoutFee.getFeeAmount(totalFee); - amountIn += amountInToBin + feeAmount; + amountIn += amountInWithoutFee + feeAmount; amountOutLeft -= amountOutOfBin; fee += feeAmount; @@ -394,14 +437,14 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { while (true) { bytes32 binReserves = _bins[id]; - if (!binReserves.isEmpty(swapForY)) { - parameters = parameters.updateVolatilityAccumulated(id); + if (!binReserves.isEmpty(!swapForY)) { + parameters = parameters.updateVolatilityAccumulator(id); - (bytes32 amountsInToBin, bytes32 amountsOutOfBin, bytes32 totalFees) = + (bytes32 amountsInWithFees, bytes32 amountsOutOfBin, bytes32 totalFees) = binReserves.getAmounts(parameters, binStep, swapForY, id, amountsInLeft); - if (amountsInToBin > 0) { - amountsInLeft = amountsInLeft.sub(amountsInToBin.add(totalFees)); + if (amountsInWithFees > 0) { + amountsInLeft = amountsInLeft.sub(amountsInWithFees); amountOut += amountsOutOfBin.decode(!swapForY); @@ -420,7 +463,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { } } - amountInLeft = amountsInLeft.decode(!swapForY); + amountInLeft = amountsInLeft.decode(swapForY); } /** @@ -441,6 +484,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { bytes32 protocolFees = _protocolFees; bytes32 amountsLeft = swapForY ? reserves.receivedX(_tokenX()) : reserves.receivedY(_tokenY()); + reserves = reserves.add(amountsLeft); if (amountsLeft == 0) revert LBPair__InsufficientAmountIn(); @@ -453,30 +497,32 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { while (true) { bytes32 binReserves = _bins[activeId]; - if (!binReserves.isEmpty(swapForY)) { - parameters = parameters.updateVolatilityAccumulated(activeId); + if (!binReserves.isEmpty(!swapForY)) { + parameters = parameters.updateVolatilityAccumulator(activeId); - (bytes32 amountsInToBin, bytes32 amountsOutOfBin, bytes32 totalFees) = + (bytes32 amountsInWithFees, bytes32 amountsOutOfBin, bytes32 totalFees) = binReserves.getAmounts(parameters, binStep, swapForY, activeId, amountsLeft); - if (amountsInToBin > 0) { - amountsLeft = amountsLeft.sub(amountsInToBin); - reserves.add(amountsInToBin.add(totalFees)); - + if (amountsInWithFees > 0) { + amountsLeft = amountsLeft.sub(amountsInWithFees); amountsOut = amountsOut.add(amountsOutOfBin); bytes32 pFees = totalFees.scalarMulDivBasisPointRoundDown(parameters.getProtocolShare()); - protocolFees = protocolFees.add(pFees); - _bins[activeId] = binReserves.add(amountsInToBin).sub(amountsOutOfBin); + if (pFees > 0) { + protocolFees = protocolFees.add(pFees); + amountsInWithFees = amountsInWithFees.sub(pFees); + } + + _bins[activeId] = binReserves.add(amountsInWithFees).sub(amountsOutOfBin); emit Swap( msg.sender, to, activeId, - amountsInToBin, + amountsInWithFees, amountsOutOfBin, - parameters.getVolatilityAccumulated(), + parameters.getVolatilityAccumulator(), totalFees, pFees ); @@ -496,9 +542,10 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { if (amountsOut == 0) revert LBPair__InsufficientAmountOut(); - parameters = _oracle.update(parameters, activeId); - _reserves = reserves.sub(amountsOut); + _protocolFees = protocolFees; + + parameters = _oracle.update(parameters, activeId); _parameters = parameters.setActiveId(activeId); if (swapForY) { @@ -521,6 +568,8 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { override nonReentrant { + if (amounts == 0) revert LBPair__ZeroBorrowAmount(); + bytes32 reservesBefore = _reserves; bytes32 parameters = _parameters; @@ -539,7 +588,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { if (balancesAfter.lt(reservesBefore.add(totalFees))) revert LBPair__FlashLoanInsufficientAmount(); - totalFees = reservesBefore.sub(balancesAfter); + totalFees = balancesAfter.sub(reservesBefore); bytes32 protocolFees = totalFees.scalarMulDivBasisPointRoundDown(parameters.getProtocolShare()); uint24 activeId = parameters.getActiveId(); @@ -678,19 +727,19 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { external override nonReentrant - onlyProtocolFeeReceiver + onlyProtocolFeeRecipient returns (bytes32 collectedProtocolFees) { bytes32 protocolFees = _protocolFees; (uint128 x, uint128 y) = protocolFees.decode(); - bytes32 ones = uint128(x > 1 ? 1 : 0).encode(uint128(y > 1 ? 1 : 0)); + bytes32 ones = uint128(x > 0 ? 1 : 0).encode(uint128(y > 0 ? 1 : 0)); collectedProtocolFees = protocolFees.sub(ones); if (collectedProtocolFees != 0) { _protocolFees = ones; - _reserves.sub(collectedProtocolFees); + _reserves = _reserves.sub(collectedProtocolFees); collectedProtocolFees.transfer(_tokenX(), _tokenY(), msg.sender); @@ -727,7 +776,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { * @param reductionFactor The reduction factor of the static fee * @param variableFeeControl The variable fee control of the static fee * @param protocolShare The protocol share of the static fee - * @param maxVolatilityAccumulated The max volatility accumulated of the static fee + * @param maxVolatilityAccumulator The max volatility accumulator of the static fee */ function setStaticFeeParameters( uint16 baseFactor, @@ -736,7 +785,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ) external override onlyFactory { _setStaticFeeParameters( _parameters, @@ -746,7 +795,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); } @@ -802,7 +851,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { * @return The encoded fees amounts */ function _getFlashLoanFees(bytes32 amounts) private view returns (bytes32) { - uint128 fee = uint128(_factory.getFlashloanFee()); + uint128 fee = uint128(_factory.getFlashLoanFee()); (uint128 x, uint128 y) = amounts.decode(); unchecked { @@ -823,7 +872,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { * @param reductionFactor The reduction factor of the static fee * @param variableFeeControl The variable fee control of the static fee * @param protocolShare The protocol share of the static fee - * @param maxVolatilityAccumulated The max volatility accumulated of the static fee + * @param maxVolatilityAccumulator The max volatility accumulator of the static fee */ function _setStaticFeeParameters( bytes32 parameters, @@ -833,11 +882,11 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ) internal { if ( baseFactor == 0 && filterPeriod == 0 && decayPeriod == 0 && reductionFactor == 0 && variableFeeControl == 0 - && protocolShare == 0 && maxVolatilityAccumulated == 0 + && protocolShare == 0 && maxVolatilityAccumulator == 0 ) { revert LBPair__InvalidStaticFeeParameters(); } @@ -849,7 +898,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); emit StaticFeeParametersSet( @@ -860,7 +909,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); } diff --git a/src/interfaces/ILBFactory.sol b/src/interfaces/ILBFactory.sol index 04d01884..21bc3b63 100644 --- a/src/interfaces/ILBFactory.sol +++ b/src/interfaces/ILBFactory.sol @@ -70,7 +70,7 @@ interface ILBFactory is IPendingOwnable { uint256 reductionFactor, uint256 variableFeeControl, uint256 protocolShare, - uint256 maxVolatilityAccumulated + uint256 maxVolatilityAccumulator ); event FactoryLockedStatusUpdated(bool unlocked); @@ -87,7 +87,7 @@ interface ILBFactory is IPendingOwnable { uint256 reductionFactor, uint256 variableFeeControl, uint256 protocolShare, - uint256 maxVolatilityAccumulated + uint256 maxVolatilityAccumulator ); event PresetRemoved(uint256 indexed binStep); @@ -114,7 +114,7 @@ interface ILBFactory is IPendingOwnable { function getFeeRecipient() external view returns (address); - function getFlashloanFee() external view returns (uint256); + function getFlashLoanFee() external view returns (uint256); function isCreationUnlocked() external view returns (bool); @@ -167,7 +167,7 @@ interface ILBFactory is IPendingOwnable { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ) external; function removePreset(uint8 binStep) external; @@ -183,7 +183,7 @@ interface ILBFactory is IPendingOwnable { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ) external; function setFeeRecipient(address feeRecipient) external; diff --git a/src/interfaces/ILBLegacyFactory.sol b/src/interfaces/ILBLegacyFactory.sol index 0934a2c9..4642233c 100644 --- a/src/interfaces/ILBLegacyFactory.sol +++ b/src/interfaces/ILBLegacyFactory.sol @@ -41,7 +41,7 @@ interface ILBLegacyFactory is IPendingOwnable { uint256 reductionFactor, uint256 variableFeeControl, uint256 protocolShare, - uint256 maxVolatilityAccumulated + uint256 maxVolatilityAccumulator ); event FactoryLockedStatusUpdated(bool unlocked); @@ -58,7 +58,7 @@ interface ILBLegacyFactory is IPendingOwnable { uint256 reductionFactor, uint256 variableFeeControl, uint256 protocolShare, - uint256 maxVolatilityAccumulated, + uint256 maxVolatilityAccumulator, uint256 sampleLifetime ); @@ -136,7 +136,7 @@ interface ILBLegacyFactory is IPendingOwnable { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated, + uint24 maxVolatilityAccumulator, uint16 sampleLifetime ) external; @@ -152,7 +152,7 @@ interface ILBLegacyFactory is IPendingOwnable { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ) external; function setFeeRecipient(address feeRecipient) external; diff --git a/src/interfaces/ILBPair.sol b/src/interfaces/ILBPair.sol index 516db1f3..b176a80c 100644 --- a/src/interfaces/ILBPair.sol +++ b/src/interfaces/ILBPair.sol @@ -9,6 +9,7 @@ import {ILBFlashLoanCallback} from "./ILBFlashLoanCallback.sol"; import {ILBToken} from "./ILBToken.sol"; interface ILBPair is ILBToken { + error LBPair__ZeroBorrowAmount(); error LBPair__AddressZero(); error LBPair__AlreadyInitialized(); error LBPair__EmptyMarketConfigs(); @@ -19,7 +20,7 @@ interface ILBPair is ILBToken { error LBPair__InvalidInput(); error LBPair__InvalidStaticFeeParameters(); error LBPair__OnlyFactory(); - error LBPair__OnlyProtocolFeeReceiver(); + error LBPair__OnlyProtocolFeeRecipient(); error LBPair__OutOfLiquidity(); error LBPair__TokenNotSupported(); error LBPair__ZeroAmount(uint24 id); @@ -40,7 +41,7 @@ interface ILBPair is ILBToken { uint24 id, bytes32 amountsIn, bytes32 amountsOut, - uint24 volatilityAccumulated, + uint24 volatilityAccumulator, bytes32 totalFees, bytes32 protocolFees ); @@ -53,7 +54,7 @@ interface ILBPair is ILBToken { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ); event FlashLoan( @@ -76,7 +77,7 @@ interface ILBPair is ILBToken { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated, + uint24 maxVolatilityAccumulator, uint24 activeId ) external; @@ -108,13 +109,18 @@ interface ILBPair is ILBToken { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ); function getVariableFeeParameters() external view - returns (uint24 volatilityAccumulated, uint24 volatilityReference, uint24 idReference, uint40 timeOfLastUpdate); + returns (uint24 volatilityAccumulator, uint24 volatilityReference, uint24 idReference, uint40 timeOfLastUpdate); + + function getOracleParameters() + external + view + returns (uint8 sampleLifetime, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp); function getOracleSampleAt(uint40 lookupTimestamp) external @@ -158,7 +164,7 @@ interface ILBPair is ILBToken { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ) external; function forceDecay() external; diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index fd151b8c..f74d097b 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -183,7 +183,7 @@ library BinHelper { * @param swapForY Whether the swap is for Y (true) or for X (false) * @param activeId The id of the active bin * @param amountsInLeft The amounts of tokens left to swap - * @return amountsInToBin The encoded amounts of tokens that will be added to the bin + * @return amountsInWithFees The encoded amounts of tokens that will be added to the bin, including fees * @return amountsOutOfBin The encoded amounts of tokens that will be removed from the bin * @return totalFees The encoded fees that will be charged */ @@ -194,7 +194,7 @@ library BinHelper { bool swapForY, // swap `swapForY` and `activeId` to avoid stack too deep uint24 activeId, bytes32 amountsInLeft - ) internal pure returns (bytes32 amountsInToBin, bytes32 amountsOutOfBin, bytes32 totalFees) { + ) internal pure returns (bytes32 amountsInWithFees, bytes32 amountsOutOfBin, bytes32 totalFees) { uint256 price = activeId.getPriceFromId(binStep); uint128 binReserveOut = binReserves.decode(!swapForY); @@ -206,27 +206,30 @@ library BinHelper { uint128 totalFee = parameters.getTotalFee(binStep); uint128 maxFee = maxAmountIn.getFeeAmount(totalFee); + maxAmountIn += maxFee; + uint128 amountIn128 = amountsInLeft.decode(swapForY); uint128 fee128; uint128 amountOut128; - if (amountIn128 >= maxAmountIn + maxFee) { + if (amountIn128 >= maxAmountIn) { fee128 = maxFee; amountIn128 = maxAmountIn; amountOut128 = binReserveOut; } else { fee128 = amountIn128.getFeeAmountFrom(totalFee); - amountIn128 -= fee128; + + uint256 amountIn = amountIn128 - fee128; amountOut128 = swapForY - ? uint256(amountIn128).mulShiftRoundDown(price, Constants.SCALE_OFFSET).safe128() - : uint256(amountIn128).shiftDivRoundDown(Constants.SCALE_OFFSET, price).safe128(); + ? uint256(amountIn).mulShiftRoundDown(price, Constants.SCALE_OFFSET).safe128() + : uint256(amountIn).shiftDivRoundDown(Constants.SCALE_OFFSET, price).safe128(); if (amountOut128 > binReserveOut) amountOut128 = binReserveOut; } - (amountsInToBin, amountsOutOfBin, totalFees) = swapForY + (amountsInWithFees, amountsOutOfBin, totalFees) = swapForY ? (amountIn128.encodeFirst(), amountOut128.encodeSecond(), fee128.encodeFirst()) : (amountIn128.encodeSecond(), amountOut128.encodeFirst(), fee128.encodeSecond()); } diff --git a/src/libraries/OracleHelper.sol b/src/libraries/OracleHelper.sol index 3951d58e..eadc4462 100644 --- a/src/libraries/OracleHelper.sol +++ b/src/libraries/OracleHelper.sol @@ -14,7 +14,7 @@ import {PairParameterHelper} from "./PairParameterHelper.sol"; * Each sample is encoded as follows: * 0 - 16: oracle length (16 bits) * 16 - 80: cumulative id (64 bits) - * 80 - 144: cumulative volatility accumulated (64 bits) + * 80 - 144: cumulative volatility accumulator (64 bits) * 144 - 208: cumulative bin crossed (64 bits) * 208 - 216: sample lifetime (8 bits) * 216 - 256: sample creation timestamp (40 bits) @@ -113,9 +113,7 @@ library OracleHelper { } else { lastUpdate = lookUpTimestamp; } - (bytes32 prevSample, bytes32 nextSample) = binarySearch(oracle, oracleId, lookUpTimestamp, activeSize); - uint40 weightPrev = nextSample.getSampleLastUpdate() - lookUpTimestamp; uint40 weightNext = lookUpTimestamp - prevSample.getSampleLastUpdate(); @@ -137,15 +135,15 @@ library OracleHelper { view returns (bytes32, bytes32) { - uint16 low = 0; - uint16 high = length - 1; + uint256 low = 0; + uint256 high = length - 1; bytes32 sample; uint40 sampleLastUpdate; uint256 startId = oracleId; // oracleId is 1-based while (low <= high) { - uint16 mid = (low + high) >> 1; + uint256 mid = (low + high) >> 1; assembly { oracleId := addmod(startId, mid, length) @@ -206,33 +204,39 @@ library OracleHelper { bytes32 sample = getSample(oracle, oracleId); uint40 createdAt = sample.getSampleCreation(); - uint40 deltaTime = block.timestamp.safe40() - createdAt; + uint40 lastUpdatedAt = createdAt + sample.getSampleLifetime(); - if (deltaTime > 0) { - (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = sample.update( - deltaTime, activeId, parameters.getVolatilityAccumulated(), parameters.getDeltaId(activeId) - ); + if (block.timestamp.safe40() > lastUpdatedAt) { + unchecked { + (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = sample.update( + uint40(block.timestamp - lastUpdatedAt), + activeId, + parameters.getVolatilityAccumulator(), + parameters.getDeltaId(activeId) + ); - uint16 length = sample.getOracleLength(); + uint16 length = sample.getOracleLength(); + uint256 lifetime = block.timestamp - createdAt; - if (deltaTime > _MAX_SAMPLE_LIFETIME) { - assembly { - oracleId := add(mod(oracleId, length), 1) - } + if (lifetime > _MAX_SAMPLE_LIFETIME) { + assembly { + oracleId := add(mod(oracleId, length), 1) + } - deltaTime = 0; - createdAt = uint40(block.timestamp); + lifetime = 0; + createdAt = uint40(block.timestamp); - parameters = parameters.setOracleId(oracleId); + parameters = parameters.setOracleId(oracleId); + } + + sample = SampleMath.encode( + length, cumulativeId, cumulativeVolatility, cumulativeBinCrossed, uint8(lifetime), createdAt + ); } - sample = SampleMath.encode( - length, cumulativeId, cumulativeVolatility, cumulativeBinCrossed, uint8(deltaTime), createdAt - ); + setSample(oracle, oracleId, sample); } - setSample(oracle, oracleId, sample); - return parameters; } diff --git a/src/libraries/PairParameterHelper.sol b/src/libraries/PairParameterHelper.sol index 8a11e367..09d23159 100644 --- a/src/libraries/PairParameterHelper.sol +++ b/src/libraries/PairParameterHelper.sol @@ -17,8 +17,8 @@ import {Encoded} from "./math/Encoded.sol"; * [40 - 54[: reduction factor (14 bits) * [54 - 78[: variable fee control (24 bits) * [78 - 92[: protocol share (14 bits) - * [92 - 112[: max volatility accumulated (20 bits) - * [112 - 132[: volatility accumulated (20 bits) + * [92 - 112[: max volatility accumulator (20 bits) + * [112 - 132[: volatility accumulator (20 bits) * [132 - 152[: volatility reference (20 bits) * [152 - 176[: index reference (24 bits) * [176 - 216[: time of last update (40 bits) @@ -120,27 +120,27 @@ library PairParameterHelper { } /** - * @dev Get the max volatility accumulated from the encoded pair parameters + * @dev Get the max volatility accumulator from the encoded pair parameters * @param params The encoded pair parameters, as follows: * [0 - 92[: other parameters - * [92 - 112[: max volatility accumulated (20 bits) + * [92 - 112[: max volatility accumulator (20 bits) * [112 - 256[: other parameters - * @return maxVolatilityAccumulated The max volatility accumulated + * @return maxVolatilityAccumulator The max volatility accumulator */ - function getMaxVolatilityAccumulated(bytes32 params) internal pure returns (uint24 maxVolatilityAccumulated) { - maxVolatilityAccumulated = params.decodeUint20(OFFSET_MAX_VOL_ACC); + function getMaxVolatilityAccumulator(bytes32 params) internal pure returns (uint24 maxVolatilityAccumulator) { + maxVolatilityAccumulator = params.decodeUint20(OFFSET_MAX_VOL_ACC); } /** - * @dev Get the volatility accumulated from the encoded pair parameters + * @dev Get the volatility accumulator from the encoded pair parameters * @param params The encoded pair parameters, as follows: * [0 - 112[: other parameters - * [112 - 132[: volatility accumulated (20 bits) + * [112 - 132[: volatility accumulator (20 bits) * [132 - 256[: other parameters - * @return volatilityAccumulated The volatility accumulated + * @return volatilityAccumulator The volatility accumulator */ - function getVolatilityAccumulated(bytes32 params) internal pure returns (uint24 volatilityAccumulated) { - volatilityAccumulated = params.decodeUint20(OFFSET_VOL_ACC); + function getVolatilityAccumulator(bytes32 params) internal pure returns (uint24 volatilityAccumulator) { + volatilityAccumulator = params.decodeUint20(OFFSET_VOL_ACC); } /** @@ -241,9 +241,9 @@ library PairParameterHelper { if (variableFeeControl != 0) { unchecked { - // The volatility accumulated is in basis points, binStep is in 20_000th, + // The volatility accumulator is in basis points, binStep is in 20_000th, // and the variable fee control is in basis points, so the result is in 400e18th - uint256 prod = uint256(getVolatilityAccumulated(params)) * binStep; + uint256 prod = uint256(getVolatilityAccumulator(params)) * binStep; variableFee = (prod * prod * variableFeeControl + 399) / 400; } } @@ -302,7 +302,7 @@ library PairParameterHelper { * @param reductionFactor The reduction factor * @param variableFeeControl The variable fee control * @param protocolShare The protocol share - * @param maxVolatilityAccumulated The max volatility accumulated + * @param maxVolatilityAccumulator The max volatility accumulator * @return The updated encoded pair parameters */ function setStaticFeeParameters( @@ -313,12 +313,12 @@ library PairParameterHelper { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ) internal pure returns (bytes32) { if ( filterPeriod > decayPeriod || decayPeriod > Encoded.MASK_UINT12 || reductionFactor > Constants.BASIS_POINT_MAX || protocolShare > MAX_PROTOCOL_SHARE - || maxVolatilityAccumulated > Encoded.MASK_UINT20 + || maxVolatilityAccumulator > Encoded.MASK_UINT20 ) revert PairParametersHelper__InvalidParameter(); uint256 staticParams; @@ -329,7 +329,7 @@ library PairParameterHelper { staticParams := or(staticParams, shl(OFFSET_REDUCTION_FACTOR, reductionFactor)) staticParams := or(staticParams, shl(OFFSET_VAR_FEE_CONTROL, variableFeeControl)) staticParams := or(staticParams, shl(OFFSET_PROTOCOL_SHARE, protocolShare)) - staticParams := or(staticParams, shl(OFFSET_MAX_VOL_ACC, maxVolatilityAccumulated)) + staticParams := or(staticParams, shl(OFFSET_MAX_VOL_ACC, maxVolatilityAccumulator)) } return params.set(staticParams, MASK_STATIC_PARAMETER, 0); @@ -361,7 +361,7 @@ library PairParameterHelper { * @return The updated encoded pair parameters */ function updateVolatilityReference(bytes32 params) internal pure returns (bytes32) { - uint256 volAcc = getVolatilityAccumulated(params); + uint256 volAcc = getVolatilityAccumulator(params); uint256 reductionFactor = getReductionFactor(params); uint24 volRef; @@ -373,20 +373,21 @@ library PairParameterHelper { } /** - * @dev Updates the volatility accumulated in the encoded pair parameters + * @dev Updates the volatility accumulator in the encoded pair parameters * @param params The encoded pair parameters * @param activeId The active id * @return The updated encoded pair parameters */ - function updateVolatilityAccumulated(bytes32 params, uint24 activeId) internal pure returns (bytes32) { - uint256 deltaId = getDeltaId(params, activeId); + function updateVolatilityAccumulator(bytes32 params, uint24 activeId) internal pure returns (bytes32) { + uint256 idReference = getIdReference(params); + uint256 deltaId = activeId > idReference ? activeId - idReference : idReference - activeId; uint256 volAcc; unchecked { - volAcc = (uint256(getVolatilityAccumulated(params)) + deltaId * Constants.BASIS_POINT_MAX); + volAcc = (uint256(getVolatilityReference(params)) + deltaId * Constants.BASIS_POINT_MAX); } - uint256 maxVolAcc = getMaxVolatilityAccumulated(params); + uint256 maxVolAcc = getMaxVolatilityAccumulator(params); volAcc = volAcc > maxVolAcc ? maxVolAcc : volAcc; @@ -394,7 +395,7 @@ library PairParameterHelper { } /** - * @dev Updates the volatility reference and the volatility accumulated in the encoded pair parameters + * @dev Updates the volatility reference and the volatility accumulator in the encoded pair parameters * @param params The encoded pair parameters * @return The updated encoded pair parameters */ @@ -410,13 +411,13 @@ library PairParameterHelper { } /** - * @dev Updates the volatility reference and the volatility accumulated in the encoded pair parameters + * @dev Updates the volatility reference and the volatility accumulator in the encoded pair parameters * @param params The encoded pair parameters * @param activeId The active id * @return The updated encoded pair parameters */ function updateVolatilityParameters(bytes32 params, uint24 activeId) internal view returns (bytes32) { params = updateReferences(params); - return updateVolatilityAccumulated(params, activeId); + return updateVolatilityAccumulator(params, activeId); } } diff --git a/src/libraries/math/SampleMath.sol b/src/libraries/math/SampleMath.sol index a84c822f..78bb5780 100644 --- a/src/libraries/math/SampleMath.sol +++ b/src/libraries/math/SampleMath.sol @@ -12,7 +12,7 @@ import {Encoded} from "./Encoded.sol"; * The sample is encoded as follows: * 0 - 16: oracle length (16 bits) * 16 - 80: cumulative id (64 bits) - * 80 - 144: cumulative volatility accumulated (64 bits) + * 80 - 144: cumulative volatility accumulator (64 bits) * 144 - 208: cumulative bin crossed (64 bits) * 208 - 216: sample lifetime (8 bits) * 216 - 256: sample creation timestamp (40 bits) @@ -77,14 +77,14 @@ library SampleMath { } /** - * @dev Gets the cumulative volatility accumulated from an encoded sample + * @dev Gets the cumulative volatility accumulator from an encoded sample * @param sample The encoded sample as follows: * [0 - 80[: any (80 bits) - * [80 - 144[: cumulative volatility accumulated (64 bits) + * [80 - 144[: cumulative volatility accumulator (64 bits) * [144 - 256[: any (112 bits) - * @return volatilityAccumulated The cumulative volatility + * @return volatilityAccumulator The cumulative volatility */ - function getCumulativeVolatility(bytes32 sample) internal pure returns (uint64 volatilityAccumulated) { + function getCumulativeVolatility(bytes32 sample) internal pure returns (uint64 volatilityAccumulator) { return sample.decodeUint64(OFFSET_CUMULATIVE_VOLATILITY); } @@ -175,20 +175,20 @@ library SampleMath { * @param sample The encoded sample * @param deltaTime The time elapsed since the last update * @param activeId The active id - * @param volatilityAccumulated The volatility accumulated + * @param volatilityAccumulator The volatility accumulator * @param binCrossed The bin crossed * @return cumulativeId The cumulative id * @return cumulativeVolatility The cumulative volatility * @return cumulativeBinCrossed The cumulative bin crossed */ - function update(bytes32 sample, uint40 deltaTime, uint24 activeId, uint24 volatilityAccumulated, uint24 binCrossed) + function update(bytes32 sample, uint40 deltaTime, uint24 activeId, uint24 volatilityAccumulator, uint24 binCrossed) internal pure returns (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) { unchecked { cumulativeId = uint64(activeId) * deltaTime; - cumulativeVolatility = uint64(volatilityAccumulated) * deltaTime; + cumulativeVolatility = uint64(volatilityAccumulator) * deltaTime; cumulativeBinCrossed = uint64(binCrossed) * deltaTime; } diff --git a/test/LBFactory.t.sol b/test/LBFactory.t.sol index 626e04ab..c6222d1c 100644 --- a/test/LBFactory.t.sol +++ b/test/LBFactory.t.sol @@ -41,7 +41,7 @@ contract LiquidityBinFactoryTest is TestHelper { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ); event PresetSet( @@ -52,7 +52,7 @@ contract LiquidityBinFactoryTest is TestHelper { uint256 reductionFactor, uint256 variableFeeControl, uint256 protocolShare, - uint256 maxVolatilityAccumulated + uint256 maxVolatilityAccumulator ); event LBPairIgnoredStateChanged(ILBPair indexed LBPair, bool ignored); @@ -78,7 +78,7 @@ contract LiquidityBinFactoryTest is TestHelper { function test_constructor() public { assertEq(factory.getFeeRecipient(), DEV); - assertEq(factory.getFlashloanFee(), DEFAULT_FLASHLOAN_FEE); + assertEq(factory.getFlashLoanFee(), DEFAULT_FLASHLOAN_FEE); vm.expectEmit(true, true, true, true); emit FlashLoanFeeSet(0, DEFAULT_FLASHLOAN_FEE); @@ -147,7 +147,7 @@ contract LiquidityBinFactoryTest is TestHelper { // DEFAULT_REDUCTION_FACTOR, // DEFAULT_VARIABLE_FEE_CONTROL, // DEFAULT_PROTOCOL_SHARE, - // DEFAULT_MAX_VOLATILITY_ACCUMULATED + // DEFAULT_MAX_VOLATILITY_ACCUMULATOR // ); ILBPair pair = factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); @@ -171,11 +171,11 @@ contract LiquidityBinFactoryTest is TestHelper { assertEq(address(pair.getTokenY()), address(usdc), "test_createLBPair::8"); // FeeHelper.FeeParameters memory feeParameters = pair.feeParameters(); - // assertEq(feeParameters.volatilityAccumulated, 0, "test_createLBPair::9"); + // assertEq(feeParameters.volatilityAccumulator, 0, "test_createLBPair::9"); // assertEq(feeParameters.volatilityReference, 0, "test_createLBPair::10"); // assertEq(feeParameters.indexRef, 0, "test_createLBPair::11"); // assertEq(feeParameters.time, 0, "test_createLBPair::12"); - // assertEq(feeParameters.maxVolatilityAccumulated, DEFAULT_MAX_VOLATILITY_ACCUMULATED, "test_createLBPair::13"); + // assertEq(feeParameters.maxVolatilityAccumulator, DEFAULT_MAX_VOLATILITY_ACCUMULATOR, "test_createLBPair::13"); // assertEq(feeParameters.filterPeriod, DEFAULT_FILTER_PERIOD, "test_createLBPair::14"); // assertEq(feeParameters.decayPeriod, DEFAULT_DECAY_PERIOD, "test_createLBPair::15"); // assertEq(feeParameters.binStep, DEFAULT_BIN_STEP, "test_createLBPair::16"); @@ -252,7 +252,7 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED + DEFAULT_MAX_VOLATILITY_ACCUMULATOR ); // Can't create the same pair twice (a revision should be created instead) @@ -290,7 +290,7 @@ contract LiquidityBinFactoryTest is TestHelper { // DEFAULT_REDUCTION_FACTOR, // DEFAULT_VARIABLE_FEE_CONTROL, // DEFAULT_PROTOCOL_SHARE, - // DEFAULT_MAX_VOLATILITY_ACCUMULATED + // DEFAULT_MAX_VOLATILITY_ACCUMULATOR // ); ILBPair revision = factory.createLBPairRevision(usdt, usdc, DEFAULT_BIN_STEP); @@ -320,11 +320,11 @@ contract LiquidityBinFactoryTest is TestHelper { assertEq(address(revision.getTokenY()), address(usdc), "test_createLBPair::8"); // FeeHelper.FeeParameters memory feeParameters = revision.feeParameters(); - // assertEq(feeParameters.volatilityAccumulated, 0, "test_createLBPair::9"); + // assertEq(feeParameters.volatilityAccumulator, 0, "test_createLBPair::9"); // assertEq(feeParameters.volatilityReference, 0, "test_createLBPair::10"); // assertEq(feeParameters.indexRef, 0, "test_createLBPair::11"); // assertEq(feeParameters.time, 0, "test_createLBPair::12"); - // assertEq(feeParameters.maxVolatilityAccumulated, DEFAULT_MAX_VOLATILITY_ACCUMULATED, "test_createLBPair::13"); + // assertEq(feeParameters.maxVolatilityAccumulator, DEFAULT_MAX_VOLATILITY_ACCUMULATOR, "test_createLBPair::13"); // assertEq(feeParameters.filterPeriod, DEFAULT_FILTER_PERIOD, "test_createLBPair::14"); // assertEq(feeParameters.decayPeriod, DEFAULT_DECAY_PERIOD, "test_createLBPair::15"); // assertEq(feeParameters.binStep, DEFAULT_BIN_STEP, "test_createLBPair::16"); @@ -411,7 +411,7 @@ contract LiquidityBinFactoryTest is TestHelper { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ) public { binStep = uint8(bound(binStep, factory.getMinBinStep(), factory.getMaxBinStep())); filterPeriod = uint16(bound(filterPeriod, 0, type(uint16).max - 1)); @@ -420,11 +420,11 @@ contract LiquidityBinFactoryTest is TestHelper { protocolShare = uint16(bound(protocolShare, 0, factory.getMaxProtocolShare())); variableFeeControl = uint24(bound(variableFeeControl, 0, Constants.BASIS_POINT_MAX)); - // TODO: maxVolatilityAccumulated should be bounded but that's quite hard to calculate + // TODO: maxVolatilityAccumulator should be bounded but that's quite hard to calculate uint256 totalFeesMax; { uint256 baseFee = (uint256(baseFactor) * binStep) * 1e10; - uint256 prod = uint256(maxVolatilityAccumulated) * binStep; + uint256 prod = uint256(maxVolatilityAccumulator) * binStep; uint256 maxVariableFee = (prod * prod * variableFeeControl) / 100; totalFeesMax = baseFee + maxVariableFee; } @@ -439,7 +439,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); } else { vm.expectEmit(true, true, true, true); @@ -451,7 +451,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); factory.setPreset( @@ -462,7 +462,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); // Bin step DEFAULT_BIN_STEP is already there @@ -496,12 +496,12 @@ contract LiquidityBinFactoryTest is TestHelper { } { - (,,,, uint256 variableFeeControlView, uint256 protocolShareView, uint256 maxVolatilityAccumulatedView) = + (,,,, uint256 variableFeeControlView, uint256 protocolShareView, uint256 maxVolatilityAccumulatorView) = factory.getPreset(binStep); assertEq(variableFeeControlView, variableFeeControl); assertEq(protocolShareView, protocolShare); - assertEq(maxVolatilityAccumulatedView, maxVolatilityAccumulated); + assertEq(maxVolatilityAccumulatorView, maxVolatilityAccumulator); } } } @@ -515,10 +515,10 @@ contract LiquidityBinFactoryTest is TestHelper { uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulated + uint24 maxVolatilityAccumulator ) public { uint256 baseFee = (uint256(baseFactor) * binStep) * 1e10; - uint256 prod = uint256(maxVolatilityAccumulated) * binStep; + uint256 prod = uint256(maxVolatilityAccumulator) * binStep; uint256 maxVariableFee = (prod * prod * variableFeeControl) / 100; if (binStep < factory.getMinBinStep() || binStep > factory.getMaxBinStep()) { @@ -538,7 +538,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); } else if (filterPeriod >= decayPeriod) { vm.expectRevert( @@ -552,7 +552,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); } else if (reductionFactor > Constants.BASIS_POINT_MAX) { vm.expectRevert( @@ -568,7 +568,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); } else if (protocolShare > factory.getMaxProtocolShare()) { vm.expectRevert( @@ -584,7 +584,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); } else if (baseFee + maxVariableFee > factory.getMaxFee()) { vm.expectRevert(); @@ -596,7 +596,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); } else { factory.setPreset( @@ -607,7 +607,7 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulated + maxVolatilityAccumulator ); } } @@ -621,7 +621,7 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED + DEFAULT_MAX_VOLATILITY_ACCUMULATOR ); factory.setPreset( @@ -632,7 +632,7 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED + DEFAULT_MAX_VOLATILITY_ACCUMULATOR ); assertEq(factory.getAllBinSteps().length, 3); @@ -664,7 +664,7 @@ contract LiquidityBinFactoryTest is TestHelper { uint16 newReductionFactor = DEFAULT_REDUCTION_FACTOR * 2; uint24 newVariableFeeControl = DEFAULT_VARIABLE_FEE_CONTROL * 2; uint16 newProtocolShare = DEFAULT_PROTOCOL_SHARE * 2; - uint24 newMaxVolatilityAccumulated = DEFAULT_MAX_VOLATILITY_ACCUMULATED * 2; + uint24 newMaxVolatilityAccumulator = DEFAULT_MAX_VOLATILITY_ACCUMULATOR * 2; factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); @@ -679,7 +679,7 @@ contract LiquidityBinFactoryTest is TestHelper { newReductionFactor, newVariableFeeControl, newProtocolShare, - newMaxVolatilityAccumulated + newMaxVolatilityAccumulator ); factory.setFeesParametersOnPair( @@ -693,7 +693,7 @@ contract LiquidityBinFactoryTest is TestHelper { newReductionFactor, newVariableFeeControl, newProtocolShare, - newMaxVolatilityAccumulated + newMaxVolatilityAccumulator ); // FeeHelper.FeeParameters memory feeParameters = pair.feeParameters(); @@ -704,10 +704,10 @@ contract LiquidityBinFactoryTest is TestHelper { // assertEq(feeParameters.reductionFactor, newReductionFactor); // assertEq(feeParameters.variableFeeControl, newVariableFeeControl); // assertEq(feeParameters.protocolShare, newProtocolShare); - // assertEq(feeParameters.maxVolatilityAccumulated, newMaxVolatilityAccumulated); + // assertEq(feeParameters.maxVolatilityAccumulator, newMaxVolatilityAccumulator); // // Rest of the fee parameters slot should be the same - // assertEq(feeParameters.volatilityAccumulated, oldFeeParameters.volatilityAccumulated); + // assertEq(feeParameters.volatilityAccumulator, oldFeeParameters.volatilityAccumulator); // assertEq(feeParameters.volatilityReference, oldFeeParameters.volatilityReference); // assertEq(feeParameters.indexRef, oldFeeParameters.indexRef); // assertEq(feeParameters.time, oldFeeParameters.time); @@ -726,7 +726,7 @@ contract LiquidityBinFactoryTest is TestHelper { newReductionFactor, newVariableFeeControl, newProtocolShare, - newMaxVolatilityAccumulated + newMaxVolatilityAccumulator ); // Can't update a pair that does not exist @@ -744,7 +744,7 @@ contract LiquidityBinFactoryTest is TestHelper { newReductionFactor, newVariableFeeControl, newProtocolShare, - newMaxVolatilityAccumulated + newMaxVolatilityAccumulator ); } @@ -775,7 +775,7 @@ contract LiquidityBinFactoryTest is TestHelper { emit FlashLoanFeeSet(DEFAULT_FLASHLOAN_FEE, newFlashLoanFee); factory.setFlashLoanFee(newFlashLoanFee); - assertEq(factory.getFlashloanFee(), newFlashLoanFee); + assertEq(factory.getFlashLoanFee(), newFlashLoanFee); // Can't set if not the owner vm.prank(ALICE); @@ -892,7 +892,7 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED + DEFAULT_MAX_VOLATILITY_ACCUMULATOR ); factory.setPreset( @@ -903,7 +903,7 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED + DEFAULT_MAX_VOLATILITY_ACCUMULATOR ); ILBPair pair1 = factory.createLBPair(weth, usdc, ID_ONE, 5); diff --git a/test/LBPairFees.t.sol b/test/LBPairFees.t.sol new file mode 100644 index 00000000..12c3c369 --- /dev/null +++ b/test/LBPairFees.t.sol @@ -0,0 +1,546 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "./helpers/TestHelper.sol"; + +contract LBPairFeesTest is TestHelper { + using PackedUint128Math for uint128; + + uint256 constant amountX = 1e18; + uint256 constant amountY = 1e18; + + function setUp() public override { + super.setUp(); + + pairWavax = createLBPair(wavax, usdc); + + addLiquidity(DEV, DEV, pairWavax, ID_ONE, amountX, amountY, 10, 10); + require(wavax.balanceOf(DEV) == 0 && usdc.balanceOf(DEV) == 0, "setUp::1"); + } + + function testFuzz_SwapInX(uint128 amountOut) external { + vm.assume(amountOut > 0 && amountOut <= 1e18); + + (uint128 amountIn, uint128 amountOutLeft,) = pairWavax.getSwapIn(amountOut, true); + assertEq(amountOutLeft, 0, "testFuzz_SwapInFeesAmounts::1"); + + deal(address(wavax), ALICE, amountIn); + + vm.prank(ALICE); + wavax.transfer(address(pairWavax), amountIn); + pairWavax.swap(true, ALICE); + + assertEq(wavax.balanceOf(ALICE), 0, "testFuzz_SwapInFeesAmounts::2"); + assertEq(usdc.balanceOf(ALICE), amountOut, "testFuzz_SwapInFeesAmounts::3"); + + removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + + (uint128 protocolFeeX,) = pairWavax.getProtocolFees(); + + uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceY = usdc.balanceOf(DEV); + + assertEq(balanceX, amountX + amountIn - protocolFeeX, "testFuzz_SwapInFeesAmounts::4"); + assertEq(balanceY, amountY - amountOut, "testFuzz_SwapInFeesAmounts::5"); + } + + function testFuzz_SwapInY(uint128 amountOut) external { + vm.assume(amountOut > 0 && amountOut <= 1e18); + + (uint128 amountIn, uint128 amountOutLeft,) = pairWavax.getSwapIn(amountOut, false); + assertEq(amountOutLeft, 0, "testFuzz_SwapInFeesAmounts::1"); + + deal(address(usdc), ALICE, amountIn); + + vm.prank(ALICE); + usdc.transfer(address(pairWavax), amountIn); + pairWavax.swap(false, ALICE); + + assertEq(usdc.balanceOf(ALICE), 0, "testFuzz_SwapInFeesAmounts::2"); + assertEq(wavax.balanceOf(ALICE), amountOut, "testFuzz_SwapInFeesAmounts::3"); + + removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + + (, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + + uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceY = usdc.balanceOf(DEV); + + assertEq(balanceX, amountX - amountOut, "testFuzz_SwapInFeesAmounts::4"); + assertEq(balanceY, amountY + amountIn - protocolFeeY, "testFuzz_SwapInFeesAmounts::5"); + } + + function testFuzz_SwapOutX(uint128 amountIn) external { + (uint128 amountInLeft, uint128 amountOut,) = pairWavax.getSwapOut(amountIn, true); + vm.assume(amountOut > 0 && amountInLeft == 0); + + deal(address(wavax), ALICE, amountIn); + + vm.prank(ALICE); + wavax.transfer(address(pairWavax), amountIn); + pairWavax.swap(true, ALICE); + + assertEq(wavax.balanceOf(ALICE), 0, "testFuzz_SwapInFeesAmounts::2"); + assertEq(usdc.balanceOf(ALICE), amountOut, "testFuzz_SwapInFeesAmounts::3"); + + removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + + (uint128 protocolFeeX,) = pairWavax.getProtocolFees(); + + uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceY = usdc.balanceOf(DEV); + + assertEq(balanceX, amountX + amountIn - protocolFeeX, "testFuzz_SwapInFeesAmounts::4"); + assertEq(balanceY, amountY - amountOut, "testFuzz_SwapInFeesAmounts::5"); + } + + function testFuzz_SwapOutY(uint128 amountIn) external { + (uint128 amountInLeft, uint128 amountOut,) = pairWavax.getSwapOut(amountIn, false); + vm.assume(amountOut > 0 && amountInLeft == 0); + + deal(address(usdc), ALICE, amountIn); + + vm.prank(ALICE); + usdc.transfer(address(pairWavax), amountIn); + pairWavax.swap(false, ALICE); + + assertEq(usdc.balanceOf(ALICE), 0, "testFuzz_SwapInFeesAmounts::2"); + assertEq(wavax.balanceOf(ALICE), amountOut, "testFuzz_SwapInFeesAmounts::3"); + + removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + + (, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + + uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceY = usdc.balanceOf(DEV); + + assertEq(balanceX, amountX - amountOut, "testFuzz_SwapInFeesAmounts::4"); + assertEq(balanceY, amountY + amountIn - protocolFeeY, "testFuzz_SwapInFeesAmounts::5"); + } + + function testFuzz_SwapInXAndY(uint128 amountXOut, uint128 amountYOut) external { + vm.assume(amountXOut > 0 && amountXOut <= 1e18 && amountYOut > 0 && amountYOut <= 1e18); + + (uint128 amountXIn, uint128 amountYOutLeft,) = pairWavax.getSwapIn(amountYOut, true); + assertEq(amountYOutLeft, 0, "testFuzz_SwapInFeesAmounts::1"); + + deal(address(wavax), BOB, 1e36); + deal(address(usdc), BOB, 1e36); + + vm.prank(BOB); + wavax.transfer(address(pairWavax), amountXIn); + pairWavax.swap(true, ALICE); + + assertEq(usdc.balanceOf(ALICE), amountYOut, "testFuzz_SwapInFeesAmounts::2"); + + (uint128 amountYIn, uint128 amountXOutLeft,) = pairWavax.getSwapIn(amountXOut, false); + assertEq(amountXOutLeft, 0, "testFuzz_SwapInFeesAmounts::3"); + + vm.prank(BOB); + usdc.transfer(address(pairWavax), amountYIn); + pairWavax.swap(false, ALICE); + + uint256 realAmountXOut = wavax.balanceOf(ALICE); + assertGe(realAmountXOut, amountXOut, "testFuzz_SwapInFeesAmounts::4"); + + removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + + assertEq(wavax.balanceOf(address(pairWavax)), protocolFeeX, "testFuzz_SwapInFeesAmounts::5"); + assertEq(usdc.balanceOf(address(pairWavax)), protocolFeeY, "testFuzz_SwapInFeesAmounts::6"); + + uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceY = usdc.balanceOf(DEV); + + assertEq(balanceX, amountX + amountXIn - realAmountXOut - protocolFeeX, "testFuzz_SwapInFeesAmounts::7"); + assertEq(balanceY, amountY + amountYIn - amountYOut - protocolFeeY, "testFuzz_SwapInFeesAmounts::8"); + } + + function testFuzz_SwapInYandX(uint128 amountYOut, uint128 amountXOut) external { + vm.assume(amountXOut > 0 && amountXOut <= 1e18 && amountYOut > 0 && amountYOut <= 1e18); + + (uint128 amountYIn, uint128 amountXOutLeft,) = pairWavax.getSwapIn(amountXOut, false); + assertEq(amountXOutLeft, 0, "testFuzz_SwapInFeesAmounts::1"); + + deal(address(wavax), BOB, 1e36); + deal(address(usdc), BOB, 1e36); + + vm.prank(BOB); + usdc.transfer(address(pairWavax), amountYIn); + pairWavax.swap(false, ALICE); + + assertEq(wavax.balanceOf(ALICE), amountXOut, "testFuzz_SwapInFeesAmounts::2"); + + (uint128 amountXIn, uint128 amountYOutLeft,) = pairWavax.getSwapIn(amountYOut, true); + assertEq(amountYOutLeft, 0, "testFuzz_SwapInFeesAmounts::3"); + + vm.prank(BOB); + wavax.transfer(address(pairWavax), amountXIn); + pairWavax.swap(true, ALICE); + + uint256 realAmountYOut = usdc.balanceOf(ALICE); + assertGe(realAmountYOut, amountYOut, "testFuzz_SwapInFeesAmounts::4"); + + removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + + assertEq(wavax.balanceOf(address(pairWavax)), protocolFeeX, "testFuzz_SwapInFeesAmounts::5"); + assertEq(usdc.balanceOf(address(pairWavax)), protocolFeeY, "testFuzz_SwapInFeesAmounts::6"); + + uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceY = usdc.balanceOf(DEV); + + assertEq(balanceX, amountX + amountXIn - amountXOut - protocolFeeX, "testFuzz_SwapInFeesAmounts::7"); + assertEq(balanceY, amountY + amountYIn - realAmountYOut - protocolFeeY, "testFuzz_SwapInFeesAmounts::8"); + } + + function testFuzz_SwapOutXAndY(uint128 amountXIn, uint128 amountYIn) external { + (uint128 amountXInLeft, uint128 amountYOut,) = pairWavax.getSwapOut(amountXIn, true); + vm.assume(amountXInLeft == 0 && amountYOut > 0); + + deal(address(wavax), BOB, 1e36); + deal(address(usdc), BOB, 1e36); + + vm.prank(BOB); + wavax.transfer(address(pairWavax), amountXIn); + pairWavax.swap(true, ALICE); + + assertEq(usdc.balanceOf(ALICE), amountYOut, "testFuzz_SwapInFeesAmounts::1"); + + (uint128 amountYInLeft, uint128 amountXOut,) = pairWavax.getSwapOut(amountYIn, false); + vm.assume(amountYInLeft == 0 && amountXOut > 0); + + vm.prank(BOB); + usdc.transfer(address(pairWavax), amountYIn); + pairWavax.swap(false, ALICE); + + uint256 realAmountXOut = wavax.balanceOf(ALICE); + assertGe(realAmountXOut, amountXOut, "testFuzz_SwapInFeesAmounts::2"); + + removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + + assertEq(wavax.balanceOf(address(pairWavax)), protocolFeeX, "testFuzz_SwapInFeesAmounts::3"); + assertEq(usdc.balanceOf(address(pairWavax)), protocolFeeY, "testFuzz_SwapInFeesAmounts::4"); + + uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceY = usdc.balanceOf(DEV); + + assertEq(balanceX, amountX + amountXIn - realAmountXOut - protocolFeeX, "testFuzz_SwapInFeesAmounts::5"); + assertEq(balanceY, amountY + amountYIn - amountYOut - protocolFeeY, "testFuzz_SwapInFeesAmounts::6"); + } + + function testFuzz_SwapOutYAndX(uint128 amountXIn, uint128 amountYIn) external { + (uint128 amountYInLeft, uint128 amountXOut,) = pairWavax.getSwapOut(amountYIn, false); + vm.assume(amountYInLeft == 0 && amountXOut > 0); + + deal(address(wavax), BOB, 1e36); + deal(address(usdc), BOB, 1e36); + + vm.prank(BOB); + usdc.transfer(address(pairWavax), amountYIn); + pairWavax.swap(false, ALICE); + + assertEq(wavax.balanceOf(ALICE), amountXOut, "testFuzz_SwapInFeesAmounts::1"); + + (uint128 amountXInLeft, uint128 amountYOut,) = pairWavax.getSwapOut(amountXIn, true); + vm.assume(amountXInLeft == 0 && amountYOut > 0); + + vm.prank(BOB); + wavax.transfer(address(pairWavax), amountXIn); + pairWavax.swap(true, ALICE); + + uint256 realAmountYOut = usdc.balanceOf(ALICE); + assertGe(realAmountYOut, amountYOut, "testFuzz_SwapInFeesAmounts::2"); + + removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + + assertEq(wavax.balanceOf(address(pairWavax)), protocolFeeX, "testFuzz_SwapInFeesAmounts::3"); + assertEq(usdc.balanceOf(address(pairWavax)), protocolFeeY, "testFuzz_SwapInFeesAmounts::4"); + + uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceY = usdc.balanceOf(DEV); + + assertEq(balanceX, amountX + amountXIn - amountXOut - protocolFeeX, "testFuzz_SwapInFeesAmounts::5"); + assertEq(balanceY, amountY + amountYIn - realAmountYOut - protocolFeeY, "testFuzz_SwapInFeesAmounts::6"); + } + + function test_FeesX2LP() external { + addLiquidity(ALICE, ALICE, pairWavax, ID_ONE, amountX, amountY, 10, 10); + + uint128 amountXIn = 1e18; + (, uint128 amountYOut,) = pairWavax.getSwapOut(amountXIn, true); + + deal(address(wavax), BOB, amountXIn); + + vm.prank(BOB); + wavax.transfer(address(pairWavax), amountXIn); + pairWavax.swap(true, BOB); + + removeLiquidity(ALICE, ALICE, pairWavax, ID_ONE, 1e18, 10, 10); + + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + + assertApproxEqAbs( + wavax.balanceOf(address(ALICE)), amountX + (amountXIn - protocolFeeX) / 2, 2, "test_FeesX2LP::1" + ); + assertApproxEqAbs( + usdc.balanceOf(address(ALICE)), amountY - (amountYOut + protocolFeeY) / 2, 2, "test_FeesX2LP::2" + ); + + removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + + assertApproxEqAbs( + wavax.balanceOf(address(DEV)), amountX + (amountXIn - protocolFeeX) / 2, 2, "test_FeesX2LP::3" + ); + assertApproxEqAbs( + usdc.balanceOf(address(DEV)), amountY - (amountYOut + protocolFeeY) / 2, 2, "test_FeesX2LP::4" + ); + } + + function test_FeesY2LP() external { + addLiquidity(ALICE, ALICE, pairWavax, ID_ONE, amountX, amountY, 10, 10); + + uint128 amountYIn = 1e18; + (, uint128 amountXOut,) = pairWavax.getSwapOut(amountYIn, false); + + deal(address(usdc), BOB, amountYIn); + + vm.prank(BOB); + usdc.transfer(address(pairWavax), amountYIn); + pairWavax.swap(false, BOB); + + removeLiquidity(ALICE, ALICE, pairWavax, ID_ONE, 1e18, 10, 10); + + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + + assertApproxEqAbs( + wavax.balanceOf(address(ALICE)), amountX - (amountXOut + protocolFeeX) / 2, 2, "test_FeesY2LP::1" + ); + assertApproxEqAbs( + usdc.balanceOf(address(ALICE)), amountY + (amountYIn - protocolFeeY) / 2, 2, "test_FeesY2LP::2" + ); + + removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + + assertApproxEqAbs( + wavax.balanceOf(address(DEV)), amountX - (amountXOut + protocolFeeX) / 2, 2, "test_FeesY2LP::3" + ); + assertApproxEqAbs(usdc.balanceOf(address(DEV)), amountY + (amountYIn - protocolFeeY) / 2, 2, "test_FeesY2LP::4"); + } + + function test_Fees2LPFlashloan() external { + removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + addLiquidity(DEV, DEV, pairWavax, ID_ONE, amountX, amountY, 1, 1); + + FlashBorrower borrower = new FlashBorrower(pairWavax); + + deal(address(wavax), address(borrower), 2e18); + deal(address(usdc), address(borrower), 2e18); + + (uint128 feeX1, uint128 feeY1) = (1e18 + 1, 1e18); + + vm.prank(address(borrower)); + pairWavax.flashLoan(borrower, bytes32(uint256(1)), abi.encode(feeX1, feeY1, Constants.CALLBACK_SUCCESS, 0)); + + (uint128 protocolFeeX1, uint128 protocolFeeY1) = pairWavax.getProtocolFees(); + (feeX1, feeY1) = (feeX1 - protocolFeeX1, feeY1 - protocolFeeY1); + + addLiquidity(ALICE, ALICE, pairWavax, ID_ONE, amountX, amountY, 1, 1); + + (uint128 feeX2, uint128 feeY2) = (1e18 + 1, 1e18); + + vm.prank(address(borrower)); + pairWavax.flashLoan(borrower, bytes32(uint256(1)), abi.encode(feeX2, feeY2, Constants.CALLBACK_SUCCESS, 0)); + + { + (uint128 protocolFeeX2, uint128 protocolFeeY2) = pairWavax.getProtocolFees(); + (feeX2, feeY2) = (feeX2 - (protocolFeeX2 - protocolFeeX1), feeY2 - (protocolFeeY2 - protocolFeeY1)); + } + + (uint256 shareAlice, uint256 shareDev) = + (pairWavax.balanceOf(address(ALICE), ID_ONE), pairWavax.balanceOf(address(DEV), ID_ONE)); + + removeLiquidity(ALICE, ALICE, pairWavax, ID_ONE, 1e18, 1, 1); + removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 1, 1); + + assertApproxEqAbs( + wavax.balanceOf(address(ALICE)), + amountX + feeX2 * shareAlice / (shareAlice + shareDev), + 1, + "test_Fees2LPFlashloan::1" + ); + assertApproxEqAbs( + usdc.balanceOf(address(ALICE)), + amountY + feeY2 * shareAlice / (shareAlice + shareDev), + 1, + "test_Fees2LPFlashloan::2" + ); + + assertApproxEqAbs( + wavax.balanceOf(address(DEV)), + amountX + feeX1 + feeX2 * shareDev / (shareAlice + shareDev), + 1, + "test_Fees2LPFlashloan::3" + ); + assertApproxEqAbs( + usdc.balanceOf(address(DEV)), + amountY + feeY1 + feeY2 * shareDev / (shareAlice + shareDev), + 1, + "test_Fees2LPFlashloan::4" + ); + } + + function test_CollectProtocolFeesXTokens() external { + FlashBorrower borrower = new FlashBorrower(pairWavax); + + deal(address(wavax), address(borrower), 1e36); + deal(address(usdc), address(borrower), 1e36); + + vm.prank(address(borrower)); + pairWavax.flashLoan(borrower, uint128(1).encode(0), abi.encode(1e18 + 1, 0, Constants.CALLBACK_SUCCESS, 0)); + + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + + address feeRecipient = factory.getFeeRecipient(); + + vm.prank(feeRecipient); + pairWavax.collectProtocolFees(); + + assertEq(wavax.balanceOf(feeRecipient), protocolFeeX - 1, "test_CollectProtocolFees::1"); + assertEq(usdc.balanceOf(feeRecipient), 0, "test_CollectProtocolFees::2"); + + (protocolFeeX, protocolFeeY) = pairWavax.getProtocolFees(); + + assertEq(protocolFeeX, 1, "test_CollectProtocolFees::3"); + assertEq(protocolFeeY, 0, "test_CollectProtocolFees::4"); + } + + function test_CollectProtocolFeesYTokens() external { + FlashBorrower borrower = new FlashBorrower(pairWavax); + + deal(address(wavax), address(borrower), 1e36); + deal(address(usdc), address(borrower), 1e36); + + vm.prank(address(borrower)); + pairWavax.flashLoan(borrower, uint128(0).encode(1), abi.encode(0, 1e18 + 1, Constants.CALLBACK_SUCCESS, 0)); + + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + + address feeRecipient = factory.getFeeRecipient(); + + vm.prank(feeRecipient); + pairWavax.collectProtocolFees(); + + assertEq(wavax.balanceOf(feeRecipient), 0, "test_CollectProtocolFees::1"); + assertEq(usdc.balanceOf(feeRecipient), protocolFeeY - 1, "test_CollectProtocolFees::2"); + + (protocolFeeX, protocolFeeY) = pairWavax.getProtocolFees(); + + assertEq(protocolFeeX, 0, "test_CollectProtocolFees::3"); + assertEq(protocolFeeY, 1, "test_CollectProtocolFees::4"); + } + + function test_CollectProtocolFeesBothTokens() external { + FlashBorrower borrower = new FlashBorrower(pairWavax); + + deal(address(wavax), address(borrower), 1e36); + deal(address(usdc), address(borrower), 1e36); + + vm.prank(address(borrower)); + pairWavax.flashLoan( + borrower, uint128(1).encode(1), abi.encode(1e18 + 1, 1e18 + 1, Constants.CALLBACK_SUCCESS, 0) + ); + + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + + address feeRecipient = factory.getFeeRecipient(); + + vm.prank(feeRecipient); + pairWavax.collectProtocolFees(); + + assertEq(wavax.balanceOf(feeRecipient), protocolFeeX - 1, "test_CollectProtocolFees::1"); + assertEq(usdc.balanceOf(feeRecipient), protocolFeeY - 1, "test_CollectProtocolFees::2"); + + (protocolFeeX, protocolFeeY) = pairWavax.getProtocolFees(); + + assertEq(protocolFeeX, 1, "test_CollectProtocolFees::3"); + assertEq(protocolFeeY, 1, "test_CollectProtocolFees::4"); + } + + function test_CollectProtocolFeesAfterSwap() external { + deal(address(wavax), address(BOB), 1e18); + + vm.prank(BOB); + wavax.transfer(address(pairWavax), 1e18); + pairWavax.swap(true, BOB); + + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + uint128 previousProtocolFeeX = protocolFeeX; + + assertGt(protocolFeeX, 0, "test_CollectProtocolFees::1"); + assertEq(protocolFeeY, 0, "test_CollectProtocolFees::2"); + + (uint128 reserveX, uint128 reserveY) = pairWavax.getReserves(); + + address feeRecipient = factory.getFeeRecipient(); + + vm.prank(feeRecipient); + pairWavax.collectProtocolFees(); + + (uint128 reserveXAfter, uint128 reserveYAfter) = pairWavax.getReserves(); + + assertEq(reserveXAfter, reserveX, "test_CollectProtocolFees::3"); + assertEq(reserveYAfter, reserveY, "test_CollectProtocolFees::4"); + + assertEq(wavax.balanceOf(feeRecipient), protocolFeeX - 1, "test_CollectProtocolFees::5"); + assertEq(usdc.balanceOf(feeRecipient), 0, "test_CollectProtocolFees::6"); + + (protocolFeeX, protocolFeeY) = pairWavax.getProtocolFees(); + + assertEq(protocolFeeX, 1, "test_CollectProtocolFees::7"); + assertEq(protocolFeeY, 0, "test_CollectProtocolFees::8"); + + deal(address(usdc), address(BOB), 1e18); + + vm.prank(BOB); + usdc.transfer(address(pairWavax), 1e18); + pairWavax.swap(false, BOB); + + (protocolFeeX, protocolFeeY) = pairWavax.getProtocolFees(); + uint128 previousProtocolFeeY = protocolFeeY; + + assertEq(protocolFeeX, 1, "test_CollectProtocolFees::9"); + assertGt(protocolFeeY, 0, "test_CollectProtocolFees::10"); + + (reserveX, reserveY) = pairWavax.getReserves(); + + vm.prank(feeRecipient); + pairWavax.collectProtocolFees(); + + (reserveXAfter, reserveYAfter) = pairWavax.getReserves(); + + assertEq(reserveXAfter, reserveX, "test_CollectProtocolFees::11"); + assertEq(reserveYAfter, reserveY, "test_CollectProtocolFees::12"); + + assertEq(wavax.balanceOf(feeRecipient), previousProtocolFeeX - 1, "test_CollectProtocolFees::13"); + assertEq(usdc.balanceOf(feeRecipient), protocolFeeY - 1, "test_CollectProtocolFees::14"); + + (protocolFeeX, protocolFeeY) = pairWavax.getProtocolFees(); + + assertEq(protocolFeeX, 1, "test_CollectProtocolFees::15"); + assertEq(protocolFeeY, 1, "test_CollectProtocolFees::16"); + + vm.prank(feeRecipient); + pairWavax.collectProtocolFees(); + + assertEq(wavax.balanceOf(feeRecipient), previousProtocolFeeX - 1, "test_CollectProtocolFees::19"); + assertEq(usdc.balanceOf(feeRecipient), previousProtocolFeeY - 1, "test_CollectProtocolFees::20"); + } +} diff --git a/test/LBPairFlashloan.t.sol b/test/LBPairFlashloan.t.sol new file mode 100644 index 00000000..89011dd9 --- /dev/null +++ b/test/LBPairFlashloan.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "./helpers/TestHelper.sol"; + +import "./mocks/FlashBorrower.sol"; + +contract LBPairFlashloanTest is TestHelper { + using SafeCast for uint256; + using PackedUint128Math for bytes32; + using PackedUint128Math for uint128; + + FlashBorrower borrower; + + function setUp() public override { + super.setUp(); + + pairWavax = createLBPair(wavax, usdc); + + addLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 1e18, 50, 50); + + borrower = new FlashBorrower(pairWavax); + + // Make sure the borrower can pay back the flash loan + deal(address(wavax), address(borrower), 1e18); + deal(address(usdc), address(borrower), 1e18); + } + + function testFuzz_FlashLoan(uint128 amountX, uint128 amountY) external { + vm.assume(amountX <= 1e18 && amountY <= 1e18 && (amountX > 0 || amountY > 0)); + + bytes32 amountsBorrowed = amountX.encode(amountY); + bytes memory data = abi.encode(type(uint128).max, type(uint128).max, Constants.CALLBACK_SUCCESS, 0); + + uint256 balanceX = wavax.balanceOf(address(pairWavax)); + uint256 balanceY = usdc.balanceOf(address(pairWavax)); + + uint256 flashLoanFee = factory.getFlashLoanFee(); + + uint256 feeX = (amountX * flashLoanFee + 1e18 - 1) / 1e18; + uint256 feeY = (amountY * flashLoanFee + 1e18 - 1) / 1e18; + + pairWavax.flashLoan(borrower, amountsBorrowed, data); + + assertEq(wavax.balanceOf(address(pairWavax)), balanceX + feeX, "TestFuzz_Flashloan::1"); + assertEq(usdc.balanceOf(address(pairWavax)), balanceY + feeY, "TestFuzz_Flashloan::2"); + + (uint256 reserveX, uint256 reserveY) = pairWavax.getReserves(); + (uint256 protocolFeeX, uint256 protocolFeeY) = pairWavax.getProtocolFees(); + + assertEq(reserveX + protocolFeeX, balanceX + feeX, "TestFuzz_Flashloan::3"); + assertEq(reserveY + protocolFeeY, balanceY + feeY, "TestFuzz_Flashloan::4"); + } + + function testFuzz_revert_FlashLoanInsufficientAmount(uint128 amountX, uint128 amountY) external { + vm.assume(amountX > 0 && amountY > 0 && amountX <= 1e18 && amountY <= 1e18); + + uint256 flashLoanFee = factory.getFlashLoanFee(); + + uint256 feeX = (amountX * flashLoanFee + 1e18 - 1) / 1e18; + uint256 feeY = (amountY * flashLoanFee + 1e18 - 1) / 1e18; + + bytes32 amountsBorrowed = amountX.encode(amountY); + bytes memory data = abi.encode(amountX + feeX - 1, amountY + feeY, Constants.CALLBACK_SUCCESS, 0); + + vm.expectRevert(ILBPair.LBPair__FlashLoanInsufficientAmount.selector); + pairWavax.flashLoan(borrower, amountsBorrowed, data); + + data = abi.encode(amountX + feeX, amountY + feeY - 1, Constants.CALLBACK_SUCCESS, 0); + + vm.expectRevert(ILBPair.LBPair__FlashLoanInsufficientAmount.selector); + pairWavax.flashLoan(borrower, amountsBorrowed, data); + + data = abi.encode(amountX + feeX - 1, amountY + feeY - 1, Constants.CALLBACK_SUCCESS, 0); + + vm.expectRevert(ILBPair.LBPair__FlashLoanInsufficientAmount.selector); + pairWavax.flashLoan(borrower, amountsBorrowed, data); + } + + function testFuzz_revert_FlashLoanCallbackFailed(bytes32 callback) external { + vm.assume(callback != Constants.CALLBACK_SUCCESS); + + bytes32 amountsBorrowed = bytes32(uint256(1)); + bytes memory data = abi.encode(0, 0, callback, 0); + + vm.expectRevert(ILBPair.LBPair__FlashLoanCallbackFailed.selector); + pairWavax.flashLoan(borrower, amountsBorrowed, data); + } + + function testFuzz_revert_FlashLoanReentrant(bytes32 callback) external { + vm.assume(callback != Constants.CALLBACK_SUCCESS); + + bytes32 amountsBorrowed = bytes32(uint256(1)); + bytes memory data = abi.encode(0, 0, callback, 1); + + vm.expectRevert(ReentrancyGuard.ReentrancyGuard__ReentrantCall.selector); + pairWavax.flashLoan(borrower, amountsBorrowed, data); + } + + function test_revert_FlashLoan0Amounts() external { + vm.expectRevert(ILBPair.LBPair__ZeroBorrowAmount.selector); + pairWavax.flashLoan(borrower, 0, ""); + } +} diff --git a/test/LBPairImplementation.t.sol b/test/LBPairImplementation.t.sol new file mode 100644 index 00000000..6af832f6 --- /dev/null +++ b/test/LBPairImplementation.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "forge-std/Test.sol"; + +import "../src/LBPair.sol"; +import "../src/libraries/ImmutableClone.sol"; + +contract LBPairImplementationTest is Test { + address factory; + address implementation; + + function setUp() public { + factory = makeAddr("factory"); + implementation = address(new LBPair(ILBFactory(factory))); + } + + function testFuzz_Getters(address tokenX, address tokenY, uint8 binStep) public { + bytes32 salt = keccak256(abi.encodePacked(tokenX, tokenY, binStep)); + bytes memory data = abi.encodePacked(tokenX, tokenY, binStep); + + LBPair pair = LBPair(ImmutableClone.cloneDeterministic(implementation, data, salt)); + + assertEq(address(pair.getTokenX()), tokenX); + assertEq(address(pair.getTokenY()), tokenY); + assertEq(pair.getBinStep(), binStep); + } + + function testFuzz_revert_InitializeImplementation() public { + vm.expectRevert(ILBPair.LBPair__OnlyFactory.selector); + LBPair(implementation).initialize(1, 1, 1, 1, 1, 1, 1, 1); + + vm.expectRevert(ILBPair.LBPair__AlreadyInitialized.selector); + vm.prank(address(factory)); + LBPair(implementation).initialize(1, 1, 1, 1, 1, 1, 1, 1); + } +} diff --git a/test/LBPairInitialState.t.sol b/test/LBPairInitialState.t.sol new file mode 100644 index 00000000..b8e8707f --- /dev/null +++ b/test/LBPairInitialState.t.sol @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "./helpers/TestHelper.sol"; + +contract LBPairInitialStateTest is TestHelper { + function setUp() public override { + super.setUp(); + + pairWavax = createLBPair(wavax, usdc); + } + + function test_GetFactory() external { + assertEq(address(pairWavax.getFactory()), address(factory), "test_GetFactory::1"); + } + + function test_GetTokenX() external { + assertEq(address(pairWavax.getTokenX()), address(wavax), "test_GetTokenX::1"); + } + + function test_GetTokenY() external { + assertEq(address(pairWavax.getTokenY()), address(usdc), "test_GetTokenY::1"); + } + + function test_GetBinStep() external { + assertEq(pairWavax.getBinStep(), DEFAULT_BIN_STEP, "test_GetBinStep::1"); + } + + function test_GetReserves() external { + (uint128 reserveX, uint128 reserveY) = pairWavax.getReserves(); + + assertEq(reserveX, 0, "test_GetReserves::1"); + assertEq(reserveY, 0, "test_GetReserves::2"); + } + + function test_GetActiveId() external { + assertEq(pairWavax.getActiveId(), ID_ONE, "test_GetActiveId::1"); + } + + function testFuzz_GetBin(uint24 id) external { + (uint128 reserveX, uint128 reserveY) = pairWavax.getBin(id); + + assertEq(reserveX, 0, "test_GetBin::1"); + assertEq(reserveY, 0, "test_GetBin::2"); + } + + function test_GetNextNonEmptyBin() external { + assertEq(pairWavax.getNextNonEmptyBin(false, 0), 0, "test_GetNextNonEmptyBin::1"); + assertEq(pairWavax.getNextNonEmptyBin(true, 0), type(uint24).max, "test_GetNextNonEmptyBin::2"); + + assertEq(pairWavax.getNextNonEmptyBin(false, type(uint24).max), 0, "test_GetNextNonEmptyBin::3"); + assertEq(pairWavax.getNextNonEmptyBin(true, type(uint24).max), type(uint24).max, "test_GetNextNonEmptyBin::4"); + } + + function test_GetProtocolFees() external { + (uint128 protocolFeesX, uint128 protocolFeesY) = pairWavax.getProtocolFees(); + + assertEq(protocolFeesX, 0, "test_GetProtocolFees::1"); + assertEq(protocolFeesY, 0, "test_GetProtocolFees::2"); + } + + function test_GetStaticFeeParameters() external { + ( + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulator + ) = pairWavax.getStaticFeeParameters(); + + assertEq(baseFactor, DEFAULT_BASE_FACTOR, "test_GetParameters::1"); + assertEq(filterPeriod, DEFAULT_FILTER_PERIOD, "test_GetParameters::2"); + assertEq(decayPeriod, DEFAULT_DECAY_PERIOD, "test_GetParameters::3"); + assertEq(reductionFactor, DEFAULT_REDUCTION_FACTOR, "test_GetParameters::4"); + assertEq(variableFeeControl, DEFAULT_VARIABLE_FEE_CONTROL, "test_GetParameters::5"); + assertEq(protocolShare, DEFAULT_PROTOCOL_SHARE, "test_GetParameters::6"); + assertEq(maxVolatilityAccumulator, DEFAULT_MAX_VOLATILITY_ACCUMULATOR, "test_GetParameters::7"); + } + + function test_GetVariableFeeParameters() external { + (uint24 volatilityAccumulator, uint24 volatilityReference, uint24 idReference, uint40 timeOfLastUpdate) = + pairWavax.getVariableFeeParameters(); + + assertEq(volatilityAccumulator, 0, "test_GetParameters::1"); + assertEq(volatilityReference, 0, "test_GetParameters::2"); + assertEq(idReference, ID_ONE, "test_GetParameters::3"); + assertEq(timeOfLastUpdate, 0, "test_GetParameters::4"); + } + + function test_GetOracleParameters() external { + (uint8 sampleLifetime, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp) = + pairWavax.getOracleParameters(); + + assertEq(sampleLifetime, OracleHelper._MAX_SAMPLE_LIFETIME, "test_GetParameters::1"); + assertEq(size, 0, "test_GetParameters::2"); + assertEq(activeSize, 0, "test_GetParameters::3"); + assertEq(lastUpdated, 0, "test_GetParameters::4"); + assertEq(firstTimestamp, 0, "test_GetParameters::5"); + } + + function test_GetOracleSampleAt() external { + (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = pairWavax.getOracleSampleAt(1); + + assertEq(cumulativeId, 0, "test_GetParameters::1"); + assertEq(cumulativeVolatility, 0, "test_GetParameters::2"); + assertEq(cumulativeBinCrossed, 0, "test_GetParameters::3"); + } + + function test_GetPriceFromId() external { + uint256 delta = uint256(DEFAULT_BIN_STEP) * 5e13; + + assertApproxEqRel( + pairWavax.getPriceFromId(1_000 + 2 ** 23), + 924521306405372907020063908180274956666, + delta, + "test_GetPriceFromId::1" + ); + assertApproxEqRel( + pairWavax.getPriceFromId(2 ** 23 - 1_000), + 125245452360126660303600960578690115355, + delta, + "test_GetPriceFromId::2" + ); + assertApproxEqRel( + pairWavax.getPriceFromId(2 ** 23 + 10_000), + 7457860201113570250644758522304565438757805, + delta, + "test_GetPriceFromId::3" + ); + assertApproxEqRel( + pairWavax.getPriceFromId(2 ** 23 - 10_000), + 15526181252368702469753297095319515, + delta, + "test_GetPriceFromId::4" + ); + // avoid overflow of assertApproxEqRel with a too high price + assertLe( + pairWavax.getPriceFromId(2 ** 23 + 80_000), + 18133092123953330812316154041959812232388892985347108730495479426840526848, + "test_GetPriceFromId::5" + ); + assertGe( + pairWavax.getPriceFromId(2 ** 23 + 80_000), + 18096880266539986845478224721407196147811144510344442837666495029900738560, + "test_GetPriceFromId::6" + ); + assertApproxEqRel(pairWavax.getPriceFromId(2 ** 23 - 80_000), 6392, 1e8, "test_GetPriceFromId::7"); + assertApproxEqRel( + pairWavax.getPriceFromId(2 ** 23 + 12_345), + 77718771515321296819382407317364352468140333, + delta, + "test_GetPriceFromId::8" + ); + assertApproxEqRel( + pairWavax.getPriceFromId(2 ** 23 - 12_345), + 1489885737765286392982993705955521, + delta, + "test_GetPriceFromId::9" + ); + } + + function test_GetIdFromPrice() external { + assertApproxEqAbs( + pairWavax.getIdFromPrice(924521306405372907020063908180274956666), + 1_000 + 2 ** 23, + 1, + "test_GetPriceFromId::1" + ); + assertApproxEqAbs( + pairWavax.getIdFromPrice(125245452360126660303600960578690115355), + 2 ** 23 - 1_000, + 1, + "test_GetPriceFromId::2" + ); + assertApproxEqAbs( + pairWavax.getIdFromPrice(7457860201113570250644758522304565438757805), + 2 ** 23 + 10_000, + 1, + "test_GetPriceFromId::3" + ); + assertApproxEqAbs( + pairWavax.getIdFromPrice(15526181252368702469753297095319515), 2 ** 23 - 10_000, 1, "test_GetPriceFromId::4" + ); + assertApproxEqAbs( + pairWavax.getIdFromPrice(18114977146806524168130684952726477124021312024291123319263609183005067158), + 2 ** 23 + 80_000, + 1, + "test_GetPriceFromId::5" + ); + assertApproxEqAbs(pairWavax.getIdFromPrice(6392), 2 ** 23 - 80_000, 1, "test_GetPriceFromId::6"); + assertApproxEqAbs( + pairWavax.getIdFromPrice(77718771515321296819382407317364352468140333), + 2 ** 23 + 12_345, + 1, + "test_GetPriceFromId::7" + ); + assertApproxEqAbs( + pairWavax.getIdFromPrice(1489885737765286392982993705955521), 2 ** 23 - 12_345, 1, "test_GetPriceFromId::8" + ); + } + + function testFuzz_GetSwapOut(uint128 amountOut, bool swapForY) external { + (uint128 amountIn, uint128 amountOutLeft, uint128 fee) = pairWavax.getSwapIn(amountOut, swapForY); + + assertEq(amountIn, 0, "testFuzz_GetSwapInOut::1"); + assertEq(amountOutLeft, amountOut, "testFuzz_GetSwapInOut::2"); + assertEq(fee, 0, "testFuzz_GetSwapInOut::3"); + } + + function testFuzz_GetSwapIn(uint128 amountIn, bool swapForY) external { + (uint128 amountInLeft, uint128 amountOut, uint128 fee) = pairWavax.getSwapOut(amountIn, swapForY); + + assertEq(amountInLeft, amountIn, "testFuzz_GetSwapInOut::1"); + assertEq(amountOut, 0, "testFuzz_GetSwapInOut::2"); + assertEq(fee, 0, "testFuzz_GetSwapInOut::3"); + } + + function test_revert_SetStaticFeeParameters() external { + vm.expectRevert(ILBPair.LBPair__InvalidStaticFeeParameters.selector); + vm.prank(address(factory)); + pairWavax.setStaticFeeParameters(0, 0, 0, 0, 0, 0, 0); + } +} diff --git a/test/LBPairLiquidity.t.sol b/test/LBPairLiquidity.t.sol new file mode 100644 index 00000000..ecde8a74 --- /dev/null +++ b/test/LBPairLiquidity.t.sol @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "./helpers/TestHelper.sol"; + +contract LBPairLiquidityTest is TestHelper { + using SafeCast for uint256; + + uint256 constant PRECISION = 1e18; + + uint24 immutable activeId = ID_ONE - 24647; // id where 1 AVAX = 20 USDC + + function setUp() public override { + super.setUp(); + + pairWavax = createLBPairFromStartId(wavax, usdc, activeId); + } + + function test_SimpleMint() external { + uint256 amountX = 100 * 10 ** 18; + uint256 amountY = 2_000 * 10 ** 6; + uint8 nbBinX = 6; + uint8 nbBinY = 6; + + addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, nbBinX, nbBinY); + + assertEq(wavax.balanceOf(ALICE), amountX - amountX * (PRECISION / nbBinX) / 1e18 * nbBinX, "test_SimpleMint::1"); + assertEq(usdc.balanceOf(ALICE), amountY - amountY * (PRECISION / nbBinY) / 1e18 * nbBinY, "test_SimpleMint::2"); + + uint256 total = getTotalBins(nbBinX, nbBinY); + for (uint256 i; i < total; ++i) { + uint24 id = getId(activeId, i, nbBinY); + + (uint128 binReserveX, uint128 binReserveY) = pairWavax.getBin(id); + + if (id < activeId) { + assertEq(binReserveX, 0, "test_SimpleMint::3"); + assertEq(binReserveY, amountY * (PRECISION / nbBinY) / 1e18, "test_SimpleMint::4"); + } else if (id == activeId) { + assertApproxEqRel(binReserveX, amountX * (PRECISION / nbBinX) / 1e18, 1e15, "test_SimpleMint::5"); + assertApproxEqRel(binReserveY, amountY * (PRECISION / nbBinY) / 1e18, 1e15, "test_SimpleMint::6"); + } else { + assertEq(binReserveX, amountX * (PRECISION / nbBinX) / 1e18, "test_SimpleMint::7"); + assertEq(binReserveY, 0, "test_SimpleMint::8"); + } + + assertGt(pairWavax.balanceOf(BOB, id), 0, "test_SimpleMint::9"); + assertEq(pairWavax.balanceOf(ALICE, id), 0, "test_SimpleMint::10"); + } + } + + function test_MintTwice() external { + uint256 amountX = 100 * 10 ** 18; + uint256 amountY = 2_000 * 10 ** 6; + uint8 nbBinX = 6; + uint8 nbBinY = 6; + + addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, nbBinX, nbBinY); + + uint256 total = getTotalBins(nbBinX, nbBinY); + uint256[] memory balances = new uint256[](total); + + for (uint256 i; i < total; ++i) { + uint24 id = getId(activeId, i, nbBinY); + + balances[i] = pairWavax.balanceOf(BOB, id); + } + + addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, nbBinX, nbBinY); + + for (uint256 i; i < total; ++i) { + uint24 id = getId(activeId, i, nbBinY); + + (uint128 binReserveX, uint128 binReserveY) = pairWavax.getBin(id); + + if (id < activeId) { + assertEq(binReserveX, 0, "test_SimpleMint::1"); + assertEq(binReserveY, 2 * (amountY * (PRECISION / nbBinY) / 1e18), "test_SimpleMint::2"); + } else if (id == activeId) { + assertApproxEqRel(binReserveX, 2 * (amountX * (PRECISION / nbBinX) / 1e18), 1e15, "test_SimpleMint::3"); + assertApproxEqRel(binReserveY, 2 * (amountY * (PRECISION / nbBinY) / 1e18), 1e15, "test_SimpleMint::4"); + } else { + assertEq(binReserveX, 2 * (amountX * (PRECISION / nbBinX) / 1e18), "test_SimpleMint::5"); + assertEq(binReserveY, 0, "test_SimpleMint::6"); + } + + assertEq(pairWavax.balanceOf(BOB, id), 2 * balances[i], "test_DoubleMint::7"); + } + } + + function test_MintWithDifferentBins() external { + uint256 amountX = 100 * 10 ** 18; + uint256 amountY = 2_000 * 10 ** 6; + uint8 nbBinX = 6; + uint8 nbBinY = 6; + + addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, nbBinX, nbBinY); + + uint256 total = getTotalBins(nbBinX, nbBinY); + uint256[] memory balances = new uint256[](total); + + for (uint256 i; i < total; ++i) { + uint24 id = getId(activeId, i, nbBinY); + + balances[i] = pairWavax.balanceOf(BOB, id); + } + + addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, nbBinX, 0); + addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, 0, nbBinY); + + for (uint256 i; i < total; ++i) { + uint24 id = getId(activeId, i, nbBinY); + + if (id == activeId) { + assertApproxEqRel(pairWavax.balanceOf(BOB, id), 2 * balances[i], 1e15, "test_MintWithDifferentBins::1"); // composition fee + } else { + assertEq(pairWavax.balanceOf(BOB, id), 2 * balances[i], "test_MintWithDifferentBins::2"); + } + } + } + + function test_SimpleBurn() external { + uint256 amountX = 100 * 10 ** 18; + uint256 amountY = 2_000 * 10 ** 6; + uint8 nbBinX = 6; + uint8 nbBinY = 6; + + addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, nbBinX, nbBinY); + + uint256 total = getTotalBins(nbBinX, nbBinY); + + uint256[] memory balances = new uint256[](total); + uint256[] memory ids = new uint256[](total); + + for (uint256 i; i < total; ++i) { + uint24 id = getId(activeId, i, nbBinY); + + ids[i] = id; + balances[i] = pairWavax.balanceOf(BOB, id); + } + + (uint128 reserveX, uint128 reserveY) = pairWavax.getReserves(); + + vm.prank(BOB); + pairWavax.burn(BOB, BOB, ids, balances); + + assertEq(wavax.balanceOf(BOB), reserveX, "test_SimpleBurn::1"); + assertEq(usdc.balanceOf(BOB), reserveY, "test_SimpleBurn::2"); + (reserveX, reserveY) = pairWavax.getReserves(); + + assertEq(reserveX, 0, "test_BurnPartial::3"); + assertEq(reserveY, 0, "test_BurnPartial::4"); + } + + function test_BurnHalfTwice() external { + uint256 amountX = 100 * 10 ** 18; + uint256 amountY = 2_000 * 10 ** 6; + uint8 nbBinX = 6; + uint8 nbBinY = 6; + + addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, nbBinX, nbBinY); + + uint256 total = getTotalBins(nbBinX, nbBinY); + + uint256[] memory halfbalances = new uint256[](total); + uint256[] memory balances = new uint256[](total); + uint256[] memory ids = new uint256[](total); + + for (uint256 i; i < total; ++i) { + uint24 id = getId(activeId, i, nbBinY); + + ids[i] = id; + uint256 balance = pairWavax.balanceOf(BOB, id); + + halfbalances[i] = balance / 2; + balances[i] = balance - balance / 2; + } + + (uint128 reserveX, uint128 reserveY) = pairWavax.getReserves(); + + vm.prank(BOB); + pairWavax.burn(BOB, BOB, ids, halfbalances); + + assertApproxEqRel(wavax.balanceOf(BOB), reserveX / 2, 1e10, "test_BurnPartial::1"); + assertApproxEqRel(usdc.balanceOf(BOB), reserveY / 2, 1e10, "test_BurnPartial::2"); + + vm.prank(BOB); + pairWavax.burn(BOB, BOB, ids, balances); + + assertEq(wavax.balanceOf(BOB), reserveX, "test_BurnPartial::3"); + assertEq(usdc.balanceOf(BOB), reserveY, "test_BurnPartial::4"); + + (reserveX, reserveY) = pairWavax.getReserves(); + + assertEq(reserveX, 0, "test_BurnPartial::5"); + assertEq(reserveY, 0, "test_BurnPartial::6"); + } + + function test_GetNextNonEmptyBin() external { + uint8 nbBinX = 6; + uint8 nbBinY = 6; + + addLiquidity(ALICE, BOB, pairWavax, activeId, 100 * 10 ** 18, 2_000 * 10 ** 6, nbBinX, nbBinY); + + uint24 lowId = activeId - nbBinY + 1; + uint24 upperId = activeId + nbBinX - 1; + + uint24 id = pairWavax.getNextNonEmptyBin(false, 0); + + assertEq(id, lowId, "test_GetNextNonEmptyBin::1"); + + uint256 total = getTotalBins(nbBinX, nbBinY); + + for (uint256 i; i < total - 1; ++i) { + id = pairWavax.getNextNonEmptyBin(false, id); + + assertEq(id, lowId + i + 1, "test_GetNextNonEmptyBin::2"); + } + + id = pairWavax.getNextNonEmptyBin(true, type(uint24).max); + + assertEq(id, upperId, "test_GetNextNonEmptyBin::3"); + + uint256[] memory ids = new uint256[](1); + uint256[] memory balances = new uint256[](1); + + ids[0] = activeId; + balances[0] = pairWavax.balanceOf(BOB, activeId); + + vm.prank(BOB); + pairWavax.burn(BOB, BOB, ids, balances); + + id = pairWavax.getNextNonEmptyBin(false, activeId - 1); + + assertEq(id, activeId + 1, "test_GetNextNonEmptyBin::4"); + + id = pairWavax.getNextNonEmptyBin(true, activeId + 1); + + assertEq(id, activeId - 1, "test_GetNextNonEmptyBin::5"); + } + + function test_revert_MintEmptyConfig() external { + bytes32[] memory data = new bytes32[](0); + vm.expectRevert(ILBPair.LBPair__EmptyMarketConfigs.selector); + pairWavax.mint(BOB, data, BOB); + } + + function test_revert_MintZeroShares() external { + bytes32[] memory data = new bytes32[](1); + data[0] = LiquidityConfigurations.encodeParams(1e18, 1e18, activeId); + vm.expectRevert(abi.encodeWithSelector(ILBPair.LBPair__ZeroShares.selector, activeId)); + pairWavax.mint(BOB, data, BOB); + } + + function test_revert_MintMoreThanAmountSent() external { + deal(address(wavax), address(pairWavax), 1e18); + deal(address(usdc), address(pairWavax), 1e18); + + bytes32[] memory data = new bytes32[](2); + data[0] = LiquidityConfigurations.encodeParams(0, 0.5e18, activeId - 1); + data[1] = LiquidityConfigurations.encodeParams(0, 0.5e18 + 1, activeId); + vm.expectRevert(PackedUint128Math.PackedUint128Math__SubUnderflow.selector); + pairWavax.mint(BOB, data, BOB); + + data[1] = LiquidityConfigurations.encodeParams(0.5e18, 0, activeId); + data[0] = LiquidityConfigurations.encodeParams(0.5e18 + 1, 0, activeId + 1); + vm.expectRevert(PackedUint128Math.PackedUint128Math__SubUnderflow.selector); + pairWavax.mint(BOB, data, BOB); + } + + function test_revert_BurnEmptyArraysOrDifferent() external { + uint256[] memory ids = new uint256[](0); + uint256[] memory balances = new uint256[](1); + + vm.expectRevert(ILBPair.LBPair__InvalidInput.selector); + pairWavax.burn(DEV, DEV, ids, balances); + + ids = new uint256[](1); + balances = new uint256[](0); + + vm.expectRevert(ILBPair.LBPair__InvalidInput.selector); + pairWavax.burn(DEV, DEV, ids, balances); + + ids = new uint256[](0); + balances = new uint256[](0); + + vm.expectRevert(ILBPair.LBPair__InvalidInput.selector); + pairWavax.burn(DEV, DEV, ids, balances); + + ids = new uint256[](1); + balances = new uint256[](2); + + vm.expectRevert(ILBPair.LBPair__InvalidInput.selector); + pairWavax.burn(DEV, DEV, ids, balances); + } + + function test_revert_BurnMoreThanBalance() external { + addLiquidity(ALICE, ALICE, pairWavax, activeId, 1e18, 1e18, 1, 0); + addLiquidity(DEV, DEV, pairWavax, activeId, 1e18, 1e18, 1, 0); + + uint256[] memory ids = new uint256[](1); + uint256[] memory balances = new uint256[](1); + + ids[0] = activeId; + balances[0] = pairWavax.balanceOf(DEV, activeId) + 1; + + vm.expectRevert( + abi.encodeWithSelector(ILBToken.LBToken__BurnExceedsBalance.selector, DEV, activeId, balances[0]) + ); + pairWavax.burn(DEV, DEV, ids, balances); + } + + function test_revert_BurnZeroShares() external { + uint256[] memory ids = new uint256[](1); + uint256[] memory balances = new uint256[](1); + + ids[0] = activeId; + balances[0] = 0; + + vm.expectRevert(abi.encodeWithSelector(ILBPair.LBPair__ZeroAmount.selector, activeId)); + pairWavax.burn(DEV, DEV, ids, balances); + } + + function test_revert_BurnForZeroAmounts() external { + uint256[] memory ids = new uint256[](1); + uint256[] memory balances = new uint256[](1); + + ids[0] = activeId; + balances[0] = 1; + + vm.expectRevert(abi.encodeWithSelector(ILBPair.LBPair__ZeroAmountsOut.selector, activeId)); + pairWavax.burn(DEV, DEV, ids, balances); + } +} diff --git a/test/LBPairOracle.t.sol b/test/LBPairOracle.t.sol new file mode 100644 index 00000000..e1953bba --- /dev/null +++ b/test/LBPairOracle.t.sol @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "./helpers/TestHelper.sol"; + +contract LBPairOracleTest is TestHelper { + using PairParameterHelper for bytes32; + + function setUp() public override { + super.setUp(); + + pairWavax = createLBPair(wavax, usdc); + + addLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 1e18, 10, 10); + } + + function testFuzz_IncreaseOracleLength(uint16 newLength) external { + vm.assume(newLength > 0 && newLength < 100); // 100 is arbitrary, but a reasonable upper bound + + (, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp) = pairWavax.getOracleParameters(); + + assertEq(size, 0, "TestFuzz_IncreaseOracleLength::1"); + assertEq(activeSize, 0, "TestFuzz_IncreaseOracleLength::2"); + assertEq(lastUpdated, 0, "TestFuzz_IncreaseOracleLength::3"); + assertEq(firstTimestamp, 0, "TestFuzz_IncreaseOracleLength::4"); + + pairWavax.increaseOracleLength(newLength); + + (, size, activeSize, lastUpdated, firstTimestamp) = pairWavax.getOracleParameters(); + + assertEq(size, newLength, "TestFuzz_IncreaseOracleLength::5"); + assertEq(activeSize, 0, "TestFuzz_IncreaseOracleLength::6"); + assertEq(lastUpdated, 0, "TestFuzz_IncreaseOracleLength::7"); + assertEq(firstTimestamp, 0, "TestFuzz_IncreaseOracleLength::8"); + } + + function test_1SampleAdded() external { + pairWavax.increaseOracleLength(100); + + deal(address(wavax), BOB, 1e18); + vm.prank(BOB); + wavax.transfer(address(pairWavax), 1e16); + pairWavax.swap(true, BOB); + + (, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp) = pairWavax.getOracleParameters(); + + assertEq(size, 100, "Test_1SampleAdded::1"); + assertEq(activeSize, 1, "Test_1SampleAdded::2"); + assertEq(lastUpdated, block.timestamp, "Test_1SampleAdded::3"); + assertEq(firstTimestamp, block.timestamp, "Test_1SampleAdded::4"); + + vm.warp(block.timestamp + 1); + + vm.prank(BOB); + wavax.transfer(address(pairWavax), 1e16); + pairWavax.swap(true, BOB); + + (, size, activeSize, lastUpdated, firstTimestamp) = pairWavax.getOracleParameters(); + + assertEq(size, 100, "Test_1SampleAdded::5"); + assertEq(activeSize, 1, "Test_1SampleAdded::6"); + assertEq(lastUpdated, block.timestamp, "Test_1SampleAdded::7"); + assertEq(firstTimestamp, block.timestamp, "Test_1SampleAdded::8"); + } + + function test_CircularOracleWith2Samples() external { + pairWavax.increaseOracleLength(2); + + deal(address(wavax), BOB, 1e18); + vm.prank(BOB); + wavax.transfer(address(pairWavax), 1e16); + pairWavax.swap(true, BOB); + + (, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp) = pairWavax.getOracleParameters(); + + assertEq(size, 2, "Test_CircularOracle::1"); + assertEq(activeSize, 1, "Test_CircularOracle::2"); + assertEq(lastUpdated, block.timestamp, "Test_CircularOracle::3"); + assertEq(firstTimestamp, block.timestamp, "Test_CircularOracle::4"); + + vm.warp(block.timestamp + 121); + + vm.prank(BOB); + wavax.transfer(address(pairWavax), 1e16); + pairWavax.swap(true, BOB); + + (, size, activeSize, lastUpdated, firstTimestamp) = pairWavax.getOracleParameters(); + + assertEq(size, 2, "Test_CircularOracle::5"); + assertEq(activeSize, 2, "Test_CircularOracle::6"); + assertEq(lastUpdated, block.timestamp, "Test_CircularOracle::7"); + assertEq(firstTimestamp, block.timestamp - 121, "Test_CircularOracle::8"); + + vm.warp(block.timestamp + 1000); + + vm.prank(BOB); + wavax.transfer(address(pairWavax), 1e16); + pairWavax.swap(true, BOB); + + (, size, activeSize, lastUpdated, firstTimestamp) = pairWavax.getOracleParameters(); + + assertEq(size, 2, "Test_CircularOracle::9"); + assertEq(activeSize, 2, "Test_CircularOracle::10"); + assertEq(lastUpdated, block.timestamp, "Test_CircularOracle::11"); + assertEq(firstTimestamp, block.timestamp - 1000, "Test_CircularOracle::12"); + + vm.warp(block.timestamp + 100); + + (, size, activeSize, lastUpdated, firstTimestamp) = pairWavax.getOracleParameters(); + + assertEq(size, 2, "Test_CircularOracle::13"); + assertEq(activeSize, 2, "Test_CircularOracle::14"); + assertEq(lastUpdated, block.timestamp - 100, "Test_CircularOracle::15"); + assertEq(firstTimestamp, block.timestamp - 1100, "Test_CircularOracle::16"); + } + + function test_CircularOracleGetSampleAt() external { + pairWavax.increaseOracleLength(2); + + deal(address(wavax), BOB, 1e18); + vm.prank(BOB); + wavax.transfer(address(pairWavax), 1e16); + pairWavax.swap(true, BOB); + + uint256 dt = block.timestamp; + + (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = + pairWavax.getOracleSampleAt(uint40(block.timestamp)); + + uint24 activeId = pairWavax.getActiveId(); + + assertEq(cumulativeId, activeId * dt, "Test_CircularOracleGetSampleAt::1"); + assertEq(cumulativeVolatility, 0, "Test_CircularOracleGetSampleAt::2"); + assertEq(cumulativeBinCrossed, 0, "Test_CircularOracleGetSampleAt::3"); + + dt = block.timestamp; + vm.warp(block.timestamp + 121); + + vm.prank(BOB); + wavax.transfer(address(pairWavax), 1e16); + pairWavax.swap(true, BOB); + + dt = block.timestamp - dt; + + (uint64 previousCumulativeId, uint64 previousCumulativeVolatility, uint64 previousCumulativeBinCrossed) = + (cumulativeId, cumulativeVolatility, cumulativeBinCrossed); + (cumulativeId, cumulativeVolatility, cumulativeBinCrossed) = + pairWavax.getOracleSampleAt(uint40(block.timestamp)); + + activeId = pairWavax.getActiveId(); + + assertEq(cumulativeId, previousCumulativeId + activeId * dt, "Test_CircularOracleGetSampleAt::4"); + assertEq(cumulativeVolatility, 0, "Test_CircularOracleGetSampleAt::5"); + assertEq(cumulativeBinCrossed, 0, "Test_CircularOracleGetSampleAt::6"); + + dt = block.timestamp; + vm.warp(block.timestamp + 1000); + + deal(address(usdc), BOB, 1e18); + vm.prank(BOB); + usdc.transfer(address(pairWavax), 1e18); + pairWavax.swap(false, BOB); + + dt = block.timestamp - dt; + + (previousCumulativeId, previousCumulativeVolatility, previousCumulativeBinCrossed) = + (cumulativeId, cumulativeVolatility, cumulativeBinCrossed); + + (cumulativeId, cumulativeVolatility, cumulativeBinCrossed) = + pairWavax.getOracleSampleAt(uint40(block.timestamp)); + + (uint24 volatilityAccumulator,,,) = pairWavax.getVariableFeeParameters(); + + assertEq(cumulativeId, previousCumulativeId + pairWavax.getActiveId() * dt, "Test_CircularOracleGetSampleAt::7"); + assertEq(cumulativeVolatility, volatilityAccumulator * dt, "Test_CircularOracleGetSampleAt::8"); + assertEq(cumulativeBinCrossed, (pairWavax.getActiveId() - activeId) * dt, "Test_CircularOracleGetSampleAt::9"); + } + + function test_MaxLengthOracle() external { + deal(address(wavax), BOB, 1e36); + deal(address(usdc), BOB, 1e36); + + pairWavax.increaseOracleLength(65535); + + vm.warp(1_000); + + vm.startPrank(BOB); + for (uint256 i = 0; i < 65535; i++) { + wavax.transfer(address(pairWavax), 1e10); + pairWavax.swap(true, BOB); + + vm.warp(block.timestamp + 121); + } + vm.stopPrank(); + + (, uint256 size, uint256 activeSize, uint256 lastUpdated, uint256 firstTimestamp) = + pairWavax.getOracleParameters(); + + assertEq(size, 65535, "Test_MaxLengthOracle::1"); + assertEq(activeSize, 65535, "Test_MaxLengthOracle::2"); + assertEq(lastUpdated, block.timestamp - 121, "Test_MaxLengthOracle::3"); + assertEq(firstTimestamp, block.timestamp - 65535 * 121, "Test_MaxLengthOracle::4"); + + uint24 activeId = pairWavax.getActiveId(); + + { + (uint64 cumulativeId1, uint64 cumulativeVolatility1, uint64 cumulativeBinCrossed1) = + pairWavax.getOracleSampleAt(uint40(block.timestamp)); + (uint64 cumulativeId2, uint64 cumulativeVolatility2, uint64 cumulativeBinCrossed2) = + pairWavax.getOracleSampleAt(uint40(block.timestamp - 121)); + + assertEq(cumulativeId1, cumulativeId2 + uint64(activeId) * 121, "Test_MaxLengthOracle::5"); + + // True as the active id never changed: + assertEq(cumulativeVolatility1, 0, "Test_MaxLengthOracle::6"); + assertEq(cumulativeBinCrossed1, 0, "Test_MaxLengthOracle::7"); + assertEq(cumulativeVolatility2, 0, "Test_MaxLengthOracle::8"); + assertEq(cumulativeBinCrossed2, 0, "Test_MaxLengthOracle::9"); + assertEq((cumulativeId1 - cumulativeId2) / 121, activeId, "Test_MaxLengthOracle::10"); + assertEq(cumulativeBinCrossed1, 0, "Test_MaxLengthOracle::11"); + + (cumulativeId2,,) = pairWavax.getOracleSampleAt(uint40(block.timestamp / 2)); + assertEq( + uint256(cumulativeId1) * 1e18 / block.timestamp, + uint256(cumulativeId2) * 1e18 / (block.timestamp / 2), + "Test_MaxLengthOracle::10" + ); + } + + // now a swap that moves ids: + vm.warp(block.timestamp + 1000 - 121); + + vm.prank(BOB); + usdc.transfer(address(pairWavax), 1e18); + pairWavax.swap(false, BOB); + + uint64 newActiveId = pairWavax.getActiveId(); + + (uint64 cumulativeIdNow, uint64 cumulativeVolatilityNow, uint64 cumulativeBinCrossedNow) = + pairWavax.getOracleSampleAt(uint40(block.timestamp)); + + (uint64 cumulativeIdPastHour, uint64 cumulativeVolatilityPastHour, uint64 cumulativeBinCrossedPastHour) = + pairWavax.getOracleSampleAt(uint40(block.timestamp - 3600)); + + (uint64 cumulativeIdPastDay, uint64 cumulativeVolatilityPastDay, uint64 cumulativeBinCrossedPastDay) = + pairWavax.getOracleSampleAt(uint40(block.timestamp - 86400)); + + assertEq(cumulativeVolatilityPastDay, 0, "Test_MaxLengthOracle::13"); + assertEq(cumulativeBinCrossedPastDay, 0, "Test_MaxLengthOracle::14"); + + assertEq(cumulativeVolatilityPastHour, 0, "Test_MaxLengthOracle::15"); + assertEq(cumulativeBinCrossedPastHour, 0, "Test_MaxLengthOracle::16"); + + assertEq(cumulativeIdPastDay + uint64(activeId) * 3600 * 23, cumulativeIdPastHour, "Test_MaxLengthOracle::17"); + + assertEq( + cumulativeIdPastHour + uint64(activeId) * 2600 + uint64(newActiveId) * 1000, + cumulativeIdNow, + "Test_MaxLengthOracle::18" + ); + assertEq(cumulativeVolatilityNow, (newActiveId - activeId) * 10_000 * 1000, "Test_MaxLengthOracle::19"); + assertEq(cumulativeBinCrossedNow, (newActiveId - activeId) * 1000, "Test_MaxLengthOracle::20"); + } +} diff --git a/test/LBPairSwap.t.sol b/test/LBPairSwap.t.sol new file mode 100644 index 00000000..018fea63 --- /dev/null +++ b/test/LBPairSwap.t.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "./helpers/TestHelper.sol"; + +contract LBPairSwapTest is TestHelper { + using SafeCast for uint256; + + function setUp() public override { + super.setUp(); + + pairWavax = createLBPair(wavax, usdc); + + addLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 1e18, 50, 50); + } + + function testFuzz_SwapInForY(uint128 amountOut) public { + vm.assume(amountOut > 0 && amountOut < 1e18); + + (uint128 amountIn, uint128 amountOutLeft,) = pairWavax.getSwapIn(amountOut, true); + + assertEq(amountOutLeft, 0, "TestFuzz_SwapInForY::1"); + + deal(address(wavax), ALICE, amountIn); + + vm.startPrank(ALICE); + wavax.transfer(address(pairWavax), amountIn); + pairWavax.swap(true, ALICE); + vm.stopPrank(); + + assertEq(wavax.balanceOf(ALICE), 0, "TestFuzz_SwapInForY::2"); + assertEq(usdc.balanceOf(ALICE), amountOut, "TestFuzz_SwapInForY::3"); + } + + function testFuzz_SwapInForX(uint128 amountOut) public { + vm.assume(amountOut > 0 && amountOut < 1e18); + + (uint128 amountIn, uint128 amountOutLeft,) = pairWavax.getSwapIn(amountOut, false); + + assertEq(amountOutLeft, 0, "TestFuzz_SwapInForX::1"); + + deal(address(usdc), ALICE, amountIn); + + vm.startPrank(ALICE); + usdc.transfer(address(pairWavax), amountIn); + pairWavax.swap(false, ALICE); + vm.stopPrank(); + + assertEq(usdc.balanceOf(ALICE), 0, "TestFuzz_SwapInForX::2"); + assertEq(wavax.balanceOf(ALICE), amountOut, "TestFuzz_SwapInForX::3"); + } + + function testFuzz_SwapOutForY(uint128 amountIn) public { + vm.assume(amountIn > 0 && amountIn <= 1e18); + + (uint128 amountInLeft, uint128 amountOut,) = pairWavax.getSwapOut(amountIn, true); + + vm.assume(amountOut > 0); + + assertEq(amountInLeft, 0, "TestFuzz_SwapOutForY::1"); + + deal(address(wavax), ALICE, amountIn); + + vm.startPrank(ALICE); + wavax.transfer(address(pairWavax), amountIn); + pairWavax.swap(true, ALICE); + vm.stopPrank(); + + assertEq(wavax.balanceOf(ALICE), 0, "TestFuzz_SwapOutForY::2"); + assertEq(usdc.balanceOf(ALICE), amountOut, "TestFuzz_SwapOutForY::3"); + } + + function testFuzz_SwapOutForX(uint128 amountIn) public { + vm.assume(amountIn > 0 && amountIn <= 1e18); + + (uint128 amountInLeft, uint128 amountOut,) = pairWavax.getSwapOut(amountIn, false); + + vm.assume(amountOut > 0); + + assertEq(amountInLeft, 0, "TestFuzz_SwapOutForX::1"); + + deal(address(usdc), ALICE, amountIn); + + vm.startPrank(ALICE); + usdc.transfer(address(pairWavax), amountIn); + pairWavax.swap(false, ALICE); + vm.stopPrank(); + + assertEq(usdc.balanceOf(ALICE), 0, "TestFuzz_SwapOutForX::2"); + assertEq(wavax.balanceOf(ALICE), amountOut, "TestFuzz_SwapOutForX::3"); + } + + function test_revert_SwapInsufficientAmountIn() external { + vm.expectRevert(ILBPair.LBPair__InsufficientAmountIn.selector); + pairWavax.swap(true, ALICE); + + vm.expectRevert(ILBPair.LBPair__InsufficientAmountIn.selector); + pairWavax.swap(false, ALICE); + } + + function test_revert_SwapInsufficientAmountOut() external { + deal(address(wavax), ALICE, 1); + deal(address(usdc), ALICE, 1); + + vm.prank(ALICE); + wavax.transfer(address(pairWavax), 1); + + vm.expectRevert(ILBPair.LBPair__InsufficientAmountOut.selector); + pairWavax.swap(true, ALICE); + + vm.prank(ALICE); + usdc.transfer(address(pairWavax), 1); + + vm.expectRevert(ILBPair.LBPair__InsufficientAmountOut.selector); + pairWavax.swap(false, ALICE); + } + + function test_revert_SwapOutOfLiquidity() external { + deal(address(wavax), ALICE, 2e18); + deal(address(usdc), ALICE, 2e18); + + vm.prank(ALICE); + wavax.transfer(address(pairWavax), 2e18); + + vm.expectRevert(ILBPair.LBPair__OutOfLiquidity.selector); + pairWavax.swap(true, ALICE); + + vm.prank(ALICE); + usdc.transfer(address(pairWavax), 2e18); + + vm.expectRevert(ILBPair.LBPair__OutOfLiquidity.selector); + pairWavax.swap(false, ALICE); + } +} diff --git a/test/helpers/TestHelper.sol b/test/helpers/TestHelper.sol index b9632c71..4d7a0624 100644 --- a/test/helpers/TestHelper.sol +++ b/test/helpers/TestHelper.sol @@ -24,7 +24,7 @@ import "./Utils.sol"; import "test/mocks/WAVAX.sol"; import "test/mocks/ERC20.sol"; -import "test/mocks/FlashloanBorrower.sol"; +import "test/mocks/FlashBorrower.sol"; import "test/mocks/ERC20TransferTax.sol"; import {AvalancheAddresses} from "../integration/Addresses.sol"; @@ -33,19 +33,20 @@ abstract contract TestHelper is Test { using Uint256x256Math for uint256; using Utils for uint256[]; using Utils for int256[]; + using SafeCast for uint256; uint24 internal constant ID_ONE = 2 ** 23; uint256 internal constant BASIS_POINT_MAX = 10_000; // Avalanche market config for 10bps - uint8 internal constant DEFAULT_BIN_STEP = 10; - uint16 internal constant DEFAULT_BASE_FACTOR = 1000; + uint8 internal constant DEFAULT_BIN_STEP = 20; + uint16 internal constant DEFAULT_BASE_FACTOR = 5_000; uint16 internal constant DEFAULT_FILTER_PERIOD = 30; uint16 internal constant DEFAULT_DECAY_PERIOD = 600; uint16 internal constant DEFAULT_REDUCTION_FACTOR = 5_000; uint24 internal constant DEFAULT_VARIABLE_FEE_CONTROL = 40_000; uint16 internal constant DEFAULT_PROTOCOL_SHARE = 1_000; - uint24 internal constant DEFAULT_MAX_VOLATILITY_ACCUMULATED = 350_000; + uint24 internal constant DEFAULT_MAX_VOLATILITY_ACCUMULATOR = 350_000; uint256 internal constant DEFAULT_FLASHLOAN_FEE = 8e14; address payable immutable DEV = payable(address(this)); @@ -204,7 +205,7 @@ abstract contract TestHelper is Test { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED + DEFAULT_MAX_VOLATILITY_ACCUMULATOR ); } @@ -289,4 +290,76 @@ abstract contract TestHelper is Test { } } } + + function addLiquidity( + address from, + address to, + LBPair lbPair, + uint24 activeId, + uint256 amountX, + uint256 amountY, + uint8 nbBinX, + uint8 nbBinY + ) public { + deal(address(wavax), from, amountX); + deal(address(usdc), from, amountY); + + uint256 total = getTotalBins(nbBinX, nbBinY); + + bytes32[] memory liquidityConfigurations = new bytes32[](total); + + for (uint256 i; i < total; ++i) { + uint24 id = getId(activeId, i, nbBinY); + + uint64 distribX = id >= activeId && nbBinX > 0 ? (Constants.PRECISION / nbBinX).safe64() : 0; + uint64 distribY = id <= activeId && nbBinY > 0 ? (Constants.PRECISION / nbBinY).safe64() : 0; + + liquidityConfigurations[i] = LiquidityConfigurations.encodeParams(distribX, distribY, id); + } + + vm.startPrank(from); + wavax.transfer(address(lbPair), amountX); + usdc.transfer(address(lbPair), amountY); + vm.stopPrank(); + + lbPair.mint(to, liquidityConfigurations, from); + } + + function removeLiquidity( + address from, + address to, + LBPair lbPair, + uint24 activeId, + uint256 percentToBurn, + uint8 nbBinX, + uint8 nbBinY + ) public { + require(percentToBurn <= Constants.PRECISION, "Percent to burn too high"); + + uint256 total = getTotalBins(nbBinX, nbBinY); + + uint256[] memory ids = new uint256[](total); + uint256[] memory amounts = new uint256[](total); + + for (uint256 i; i < total; ++i) { + uint24 id = getId(activeId, i, nbBinY); + + ids[i] = id; + amounts[i] = lbPair.balanceOf(from, id) * percentToBurn / Constants.PRECISION; + } + + vm.prank(from); + lbPair.burn(from, to, ids, amounts); + } + + function getTotalBins(uint8 nbBinX, uint8 nbBinY) public pure returns (uint256) { + return nbBinX > 0 && nbBinY > 0 ? nbBinX + nbBinY - 1 : nbBinX + nbBinY; + } + + function getId(uint24 activeId, uint256 i, uint8 nbBinY) public pure returns (uint24) { + uint256 id = activeId + i; + id = nbBinY > 0 ? id - nbBinY + 1 : id; + + return id.safe24(); + } } diff --git a/test/libraries/BinHelper.t.sol b/test/libraries/BinHelper.t.sol index db4b5bd0..4315eadd 100644 --- a/test/libraries/BinHelper.t.sol +++ b/test/libraries/BinHelper.t.sol @@ -153,7 +153,7 @@ contract BinHelperTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED + DEFAULT_MAX_VOLATILITY_ACCUMULATOR ); bytes32 compositionFees = binReserves.getCompositionFees(parameters, binStep, amountsIn, totalSupply, shares); @@ -189,7 +189,7 @@ contract BinHelperTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED + DEFAULT_MAX_VOLATILITY_ACCUMULATOR ); uint24 activeId = uint24(uint256(int256(uint256(ID_ONE)) + deltaId)); @@ -210,19 +210,25 @@ contract BinHelperTest is TestHelper { (bytes32 amountsInToBin, bytes32 amountsOutOfBin, bytes32 totalFees) = reserves.getAmounts(parameters, DEFAULT_BIN_STEP, swapForY, activeId, amountIn.encode(swapForY)); - assertLe(amountsInToBin.add(totalFees).decode(swapForY), amountIn, "test_GetAmounts::1"); + assertLe(amountsInToBin.decode(swapForY), amountIn, "test_GetAmounts::1"); - uint256 amountInForSwap = amountsInToBin.add(totalFees).decode(swapForY); + uint256 amountInWithoutFees = amountsInToBin.sub(totalFees).decode(swapForY); (uint256 amountOutWithNoFees, uint256 amountOut) = swapForY - ? (price.mulShiftRoundDown(amountInForSwap, Constants.SCALE_OFFSET), amountsOutOfBin.decodeSecond()) - : (uint256(amountInForSwap).shiftDivRoundDown(Constants.SCALE_OFFSET, price), amountsOutOfBin.decodeFirst()); + ? ( + price.mulShiftRoundDown(amountsInToBin.decodeFirst(), Constants.SCALE_OFFSET), + amountsOutOfBin.decodeSecond() + ) + : ( + uint256(amountsInToBin.decodeSecond()).shiftDivRoundDown(Constants.SCALE_OFFSET, price), + amountsOutOfBin.decodeFirst() + ); assertGe(amountOutWithNoFees, amountOut, "test_GetAmounts::2"); uint256 amountOutWithFees = swapForY - ? price.mulShiftRoundDown(amountsInToBin.decodeFirst(), Constants.SCALE_OFFSET) - : uint256(amountsInToBin.decodeSecond()).shiftDivRoundDown(Constants.SCALE_OFFSET, price); + ? price.mulShiftRoundDown(amountInWithoutFees, Constants.SCALE_OFFSET) + : amountInWithoutFees.shiftDivRoundDown(Constants.SCALE_OFFSET, price); assertEq(amountOut, amountOutWithFees, "test_GetAmounts::3"); } @@ -241,7 +247,7 @@ contract BinHelperTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED + DEFAULT_MAX_VOLATILITY_ACCUMULATOR ); uint24 activeId = uint24(uint256(int256(uint256(ID_ONE)) + deltaId)); @@ -262,10 +268,10 @@ contract BinHelperTest is TestHelper { (bytes32 amountsInToBin, bytes32 amountsOutOfBin, bytes32 totalFees) = reserves.getAmounts(parameters, DEFAULT_BIN_STEP, swapForY, activeId, amountIn.encode(swapForY)); - assertLe(amountsInToBin.add(totalFees).decode(swapForY), amountIn, "test_GetAmounts::1"); + assertLe(amountsInToBin.decode(swapForY), amountIn, "test_GetAmounts::1"); { - uint256 amountInForSwap = amountsInToBin.add(totalFees).decode(swapForY); + uint256 amountInForSwap = amountsInToBin.decode(swapForY); (uint256 amountOutWithNoFees, uint256 amountOut) = swapForY ? (price.mulShiftRoundDown(amountInForSwap, Constants.SCALE_OFFSET), amountsOutOfBin.decodeSecond()) @@ -274,7 +280,7 @@ contract BinHelperTest is TestHelper { assertGe(amountOutWithNoFees, amountOut, "test_GetAmounts::2"); } - uint128 amountInToBin = amountsInToBin.decode(swapForY); + uint128 amountInToBin = amountsInToBin.sub(totalFees).decode(swapForY); (uint256 amountOutWithFees, uint256 amountOutWithFeesAmountInSub1) = amountInToBin == 0 ? (0, 0) diff --git a/test/libraries/OracleHelper.t.sol b/test/libraries/OracleHelper.t.sol index 6ec647db..1990a449 100644 --- a/test/libraries/OracleHelper.t.sol +++ b/test/libraries/OracleHelper.t.sol @@ -294,7 +294,7 @@ contract OracleHelperTest is Test { } function testFuzz_IncreaseOracleLength(uint16 length, uint16 newLength) external { - vm.assume(length > 0 && newLength > length); + vm.assume(length > 0 && newLength > length && newLength <= 100); // 100 is arbitrary to avoid tests taking too long uint16 oracleId = 1; diff --git a/test/libraries/PairParameterHelper.t.sol b/test/libraries/PairParameterHelper.t.sol index c7509f3e..9424df06 100644 --- a/test/libraries/PairParameterHelper.t.sol +++ b/test/libraries/PairParameterHelper.t.sol @@ -16,7 +16,7 @@ contract PairParameterHelperTest is Test { uint16 reductionFactor; uint24 variableFeeControl; uint16 protocolShare; - uint24 maxVolatilityAccumulated; + uint24 maxVolatilityAccumulator; } function testFuzz_StaticFeeParameters(bytes32 params, StaticFeeParameters memory sfp) external { @@ -24,7 +24,7 @@ contract PairParameterHelperTest is Test { sfp.filterPeriod <= sfp.decayPeriod && sfp.decayPeriod <= Encoded.MASK_UINT12 && sfp.reductionFactor <= Constants.BASIS_POINT_MAX && sfp.protocolShare <= PairParameterHelper.MAX_PROTOCOL_SHARE - && sfp.maxVolatilityAccumulated <= Encoded.MASK_UINT20 + && sfp.maxVolatilityAccumulator <= Encoded.MASK_UINT20 ); bytes32 newParams = params.setStaticFeeParameters( @@ -34,7 +34,7 @@ contract PairParameterHelperTest is Test { sfp.reductionFactor, sfp.variableFeeControl, sfp.protocolShare, - sfp.maxVolatilityAccumulated + sfp.maxVolatilityAccumulator ); assertEq( @@ -50,7 +50,7 @@ contract PairParameterHelperTest is Test { assertEq(newParams.getVariableFeeControl(), sfp.variableFeeControl, "testFuzz_StaticFeeParameters::6"); assertEq(newParams.getProtocolShare(), sfp.protocolShare, "testFuzz_StaticFeeParameters::7"); assertEq( - newParams.getMaxVolatilityAccumulated(), sfp.maxVolatilityAccumulated, "testFuzz_StaticFeeParameters::8" + newParams.getMaxVolatilityAccumulator(), sfp.maxVolatilityAccumulator, "testFuzz_StaticFeeParameters::8" ); } @@ -59,7 +59,7 @@ contract PairParameterHelperTest is Test { sfp.filterPeriod > sfp.decayPeriod || sfp.decayPeriod > Encoded.MASK_UINT12 || sfp.reductionFactor > Constants.BASIS_POINT_MAX || sfp.protocolShare > PairParameterHelper.MAX_PROTOCOL_SHARE - || sfp.maxVolatilityAccumulated > Encoded.MASK_UINT20 + || sfp.maxVolatilityAccumulator > Encoded.MASK_UINT20 ); vm.expectRevert(PairParameterHelper.PairParametersHelper__InvalidParameter.selector); @@ -70,7 +70,7 @@ contract PairParameterHelperTest is Test { sfp.reductionFactor, sfp.variableFeeControl, sfp.protocolShare, - sfp.maxVolatilityAccumulated + sfp.maxVolatilityAccumulator ); } @@ -128,7 +128,7 @@ contract PairParameterHelperTest is Test { assertEq(baseFee, uint256(params.getBaseFactor()) * binStep * 5e9, "test_getBaseAndVariableFees::1"); - uint256 prod = uint256(params.getVolatilityAccumulated()) * binStep; + uint256 prod = uint256(params.getVolatilityAccumulator()) * binStep; assertEq( variableFee, (prod * prod * params.getVariableFeeControl() + 399) / 400, "test_getBaseAndVariableFees::2" ); @@ -166,18 +166,18 @@ contract PairParameterHelperTest is Test { } function testFuzz_UpdateVolatilityReference(bytes32 params) external { - uint256 volAccumulated = params.getVolatilityAccumulated(); + uint256 volAccumulator = params.getVolatilityAccumulator(); uint256 reductionFactor = params.getReductionFactor(); - uint256 newVolAccumulated = volAccumulated * reductionFactor / Constants.BASIS_POINT_MAX; + uint256 newVolAccumulator = volAccumulator * reductionFactor / Constants.BASIS_POINT_MAX; - if (newVolAccumulated > Encoded.MASK_UINT20) { + if (newVolAccumulator > Encoded.MASK_UINT20) { vm.expectRevert(PairParameterHelper.PairParametersHelper__InvalidParameter.selector); params.updateVolatilityReference(); } else { bytes32 newParams = params.updateVolatilityReference(); - assertEq(newParams.getVolatilityReference(), newVolAccumulated, "test_UpdateVolatilityReference::1"); + assertEq(newParams.getVolatilityReference(), newVolAccumulator, "test_UpdateVolatilityReference::1"); assertEq( newParams & bytes32(~Encoded.MASK_UINT20 << PairParameterHelper.OFFSET_VOL_REF), params & bytes32(~Encoded.MASK_UINT20 << PairParameterHelper.OFFSET_VOL_REF), @@ -186,21 +186,22 @@ contract PairParameterHelperTest is Test { } } - function testFuzz_UpdateVolatilityAccumulated(bytes32 params, uint24 activeId) external { - uint256 deltaId = params.getDeltaId(activeId); + function testFuzz_UpdateVolatilityAccumulator(bytes32 params, uint24 activeId) external { + uint256 idReference = params.getIdReference(); + uint256 deltaId = activeId > idReference ? activeId - idReference : idReference - activeId; - uint256 volAccumulated = params.getVolatilityAccumulated() + deltaId * Constants.BASIS_POINT_MAX; - volAccumulated = volAccumulated > params.getMaxVolatilityAccumulated() - ? params.getMaxVolatilityAccumulated() - : volAccumulated; + uint256 volAccumulator = params.getVolatilityReference() + deltaId * Constants.BASIS_POINT_MAX; + volAccumulator = volAccumulator > params.getMaxVolatilityAccumulator() + ? params.getMaxVolatilityAccumulator() + : volAccumulator; - bytes32 newParams = params.updateVolatilityAccumulated(activeId); + bytes32 newParams = params.updateVolatilityAccumulator(activeId); - assertEq(newParams.getVolatilityAccumulated(), volAccumulated, "test_UpdateVolatilityAccumulated::1"); + assertEq(newParams.getVolatilityAccumulator(), volAccumulator, "test_UpdateVolatilityAccumulator::1"); assertEq( newParams & bytes32(~Encoded.MASK_UINT20 << PairParameterHelper.OFFSET_VOL_ACC), params & bytes32(~Encoded.MASK_UINT20 << PairParameterHelper.OFFSET_VOL_ACC), - "test_UpdateVolatilityAccumulated::2" + "test_UpdateVolatilityAccumulator::2" ); } @@ -211,7 +212,7 @@ contract PairParameterHelperTest is Test { previousTime <= time && sfp.filterPeriod <= sfp.decayPeriod && sfp.decayPeriod <= Encoded.MASK_UINT12 && sfp.reductionFactor <= Constants.BASIS_POINT_MAX && sfp.protocolShare <= PairParameterHelper.MAX_PROTOCOL_SHARE - && sfp.maxVolatilityAccumulated <= Encoded.MASK_UINT20 + && sfp.maxVolatilityAccumulator <= Encoded.MASK_UINT20 ); vm.warp(previousTime); @@ -223,7 +224,7 @@ contract PairParameterHelperTest is Test { sfp.reductionFactor, sfp.variableFeeControl, sfp.protocolShare, - sfp.maxVolatilityAccumulated + sfp.maxVolatilityAccumulator ).updateTimeOfLastUpdate(); vm.warp(time); @@ -275,7 +276,7 @@ contract PairParameterHelperTest is Test { previousTime <= time && sfp.filterPeriod <= sfp.decayPeriod && sfp.decayPeriod <= Encoded.MASK_UINT12 && sfp.reductionFactor <= Constants.BASIS_POINT_MAX && sfp.protocolShare <= PairParameterHelper.MAX_PROTOCOL_SHARE - && sfp.maxVolatilityAccumulated <= Encoded.MASK_UINT20 + && sfp.maxVolatilityAccumulator <= Encoded.MASK_UINT20 ); vm.warp(previousTime); @@ -287,12 +288,12 @@ contract PairParameterHelperTest is Test { sfp.reductionFactor, sfp.variableFeeControl, sfp.protocolShare, - sfp.maxVolatilityAccumulated + sfp.maxVolatilityAccumulator ).updateTimeOfLastUpdate(); vm.warp(time); - bytes32 trustedParams = params.updateReferences().updateVolatilityAccumulated(activeId); + bytes32 trustedParams = params.updateReferences().updateVolatilityAccumulator(activeId); bytes32 newParams = params.updateVolatilityParameters(activeId); assertEq(newParams.getIdReference(), trustedParams.getIdReference(), "test_UpdateVolatilityParameters::1"); @@ -302,8 +303,8 @@ contract PairParameterHelperTest is Test { "test_UpdateVolatilityParameters::2" ); assertEq( - newParams.getVolatilityAccumulated(), - trustedParams.getVolatilityAccumulated(), + newParams.getVolatilityAccumulator(), + trustedParams.getVolatilityAccumulator(), "test_UpdateVolatilityParameters::3" ); assertEq(newParams.getTimeOfLastUpdate(), time, "test_UpdateVolatilityParameters::4"); diff --git a/test/libraries/math/SampleMath.t.sol b/test/libraries/math/SampleMath.t.sol index 2135e826..38e03f82 100644 --- a/test/libraries/math/SampleMath.t.sol +++ b/test/libraries/math/SampleMath.t.sol @@ -118,14 +118,14 @@ contract SampleMathTest is Test { } } - function testFuzz_update(uint40 deltaTime, uint24 activeId, uint24 volatilityAccumulated, uint24 binCrossed) + function testFuzz_update(uint40 deltaTime, uint24 activeId, uint24 volatilityAccumulator, uint24 binCrossed) external { (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = - bytes32(0).update(deltaTime, activeId, volatilityAccumulated, binCrossed); + bytes32(0).update(deltaTime, activeId, volatilityAccumulator, binCrossed); assertEq(cumulativeId, uint64(activeId) * deltaTime, "testFuzz_update::1"); - assertEq(cumulativeVolatility, uint64(volatilityAccumulated) * deltaTime, "testFuzz_update::2"); + assertEq(cumulativeVolatility, uint64(volatilityAccumulator) * deltaTime, "testFuzz_update::2"); assertEq(cumulativeBinCrossed, uint64(binCrossed) * deltaTime, "testFuzz_update::3"); } @@ -133,7 +133,7 @@ contract SampleMathTest is Test { bytes32 sample, uint40 deltaTime, uint24 activeId, - uint24 volatilityAccumulated, + uint24 volatilityAccumulator, uint24 binCrossed ) external { uint64 currentCumulativeId = sample.getCumulativeId(); @@ -141,26 +141,26 @@ contract SampleMathTest is Test { uint64 currentCumulativeBinCrossed = sample.getCumulativeBinCrossed(); uint64(deltaTime) * activeId; - uint64(deltaTime) * volatilityAccumulated; + uint64(deltaTime) * volatilityAccumulator; uint64(deltaTime) * binCrossed; if ( uint64(deltaTime) * activeId > type(uint64).max - currentCumulativeId - || uint64(deltaTime) * volatilityAccumulated > type(uint64).max - currentCumulativeVolatility + || uint64(deltaTime) * volatilityAccumulator > type(uint64).max - currentCumulativeVolatility || uint64(deltaTime) * binCrossed > type(uint64).max - currentCumulativeBinCrossed ) { vm.expectRevert(); - sample.update(deltaTime, activeId, volatilityAccumulated, binCrossed); + sample.update(deltaTime, activeId, volatilityAccumulator, binCrossed); } else { (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = - sample.update(deltaTime, activeId, volatilityAccumulated, binCrossed); + sample.update(deltaTime, activeId, volatilityAccumulator, binCrossed); assertEq( uint64(cumulativeId), currentCumulativeId + uint64(activeId) * deltaTime, "testFuzz_updateWithSample::1" ); assertEq( uint64(cumulativeVolatility), - currentCumulativeVolatility + uint64(volatilityAccumulated) * deltaTime, + currentCumulativeVolatility + uint64(volatilityAccumulator) * deltaTime, "testFuzz_updateWithSample::2" ); assertEq( diff --git a/test/mocks/FlashBorrower.sol b/test/mocks/FlashBorrower.sol new file mode 100644 index 00000000..407ecdcf --- /dev/null +++ b/test/mocks/FlashBorrower.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import {IERC20} from "openzeppelin/interfaces/IERC20.sol"; + +import {ILBPair} from "src/LBPair.sol"; +import {ILBFlashLoanCallback} from "src/interfaces/ILBFlashLoanCallback.sol"; +import {Constants} from "src/libraries/Constants.sol"; +import {PackedUint128Math} from "src/libraries/math/PackedUint128Math.sol"; +import {TokenHelper} from "src/libraries/TokenHelper.sol"; +import {AddressHelper} from "src/libraries/AddressHelper.sol"; + +contract FlashBorrower is ILBFlashLoanCallback { + using PackedUint128Math for bytes32; + using TokenHelper for IERC20; + using AddressHelper for address; + + enum Action { + NORMAL, + REENTRANT + } + + error FlashBorrower__UntrustedLender(); + error FlashBorrower__UntrustedLoanInitiator(); + + ILBPair private immutable _lender; + + constructor(ILBPair lender_) { + _lender = lender_; + } + + function LBFlashLoanCallback( + address, + IERC20 tokenX, + IERC20 tokenY, + bytes32 amounts, + bytes32 totalFees, + bytes calldata data + ) external override returns (bytes32) { + (uint128 paybackX, uint128 paybackY, bytes32 callback, Action a) = + abi.decode(data, (uint128, uint128, bytes32, Action)); + + if (a == Action.REENTRANT) { + _lender.flashLoan(this, amounts, ""); + } + + if (paybackX == type(uint128).max) { + paybackX = amounts.decodeFirst() + totalFees.decodeFirst(); + } + + if (paybackY == type(uint128).max) { + paybackY = amounts.decodeSecond() + totalFees.decodeSecond(); + } + + if (paybackX > 0) { + tokenX.safeTransfer(msg.sender, paybackX); + } + + if (paybackY > 0) { + tokenY.safeTransfer(msg.sender, paybackY); + } + + return callback; + } +} diff --git a/test/mocks/FlashloanBorrower.sol b/test/mocks/FlashloanBorrower.sol deleted file mode 100644 index 49ada687..00000000 --- a/test/mocks/FlashloanBorrower.sol +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import {IERC20} from "openzeppelin/interfaces/IERC20.sol"; - -import {ILBPair} from "src/LBPair.sol"; -import {ILBFlashLoanCallback} from "src/interfaces/ILBFlashLoanCallback.sol"; -import {Constants} from "src/libraries/Constants.sol"; - -error FlashBorrower__UntrustedLender(); -error FlashBorrower__UntrustedLoanInitiator(); - -contract FlashBorrower is ILBFlashLoanCallback { - enum Action { - NORMAL, - OTHER - } - - event CalldataTransmitted(); - - address private immutable _owner; - - ILBPair private immutable _lender; - - IERC20 private immutable _tokenX; - IERC20 private immutable _tokenY; - - constructor(ILBPair lender_) { - _owner = msg.sender; - _lender = lender_; - - (_tokenX, _tokenY) = (lender_.getTokenX(), lender_.getTokenY()); - } - - function LBFlashLoanCallback( - address, - IERC20 tokenX, - IERC20 tokenY, - bytes32 amounts, - bytes32 totalFees, - bytes calldata data - ) external override returns (bytes32) { - // if (msg.sender != address(_lender)) { - // revert FlashBorrower__UntrustedLender(); - // } - // (Action action, bool isReentrant) = abi.decode(data, (Action, bool)); - // if (isReentrant) { - // _lender.flashLoan(this, token, amount, data); - // } - // if (action == Action.NORMAL) { - // emit CalldataTransmitted(); - // } - - // token.transfer(address(_lender), amount + fee); - - // return Constants.CALLBACK_SUCCESS; - } - - /// @dev Initiate a flash loan - function flashBorrow(uint256 amountXBorrowed, uint256 amountYBorrowed) public { - bytes memory data = abi.encode(Action.NORMAL, false); - - // if (amountXBorrowed > 0) { - // _lender.flashLoan(this, _tokenX, amountXBorrowed, data); - // } - // if (amountYBorrowed > 0) { - // _lender.flashLoan(this, _tokenY, amountYBorrowed, data); - // } - } - - function flashBorrowWithReentrancy(uint256 amountXBorrowed, uint256 amountYBorrowed) public { - bytes memory data = abi.encode(Action.NORMAL, true); - - // if (amountXBorrowed > 0) { - // _lender.flashLoan(this, _tokenX, amountXBorrowed, data); - // } - // if (amountYBorrowed > 0) { - // _lender.flashLoan(this, _tokenY, amountYBorrowed, data); - // } - } -} From a0ee27e2a5f8e1588c2fca3fdd05f76863c6ea65 Mon Sep 17 00:00:00 2001 From: Mathieu <85969303+Mathieu-Be@users.noreply.github.com> Date: Sat, 4 Feb 2023 21:40:46 +0100 Subject: [PATCH 14/47] Router tests (#79) * fix typo for volatility accumulator * add getter for oracle parameter * test implementation * fix edge case on oracle * fix getSwapOut * test lbPair initial state * test adding and removing liquidity via router * add testing for sweep * fix typos * test and fix for flasloan * fix lbfactory test * fix and test binHelper * test and fix oracle helper * test and fix pair parameter helper * test and fix lbpair * add slither files * add test for simple swaps through the router * fix router test * use decodeFirst and decodeSecond * fix typo * add refundTo parameter when adding liquidity through the router --------- Co-authored-by: 0x0Louis --- src/LBRouter.sol | 90 ++++--- src/interfaces/ILBRouter.sol | 19 +- test/LBRouter.Liquidity.t.sol | 475 +++++++++++++++++++++++++++++++++- test/LBRouter.Swap.t.sol | 471 +++++++++++++++++++++++++++++++++ test/helpers/TestHelper.sol | 1 + 5 files changed, 1008 insertions(+), 48 deletions(-) create mode 100644 test/LBRouter.Swap.t.sol diff --git a/src/LBRouter.sol b/src/LBRouter.sol index 052b3fe1..27964e52 100644 --- a/src/LBRouter.sol +++ b/src/LBRouter.sol @@ -29,8 +29,6 @@ import {IWAVAX} from "./interfaces/IWAVAX.sol"; contract LBRouter is ILBRouter { using TokenHelper for IERC20; using TokenHelper for IWAVAX; - // using Uint256x256Math for uint256; - using Encoded for bytes32; using JoeLibrary for uint256; using PackedUint128Math for bytes32; @@ -156,12 +154,17 @@ contract LBRouter is ILBRouter { /// @notice Add liquidity while performing safety checks /// @dev This function is compliant with fee on transfer tokens /// @param liquidityParameters The liquidity parameters - /// @return depositIds Bin ids where the liquidity was actually deposited - /// @return liquidityMinted Amounts of LBToken minted for each bin function addLiquidity(LiquidityParameters calldata liquidityParameters) external override - returns (bytes32[] memory depositIds, uint256[] memory liquidityMinted) + returns ( + uint256 amountXAdded, + uint256 amountYAdded, + uint256 amountXLeft, + uint256 amountYLeft, + uint256[] memory depositIds, + uint256[] memory liquidityMinted + ) { ILBPair lbPair = _getLBPairInformation( liquidityParameters.tokenX, @@ -174,19 +177,25 @@ contract LBRouter is ILBRouter { liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(lbPair), liquidityParameters.amountX); liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(lbPair), liquidityParameters.amountY); - (depositIds, liquidityMinted) = _addLiquidity(liquidityParameters, lbPair); + (amountXAdded, amountYAdded, amountXLeft, amountYLeft, depositIds, liquidityMinted) = + _addLiquidity(liquidityParameters, lbPair); } /// @notice Add liquidity with AVAX while performing safety checks /// @dev This function is compliant with fee on transfer tokens /// @param liquidityParameters The liquidity parameters - /// @return depositIds Bin ids where the liquidity was actually deposited - /// @return liquidityMinted Amounts of LBToken minted for each bin function addLiquidityAVAX(LiquidityParameters calldata liquidityParameters) external payable override - returns (bytes32[] memory depositIds, uint256[] memory liquidityMinted) + returns ( + uint256 amountXAdded, + uint256 amountYAdded, + uint256 amountXLeft, + uint256 amountYLeft, + uint256[] memory depositIds, + uint256[] memory liquidityMinted + ) { ILBPair _LBPair = _getLBPairInformation( liquidityParameters.tokenX, @@ -212,7 +221,8 @@ contract LBRouter is ILBRouter { ); } - (depositIds, liquidityMinted) = _addLiquidity(liquidityParameters, _LBPair); + (amountXAdded, amountYAdded, amountXLeft, amountYLeft, depositIds, liquidityMinted) = + _addLiquidity(liquidityParameters, _LBPair); } /// @notice Remove liquidity while performing safety checks @@ -578,15 +588,20 @@ contract LBRouter is ILBRouter { /// @notice Helper function to add liquidity /// @param liq The liquidity parameter /// @param pair LBPair where liquidity is deposited - /// @return liquidityConfigs Bin ids where the liquidity was actually deposited - /// @return liquidityMinted Amounts of LBToken minted for each bin function _addLiquidity(LiquidityParameters calldata liq, ILBPair pair) private ensure(liq.deadline) - returns (bytes32[] memory liquidityConfigs, uint256[] memory liquidityMinted) + returns ( + uint256 amountXAdded, + uint256 amountYAdded, + uint256 amountXLeft, + uint256 amountYLeft, + uint256[] memory depositIds, + uint256[] memory liquidityMinted + ) { unchecked { - if (liq.deltaIds.length != liq.distributionX.length && liq.deltaIds.length != liq.distributionY.length) { + if (liq.deltaIds.length != liq.distributionX.length || liq.deltaIds.length != liq.distributionY.length) { revert LBRouter__LengthsMismatch(); } @@ -594,29 +609,40 @@ contract LBRouter is ILBRouter { revert LBRouter__IdDesiredOverflows(liq.activeIdDesired, liq.idSlippage); } - uint256 _activeId = pair.getActiveId(); - if (liq.activeIdDesired + liq.idSlippage < _activeId || _activeId + liq.idSlippage < liq.activeIdDesired) { - revert LBRouter__IdSlippageCaught(liq.activeIdDesired, liq.idSlippage, _activeId); - } + bytes32[] memory liquidityConfigs = new bytes32[](liq.deltaIds.length); + depositIds = new uint256[](liq.deltaIds.length); + { + uint256 _activeId = pair.getActiveId(); + if ( + liq.activeIdDesired + liq.idSlippage < _activeId || _activeId + liq.idSlippage < liq.activeIdDesired + ) { + revert LBRouter__IdSlippageCaught(liq.activeIdDesired, liq.idSlippage, _activeId); + } + + for (uint256 i; i < liquidityConfigs.length; ++i) { + int256 _id = int256(_activeId) + liq.deltaIds[i]; - liquidityConfigs = new bytes32[](liq.deltaIds.length); - for (uint256 i; i < liquidityConfigs.length; ++i) { - int256 _id = int256(_activeId) + liq.deltaIds[i]; - if (_id < 0 || uint256(_id) > type(uint24).max) revert LBRouter__IdOverflows(_id); - liquidityConfigs[i] = LiquidityConfigurations.encodeParams( - uint64(liq.distributionX[i]), uint64(liq.distributionY[i]), uint24(uint256(_id)) - ); + if (_id < 0 || uint256(_id) > type(uint24).max) revert LBRouter__IdOverflows(_id); + depositIds[i] = uint256(_id); + liquidityConfigs[i] = LiquidityConfigurations.encodeParams( + uint64(liq.distributionX[i]), uint64(liq.distributionY[i]), uint24(uint256(_id)) + ); + } } bytes32 amountsReceived; - (amountsReceived,, liquidityMinted) = pair.mint(msg.sender, liquidityConfigs, liq.to); + bytes32 amountsLeft; + (amountsReceived, amountsLeft, liquidityMinted) = pair.mint(liq.to, liquidityConfigs, liq.refundTo); - uint256 amountXAdded = amountsReceived.decodeUint128(0); - uint256 amountYAdded = amountsReceived.decodeUint128(128); + amountXAdded = amountsReceived.decodeFirst(); + amountYAdded = amountsReceived.decodeSecond(); if (amountXAdded < liq.amountXMin || amountYAdded < liq.amountYMin) { revert LBRouter__AmountSlippageCaught(liq.amountXMin, amountXAdded, liq.amountYMin, amountYAdded); } + + amountXLeft = amountsLeft.decodeFirst(); + amountYLeft = amountsLeft.decodeSecond(); } } @@ -639,7 +665,6 @@ contract LBRouter is ILBRouter { for (uint256 i = pairs.length; i != 0; i--) { IERC20 token = tokenPath[i - 1]; uint256 binStep = pairBinSteps[i - 1]; - address pair = pairs[i - 1]; if (binStep == 0) { @@ -649,7 +674,7 @@ contract LBRouter is ILBRouter { } uint256 amountOut_ = amountsIn[i]; - amountsIn[i - 1] = uint128(uint256(amountOut_).getAmountIn(reserveIn, reserveOut)); + amountsIn[i - 1] = uint128(amountOut_.getAmountIn(reserveIn, reserveOut)); } else { (amountsIn[i - 1],,) = getSwapIn(ILBPair(pair), uint128(amountsIn[i]), ILBPair(pair).getTokenX() == token); @@ -674,12 +699,11 @@ contract LBRouter is ILBRouter { uint256[] memory amounts, address to ) private returns (uint256 amountX, uint256 amountY) { - ILBToken(address(pair)).batchTransferFrom(msg.sender, address(pair), ids, amounts); (bytes32[] memory amountsBurned) = pair.burn(msg.sender, to, ids, amounts); for (uint256 i; i < amountsBurned.length; ++i) { - amountX += amountsBurned[i].decodeUint128(0); - amountY += amountsBurned[i].decodeUint128(128); + amountX += amountsBurned[i].decodeFirst(); + amountY += amountsBurned[i].decodeSecond(); } if (amountX < amountXMin || amountY < amountYMin) { diff --git a/src/interfaces/ILBRouter.sol b/src/interfaces/ILBRouter.sol index 5d760403..0849c7a0 100644 --- a/src/interfaces/ILBRouter.sol +++ b/src/interfaces/ILBRouter.sol @@ -71,6 +71,7 @@ interface ILBRouter { uint256[] distributionX; uint256[] distributionY; address to; + address refundTo; uint256 deadline; } @@ -112,12 +113,26 @@ interface ILBRouter { function addLiquidity(LiquidityParameters calldata liquidityParameters) external - returns (bytes32[] memory depositIds, uint256[] memory liquidityMinted); + returns ( + uint256 amountXAdded, + uint256 amountYAdded, + uint256 amountXLeft, + uint256 amountYLeft, + uint256[] memory depositIds, + uint256[] memory liquidityMinted + ); function addLiquidityAVAX(LiquidityParameters calldata liquidityParameters) external payable - returns (bytes32[] memory depositIds, uint256[] memory liquidityMinted); + returns ( + uint256 amountXAdded, + uint256 amountYAdded, + uint256 amountXLeft, + uint256 amountYLeft, + uint256[] memory depositIds, + uint256[] memory liquidityMinted + ); function removeLiquidity( IERC20 tokenX, diff --git a/test/LBRouter.Liquidity.t.sol b/test/LBRouter.Liquidity.t.sol index 46ef0bf8..6aea9cc4 100644 --- a/test/LBRouter.Liquidity.t.sol +++ b/test/LBRouter.Liquidity.t.sol @@ -4,17 +4,20 @@ pragma solidity 0.8.10; import "test/helpers/TestHelper.sol"; -/* -* Test scenarios: -* 2. Receive -* 3. Create LBPair -* 4. Add Liquidity -* 5. Add liquidity AVAX -* 6. Remove liquidity -* 7. Remove liquidity AVAX -* 8. Sweep ERC20s -* 9. Sweep LBToken*/ +/** + * Test scenarios: + * 2. Receive + * 3. Create LBPair + * 4. Add Liquidity + * 5. Add liquidity AVAX + * 6. Remove liquidity + * 7. Remove liquidity AVAX + * 8. Sweep ERC20s + * 9. Sweep LBToken + */ contract LiquidityBinRouterTest is TestHelper { + bool blockReceive; + function setUp() public override { super.setUp(); @@ -24,6 +27,12 @@ contract LiquidityBinRouterTest is TestHelper { router.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); router.createLBPair(wavax, usdc, ID_ONE, DEFAULT_BIN_STEP); router.createLBPair(taxToken, usdc, ID_ONE, DEFAULT_BIN_STEP); + + uint256 startingBalance = type(uint112).max; + deal(address(usdc), address(this), startingBalance); + deal(address(usdt), address(this), startingBalance); + deal(address(bnb), address(this), startingBalance); + deal(address(weth), address(this), startingBalance); } function test_ReceiveAVAX() public { @@ -39,6 +48,17 @@ contract LiquidityBinRouterTest is TestHelper { assertTrue(success); } + function test_CreatePair() public { + router.createLBPair(weth, usdc, ID_ONE, DEFAULT_BIN_STEP); + + factory.setFactoryLockedState(true); + + vm.expectRevert( + abi.encodeWithSelector(ILBFactory.LBFactory__FunctionIsLockedForUsers.selector, address(router)) + ); + router.createLBPair(bnb, usdc, ID_ONE, DEFAULT_BIN_STEP); + } + function testFuzz_AddLiquidityNoSlippage(uint256 amountYIn, uint24 binNumber, uint24 gap) public { amountYIn = bound(amountYIn, 5_000, type(uint112).max); binNumber = uint24(bound(binNumber, 0, 400)); @@ -47,11 +67,440 @@ contract LiquidityBinRouterTest is TestHelper { ILBRouter.LiquidityParameters memory liquidityParameters = getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); - - deal(address(usdt), DEV, liquidityParameters.amountX); - deal(address(usdc), DEV, liquidityParameters.amountY); + liquidityParameters.refundTo = BOB; // Add liquidity + ( + uint256 amountXAdded, + uint256 amountYAdded, + uint256 amountXLeft, + uint256 amountYLeft, + uint256[] memory depositIds, + uint256[] memory liquidityMinted + ) = router.addLiquidity(liquidityParameters); + + // Check amounts + assertEq(amountXAdded, liquidityParameters.amountX, "amountXAdded"); + assertEq(amountYAdded, liquidityParameters.amountY, "amountYAdded"); + assertLt(amountXLeft, amountXAdded, "amountXLeft"); + assertLt(amountYLeft, amountYAdded, "amountYLeft"); + + assertEq(usdt.balanceOf(BOB), amountXLeft, "usdt balance"); + assertEq(usdc.balanceOf(BOB), amountYLeft, "usdc balance"); + + // Check liquidity minted + assertEq(liquidityMinted.length, binNumber); + assertEq(depositIds.length, binNumber); + } + + function test_reverts_AddLiquidity() public { + uint256 amountYIn = 1e18; + uint24 binNumber = 7; + uint24 gap = 2; + + // Revert if tokens are in the wrong order + ILBRouter.LiquidityParameters memory liquidityParameters = + getLiquidityParameters(usdc, usdt, amountYIn, ID_ONE, binNumber, gap); + + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__WrongTokenOrder.selector)); + router.addLiquidity(liquidityParameters); + + // Revert if the liquidity arrays are not the same length + liquidityParameters = getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); + liquidityParameters.deltaIds = new int256[](binNumber - 1); + + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); + router.addLiquidity(liquidityParameters); + + liquidityParameters = getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); + liquidityParameters.distributionY = new uint256[](binNumber - 1); + + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); + router.addLiquidity(liquidityParameters); + + // Active Id required can't be greater than type(uint24).max + liquidityParameters = getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); + liquidityParameters.activeIdDesired = uint256(type(uint24).max) + 1; + + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__IdDesiredOverflows.selector, + liquidityParameters.activeIdDesired, + liquidityParameters.idSlippage + ) + ); + router.addLiquidity(liquidityParameters); + + liquidityParameters = getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); + liquidityParameters.idSlippage = uint256(type(uint24).max) + 1; + + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__IdDesiredOverflows.selector, + liquidityParameters.activeIdDesired, + liquidityParameters.idSlippage + ) + ); + router.addLiquidity(liquidityParameters); + + // Absolute IDs can't overflow + liquidityParameters = getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); + liquidityParameters.deltaIds[0] = -int256(uint256(ID_ONE)) - 1; + + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__IdOverflows.selector, type(uint256).max)); + router.addLiquidity(liquidityParameters); + + liquidityParameters = getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); + liquidityParameters.deltaIds[0] = int256(uint256(ID_ONE)); + + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__IdOverflows.selector, uint256(type(uint24).max) + 1)); router.addLiquidity(liquidityParameters); + + // Revert is slippage is too high + liquidityParameters = getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); + liquidityParameters.amountXMin = liquidityParameters.amountX + 1; + + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__AmountSlippageCaught.selector, + liquidityParameters.amountXMin, + liquidityParameters.amountX, + liquidityParameters.amountYMin, + liquidityParameters.amountY + ) + ); + router.addLiquidity(liquidityParameters); + + liquidityParameters = getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); + liquidityParameters.amountYMin = liquidityParameters.amountY + 1; + + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__AmountSlippageCaught.selector, + liquidityParameters.amountXMin, + liquidityParameters.amountX, + liquidityParameters.amountYMin, + liquidityParameters.amountY + ) + ); + router.addLiquidity(liquidityParameters); + + // Revert is the deadline passed + liquidityParameters = getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); + + skip(2_000); + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__DeadlineExceeded.selector, liquidityParameters.deadline, block.timestamp + ) + ); + router.addLiquidity(liquidityParameters); + } + + function test_AddLiquidityAVAX() public { + uint256 amountYIn = 1e18; + uint24 binNumber = 7; + uint24 gap = 2; + + ILBRouter.LiquidityParameters memory liquidityParameters = + getLiquidityParameters(wavax, usdc, amountYIn, ID_ONE, binNumber, gap); + + // Add liquidity + ( + uint256 amountXAdded, + uint256 amountYAdded, + uint256 amountXLeft, + uint256 amountYLeft, + uint256[] memory depositIds, + uint256[] memory liquidityMinted + ) = router.addLiquidityAVAX{value: liquidityParameters.amountX}(liquidityParameters); + + // Check amounts + assertEq(amountXAdded, liquidityParameters.amountX, "amountXAdded"); + assertEq(amountYAdded, liquidityParameters.amountY, "amountYAdded"); + assertLt(amountXLeft, amountXAdded, "amountXLeft"); + assertLt(amountYLeft, amountYAdded, "amountYLeft"); + + // Check liquidity minted + assertEq(liquidityMinted.length, binNumber); + assertEq(depositIds.length, binNumber); + + // Test with AVAX as token Y + router.createLBPair(bnb, wavax, ID_ONE, DEFAULT_BIN_STEP); + + liquidityParameters = getLiquidityParameters(bnb, wavax, amountYIn, ID_ONE, binNumber, gap); + + router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); + } + + function test_reverts_AddLiquidityAVAX() public { + uint256 amountYIn = 1e18; + uint24 binNumber = 7; + uint24 gap = 2; + + // Revert if tokens are in the wrong order + ILBRouter.LiquidityParameters memory liquidityParameters = + getLiquidityParameters(usdc, wavax, amountYIn, ID_ONE, binNumber, gap); + + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__WrongTokenOrder.selector)); + router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); + + // Revert if for non WAVAX pairs + liquidityParameters = getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); + + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__WrongAvaxLiquidityParameters.selector, + address(liquidityParameters.tokenX), + address(liquidityParameters.tokenY), + liquidityParameters.amountX, + liquidityParameters.amountY, + liquidityParameters.amountY + ) + ); + router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); + + // Revert if the amount of AVAX isn't correct + liquidityParameters = getLiquidityParameters(wavax, usdc, amountYIn, ID_ONE, binNumber, gap); + + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__WrongAvaxLiquidityParameters.selector, + address(liquidityParameters.tokenX), + address(liquidityParameters.tokenY), + liquidityParameters.amountX, + liquidityParameters.amountY, + liquidityParameters.amountY + ) + ); + // liquidityParameters.amountX should be sent as message value + router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); + } + + function test_RemoveLiquidity() public { + uint256 amountYIn = 1e18; + uint24 binNumber = 7; + uint24 gap = 2; + + ILBRouter.LiquidityParameters memory liquidityParameters = + getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); + + // Add liquidity + (uint256 amountXAdded, uint256 amountYAdded,,, uint256[] memory depositIds, uint256[] memory liquidityMinted) = + router.addLiquidity(liquidityParameters); + + ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 1).LBPair; + + pair.setApprovalForAll(address(router), true); + + (uint256 amountXOut, uint256 amountYOut) = router.removeLiquidity( + usdt, usdc, DEFAULT_BIN_STEP, 1, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp + ); + + assertApproxEqAbs(amountXOut, amountXAdded, 10, "amountXOut"); + assertApproxEqAbs(amountYOut, amountYAdded, 10, "amountYOut"); + + assertLe(amountXOut, amountXAdded, "amountXOut"); + assertLe(amountYOut, amountYAdded, "amountYOut"); + + for (uint256 i = 0; i < depositIds.length; i++) { + assertEq(pair.balanceOf(address(this), depositIds[i]), 0, "depositId"); + } + + // Try with the token inversed + (amountXAdded, amountYAdded,,, depositIds, liquidityMinted) = router.addLiquidity(liquidityParameters); + + (amountYOut, amountXOut) = router.removeLiquidity( + usdc, usdt, DEFAULT_BIN_STEP, 1, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp + ); + + assertApproxEqAbs(amountXOut, amountXAdded, 10, "amountXOut"); + assertApproxEqAbs(amountYOut, amountYAdded, 10, "amountYOut"); + + // Try removing half of the liquidity + (amountXAdded, amountYAdded,,, depositIds, liquidityMinted) = router.addLiquidity(liquidityParameters); + + for (uint256 i = 0; i < depositIds.length; i++) { + liquidityMinted[i] = liquidityMinted[i] / 2; + } + + (amountXOut, amountYOut) = router.removeLiquidity( + usdt, usdc, DEFAULT_BIN_STEP, 1, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp + ); + + assertApproxEqAbs(amountXOut, amountXAdded / 2, 10, "amountXOut"); + assertApproxEqAbs(amountYOut, amountYAdded / 2, 10, "amountYOut"); + } + + function test_reverts_RemoveLiquidity() public { + uint256 amountYIn = 1e18; + uint24 binNumber = 7; + uint24 gap = 2; + + ILBRouter.LiquidityParameters memory liquidityParameters = + getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); + + // Add liquidity + (uint256 amountXAdded, uint256 amountYAdded,,, uint256[] memory depositIds, uint256[] memory liquidityMinted) = + router.addLiquidity(liquidityParameters); + + ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 1).LBPair; + pair.setApprovalForAll(address(router), true); + + // Revert if the deadline is passed + vm.expectRevert( + abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) + ); + router.removeLiquidity( + usdt, usdc, DEFAULT_BIN_STEP, 1, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp - 1 + ); + + // Revert if the slippage is caught + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__AmountSlippageCaught.selector, amountXAdded + 1, amountXAdded - 2, 0, amountYAdded + ) + ); + router.removeLiquidity( + usdt, + usdc, + DEFAULT_BIN_STEP, + 1, + amountXAdded + 1, + 0, + depositIds, + liquidityMinted, + address(this), + block.timestamp + ); + + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__AmountSlippageCaught.selector, 0, amountXAdded - 2, amountYAdded + 1, amountYAdded + ) + ); + router.removeLiquidity( + usdt, + usdc, + DEFAULT_BIN_STEP, + 1, + 0, + amountYAdded + 1, + depositIds, + liquidityMinted, + address(this), + block.timestamp + ); + } + + function test_RemoveLiquidityAVAX() public { + uint256 amountYIn = 1e18; + uint24 binNumber = 7; + uint24 gap = 2; + + ILBRouter.LiquidityParameters memory liquidityParameters = + getLiquidityParameters(wavax, usdc, amountYIn, ID_ONE, binNumber, gap); + + // Add liquidity + (uint256 amountXAdded, uint256 amountYAdded,,, uint256[] memory depositIds, uint256[] memory liquidityMinted) = + router.addLiquidityAVAX{value: liquidityParameters.amountX}(liquidityParameters); + + ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP, 1).LBPair; + pair.setApprovalForAll(address(router), true); + + uint256 balanceAVAXBefore = address(this).balance; + uint256 balanceUSDCBefore = usdc.balanceOf(address(this)); + + (uint256 amountToken, uint256 amountAVAX) = router.removeLiquidityAVAX( + usdc, DEFAULT_BIN_STEP, 1, 0, 0, depositIds, liquidityMinted, payable(address(this)), block.timestamp + ); + + assertApproxEqAbs(amountAVAX, amountXAdded, 10, "amountXOut"); + assertApproxEqAbs(amountToken, amountYAdded, 10, "amountYOut"); + + assertEq(address(this).balance, balanceAVAXBefore + amountAVAX, "balanceAVAXAfter"); + assertEq(usdc.balanceOf(address(this)), balanceUSDCBefore + amountToken, "balanceUSDCAfter"); + } + + function test_reverts_RemoveLiquidityAVAX() public { + uint256 amountYIn = 1e18; + uint24 binNumber = 7; + uint24 gap = 2; + + ILBRouter.LiquidityParameters memory liquidityParameters = + getLiquidityParameters(wavax, usdc, amountYIn, ID_ONE, binNumber, gap); + + // Add liquidity + (uint256 amountXAdded,,,, uint256[] memory depositIds, uint256[] memory liquidityMinted) = + router.addLiquidityAVAX{value: liquidityParameters.amountX}(liquidityParameters); + + ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP, 1).LBPair; + pair.setApprovalForAll(address(router), true); + + // Revert if the deadline is passed + vm.expectRevert( + abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) + ); + router.removeLiquidityAVAX( + usdc, DEFAULT_BIN_STEP, 1, 0, 0, depositIds, liquidityMinted, payable(address(this)), block.timestamp - 1 + ); + + // Revert if the contract does not have a receive function + blockReceive = true; + vm.expectRevert( + abi.encodeWithSelector(ILBRouter.LBRouter__FailedToSendAVAX.selector, address(this), amountXAdded - 2) + ); + router.removeLiquidityAVAX( + usdc, DEFAULT_BIN_STEP, 1, 0, 0, depositIds, liquidityMinted, payable(address(this)), block.timestamp + ); + } + + function test_SweepERC20() public { + uint256 amount = 1e18; + usdc.mint(address(router), amount); + + uint256 balanceBefore = usdc.balanceOf(address(this)); + router.sweep(usdc, address(this), amount); + assertEq(usdc.balanceOf(address(this)), balanceBefore + amount, "balanceAfter"); + + // Can't sweep if non owner + usdc.mint(address(router), amount); + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__NotFactoryOwner.selector)); + vm.prank(ALICE); + router.sweep(usdc, address(this), amount); + } + + function test_SweepLBTokens() public { + ILBRouter.LiquidityParameters memory liquidityParameters = + getLiquidityParameters(usdt, usdc, 1e18, ID_ONE, 1, 0); + + liquidityParameters.to = address(router); + (,,,, uint256[] memory depositIds, uint256[] memory liquidityMinted) = router.addLiquidity(liquidityParameters); + + ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 1).LBPair; + + uint256[] memory balancesBefore = new uint256[](depositIds.length); + for (uint256 i = 0; i < depositIds.length; i++) { + balancesBefore[i] = pair.balanceOf(DEV, depositIds[i]); + } + + router.sweepLBToken(pair, DEV, depositIds, liquidityMinted); + + for (uint256 i = 0; i < depositIds.length; i++) { + assertEq(pair.balanceOf(DEV, depositIds[i]), balancesBefore[i] + liquidityMinted[i], "balanceAfter"); + } + + (,,,, depositIds, liquidityMinted) = router.addLiquidity(liquidityParameters); + + // Can't sweep if non owner + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__NotFactoryOwner.selector)); + vm.prank(ALICE); + router.sweepLBToken(pair, DEV, depositIds, liquidityMinted); + } + + receive() external payable { + if (blockReceive) { + revert("No receive function on the contract"); + } } } diff --git a/test/LBRouter.Swap.t.sol b/test/LBRouter.Swap.t.sol new file mode 100644 index 00000000..7bb3dd91 --- /dev/null +++ b/test/LBRouter.Swap.t.sol @@ -0,0 +1,471 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.10; + +import "test/helpers/TestHelper.sol"; + +/** + * This file only test single hop swaps using V2.1 pairs + * Test scenarios: + * 1. swapExactTokensForTokens + * 1. swapExactTokensForAVAX + * 2. swapExactAVAXForTokens + * 3. swapTokensForExactTokens + * 4. swapTokensForExactAVAX + * 5. swapAVAXForExactTokens + * 6. swapExactTokensForTokensSupportingFeeOnTransferTokens + * 7. swapExactTokensForAVAXSupportingFeeOnTransferTokens + * 8. swapExactAVAXForTokensSupportingFeeOnTransferTokens + */ +contract LiquidityBinRouterSwapTest is TestHelper { + function setUp() public override { + super.setUp(); + + factory.setFactoryLockedState(false); + + // Create necessary pairs + router.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + router.createLBPair(wavax, usdc, ID_ONE, DEFAULT_BIN_STEP); + router.createLBPair(taxToken, wavax, ID_ONE, DEFAULT_BIN_STEP); + + uint256 startingBalance = type(uint112).max; + deal(address(usdc), address(this), startingBalance); + deal(address(usdt), address(this), startingBalance); + deal(address(taxToken), address(this), startingBalance); + + ILBRouter.LiquidityParameters memory liquidityParameters = + getLiquidityParameters(usdt, usdc, 100e18, ID_ONE, 15, 0); + router.addLiquidity(liquidityParameters); + + liquidityParameters = getLiquidityParameters(wavax, usdc, 100e18, ID_ONE, 15, 0); + router.addLiquidityAVAX{value: liquidityParameters.amountX}(liquidityParameters); + + liquidityParameters = getLiquidityParameters(taxToken, wavax, 200e18, ID_ONE, 15, 0); + router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); + } + + function test_SwapExactTokensForTokens() public { + uint128 amountIn = 20e18; + + ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 1).LBPair; + (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, true); + + ILBRouter.Path memory path = _buildPath(usdt, usdc); + + uint256 balanceBefore = usdc.balanceOf(address(this)); + + (uint256 amountOut) = + router.swapExactTokensForTokens(amountIn, amountOutExpected, path, address(this), block.timestamp + 1); + + assertEq(amountOut, amountOutExpected, "amountOut"); + assertEq(usdc.balanceOf(address(this)), balanceBefore + amountOut, "balance"); + + // Reverts if amountOut is less than amountOutMin + (, amountOutExpected,) = router.getSwapOut(pair, amountIn, true); + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__InsufficientAmountOut.selector, amountOutExpected + 1, amountOutExpected + ) + ); + router.swapExactTokensForTokens(amountIn, amountOutExpected + 1, path, address(this), block.timestamp + 1); + + // Revert is dealine passed + vm.expectRevert( + abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) + ); + router.swapExactTokensForTokens(amountIn, amountOutExpected, path, address(this), block.timestamp - 1); + + // Revert if the path arrays are not valid + path.tokenPath = new IERC20[](0); + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); + router.swapExactTokensForTokens(amountIn, amountOutExpected, path, address(this), block.timestamp + 1); + } + + function test_SwapExactTokensForAVAX() public { + uint128 amountIn = 20e18; + + ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP, 1).LBPair; + (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, false); + + ILBRouter.Path memory path = _buildPath(usdc, wavax); + + uint256 balanceBefore = address(this).balance; + + (uint256 amountOut) = router.swapExactTokensForAVAX( + amountIn, amountOutExpected, path, payable(address(this)), block.timestamp + 1 + ); + + assertEq(amountOut, amountOutExpected, "amountOut"); + assertEq(address(this).balance, balanceBefore + amountOut, "balance"); + + // Reverts if amountOut is less than amountOutMin + (, amountOutExpected,) = router.getSwapOut(pair, amountIn, false); + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__InsufficientAmountOut.selector, amountOutExpected + 1, amountOutExpected + ) + ); + router.swapExactTokensForAVAX( + amountIn, amountOutExpected + 1, path, payable(address(this)), block.timestamp + 1 + ); + + // Revert if token out isn't WAVAX + path.tokenPath[1] = usdt; + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); + router.swapExactTokensForAVAX( + amountIn, amountOutExpected + 1, path, payable(address(this)), block.timestamp + 1 + ); + + // Revert is dealine passed + vm.expectRevert( + abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) + ); + router.swapExactTokensForAVAX(amountIn, amountOutExpected, path, payable(address(this)), block.timestamp - 1); + + // Revert if the path arrays are not valid + path.tokenPath = new IERC20[](0); + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); + router.swapExactTokensForAVAX(amountIn, amountOutExpected, path, payable(address(this)), block.timestamp + 1); + } + + function test_SwapExactAVAXForTokens() public { + uint128 amountIn = 20e18; + + ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP, 1).LBPair; + (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, true); + + ILBRouter.Path memory path = _buildPath(wavax, usdc); + + uint256 balanceBefore = usdc.balanceOf(address(this)); + + (uint256 amountOut) = + router.swapExactAVAXForTokens{value: amountIn}(amountOutExpected, path, address(this), block.timestamp + 1); + + assertEq(amountOut, amountOutExpected, "amountOut"); + assertEq(usdc.balanceOf(address(this)), balanceBefore + amountOut, "balance"); + + (, amountOutExpected,) = router.getSwapOut(pair, amountIn, true); + + // Reverts if amountOut is less than amountOutMin + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__InsufficientAmountOut.selector, amountOutExpected + 1, amountOutExpected + ) + ); + router.swapExactAVAXForTokens{value: amountIn}(amountOutExpected + 1, path, address(this), block.timestamp + 1); + + // Revert if token in isn't WAVAX + path.tokenPath[0] = usdt; + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); + router.swapExactAVAXForTokens{value: amountIn}(amountOutExpected, path, address(this), block.timestamp + 1); + + // Revert is dealine passed + vm.expectRevert( + abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) + ); + router.swapExactAVAXForTokens{value: amountIn}(amountOutExpected, path, address(this), block.timestamp - 1); + + // Revert if the path arrays are not valid + path.tokenPath = new IERC20[](0); + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); + router.swapExactAVAXForTokens{value: amountIn}(amountOutExpected, path, address(this), block.timestamp + 1); + } + + function test_SwapTokensForExactTokens() public { + uint128 amountOut = 20e18; + + ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 1).LBPair; + (uint128 amountInExpected,,) = router.getSwapIn(pair, amountOut, true); + + ILBRouter.Path memory path = _buildPath(usdt, usdc); + + uint256 balanceBefore = usdt.balanceOf(address(this)); + + (uint256[] memory amountsIn) = + router.swapTokensForExactTokens(amountOut, amountInExpected, path, address(this), block.timestamp + 1); + + assertEq(amountsIn[0], amountInExpected, "amountOut"); + assertEq(usdt.balanceOf(address(this)), balanceBefore - amountsIn[0], "balance"); + + (amountInExpected,,) = router.getSwapIn(pair, amountOut, true); + + // Revert if amountIn is too high + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__MaxAmountInExceeded.selector, amountInExpected - 1, amountInExpected + ) + ); + router.swapTokensForExactTokens(amountOut, amountInExpected - 1, path, address(this), block.timestamp + 1); + + // TODO - try to hit LBRouter__InsufficientAmountOut + + // Revert is dealine passed + vm.expectRevert( + abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) + ); + router.swapTokensForExactTokens(amountOut, amountInExpected, path, address(this), block.timestamp - 1); + + // Revert if the path arrays are not valid + path.tokenPath = new IERC20[](0); + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); + router.swapTokensForExactTokens(amountOut, amountInExpected, path, address(this), block.timestamp + 1); + } + + function test_SwapTokensForExactAVAX() public { + uint128 amountOut = 20e18; + + ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP, 1).LBPair; + (uint128 amountInExpected,,) = router.getSwapIn(pair, amountOut, false); + + ILBRouter.Path memory path = _buildPath(usdc, wavax); + + uint256 balanceBefore = usdc.balanceOf(address(this)); + + (uint256[] memory amountsIn) = router.swapTokensForExactAVAX( + amountOut, amountInExpected, path, payable(address(this)), block.timestamp + 1 + ); + + assertEq(amountsIn[0], amountInExpected, "amountOut"); + assertEq(usdc.balanceOf(address(this)), balanceBefore - amountsIn[0], "balance"); + + (amountInExpected,,) = router.getSwapIn(pair, amountOut, false); + + // Revert if amountIn is too high + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__MaxAmountInExceeded.selector, amountInExpected - 1, amountInExpected + ) + ); + router.swapTokensForExactTokens(amountOut, amountInExpected - 1, path, address(this), block.timestamp + 1); + + // TODO - try to hit LBRouter__InsufficientAmountOut + + // Revert if token out isn't WAVAX + path.tokenPath[1] = usdt; + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); + router.swapTokensForExactAVAX(amountOut, amountInExpected, path, payable(address(this)), block.timestamp + 1); + + // Revert is dealine passed + vm.expectRevert( + abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) + ); + router.swapTokensForExactTokens(amountOut, amountInExpected, path, address(this), block.timestamp - 1); + + // Revert if the path arrays are not valid + path.tokenPath = new IERC20[](0); + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); + router.swapTokensForExactTokens(amountOut, amountInExpected, path, address(this), block.timestamp + 1); + } + + function test_SwapAVAXForExactTokens() public { + uint128 amountOut = 20e18; + + ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP, 1).LBPair; + (uint128 amountInExpected,,) = router.getSwapIn(pair, amountOut, true); + + ILBRouter.Path memory path = _buildPath(wavax, usdc); + + uint256 balanceBefore = address(this).balance; + + // Sending too much AVAX to test the refund + (uint256[] memory amountsIn) = router.swapAVAXForExactTokens{value: amountInExpected + 100}( + amountOut, path, address(this), block.timestamp + 1 + ); + + assertEq(amountsIn[0], amountInExpected, "amountOut"); + assertEq(address(this).balance, balanceBefore - amountsIn[0], "balance"); + + (amountInExpected,,) = router.getSwapIn(pair, amountOut, true); + + // Revert if not enough AVAX has been sent + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__MaxAmountInExceeded.selector, amountInExpected / 2, amountInExpected + ) + ); + router.swapAVAXForExactTokens{value: amountInExpected / 2}(amountOut, path, address(this), block.timestamp + 1); + + // TODO - try to hit LBRouter__InsufficientAmountOut + + // Revert if token in isn't WAVAX + path.tokenPath[0] = usdt; + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); + router.swapAVAXForExactTokens{value: amountInExpected}(amountOut, path, address(this), block.timestamp + 1); + + // Revert is dealine passed + vm.expectRevert( + abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) + ); + router.swapAVAXForExactTokens{value: amountInExpected}(amountOut, path, address(this), block.timestamp - 1); + + // Revert if the path arrays are not valid + path.tokenPath = new IERC20[](0); + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); + router.swapAVAXForExactTokens{value: amountInExpected}(amountOut, path, address(this), block.timestamp + 1); + } + + function test_swapExactTokensForAVAXSupportingFeeOnTransferTokens() public { + uint128 amountIn = 20e18; + + ILBPair pair = factory.getLBPairInformation(taxToken, wavax, DEFAULT_BIN_STEP, 1).LBPair; + (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, true); + + ILBRouter.Path memory path = _buildPath(taxToken, wavax); + + // Reverts if amountOut is less than amountOutMin + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__InsufficientAmountOut.selector, amountOutExpected, amountOutExpected / 2 + ) + ); + router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + amountIn, amountOutExpected, path, payable(address(this)), block.timestamp + 1 + ); + + uint256 balanceBefore = address(this).balance; + + // Token tax is 50% + (uint256 amountOut) = router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + amountIn, amountOutExpected / 2, path, payable(address(this)), block.timestamp + 1 + ); + + assertEq(amountOut, amountOutExpected / 2, "amountOut"); + assertEq(address(this).balance, balanceBefore + amountOut, "balance"); + + // Revert if token out isn't WAVAX + path.tokenPath[1] = usdt; + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); + router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + amountIn, amountOutExpected / 2, path, payable(address(this)), block.timestamp + 1 + ); + + // Revert is dealine passed + vm.expectRevert( + abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) + ); + router.swapExactTokensForAVAX( + amountIn, amountOutExpected / 2, path, payable(address(this)), block.timestamp - 1 + ); + + // Revert if the path arrays are not valid + path.tokenPath = new IERC20[](0); + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); + router.swapExactTokensForAVAX( + amountIn, amountOutExpected / 2, path, payable(address(this)), block.timestamp + 1 + ); + } + + function test_SwapExactTokensForAVAXSupportingFeeOnTransferTokens() public { + uint128 amountIn = 20e18; + + ILBPair pair = factory.getLBPairInformation(taxToken, wavax, DEFAULT_BIN_STEP, 1).LBPair; + (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, true); + + ILBRouter.Path memory path = _buildPath(taxToken, wavax); + + // Reverts if amountOut is less than amountOutMin + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__InsufficientAmountOut.selector, amountOutExpected, amountOutExpected / 2 + ) + ); + router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + amountIn, amountOutExpected, path, payable(address(this)), block.timestamp + 1 + ); + + uint256 balanceBefore = address(this).balance; + + (uint256 amountOut) = router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + amountIn, amountOutExpected / 2, path, payable(address(this)), block.timestamp + 1 + ); + + assertEq(amountOut, amountOutExpected / 2, "amountOut"); + assertEq(address(this).balance, balanceBefore + amountOut, "balance"); + + // Revert if token out isn't WAVAX + path.tokenPath[1] = usdt; + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); + router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + amountIn, amountOutExpected + 1, path, payable(address(this)), block.timestamp + 1 + ); + + // Revert is dealine passed + vm.expectRevert( + abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) + ); + router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + amountIn, amountOutExpected, path, payable(address(this)), block.timestamp - 1 + ); + + // Revert if the path arrays are not valid + path.tokenPath = new IERC20[](0); + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); + router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + amountIn, amountOutExpected, path, payable(address(this)), block.timestamp + 1 + ); + } + + function test_SwapExactAVAXForTokensSupportingFeeOnTransferTokens() public { + uint128 amountIn = 20e18; + + ILBPair pair = factory.getLBPairInformation(taxToken, wavax, DEFAULT_BIN_STEP, 1).LBPair; + (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, false); + + ILBRouter.Path memory path = _buildPath(wavax, taxToken); + + // Reverts if amountOut is less than amountOutMin + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__InsufficientAmountOut.selector, amountOutExpected, amountOutExpected / 2 + 1 + ) + ); + router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( + amountOutExpected, path, address(this), block.timestamp + 1 + ); + + uint256 balanceBefore = taxToken.balanceOf(address(this)); + + (uint256 amountOut) = router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( + amountOutExpected / 2, path, address(this), block.timestamp + 1 + ); + + assertEq(amountOut, amountOutExpected / 2 + 1, "amountOut"); + assertEq(taxToken.balanceOf(address(this)), balanceBefore + amountOut, "balance"); + + // Revert if token in isn't WAVAX + path.tokenPath[0] = usdt; + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); + router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( + amountOutExpected, path, address(this), block.timestamp + 1 + ); + + // Revert is dealine passed + vm.expectRevert( + abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) + ); + router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( + amountOutExpected, path, address(this), block.timestamp - 1 + ); + + // Revert if the path arrays are not valid + path.tokenPath = new IERC20[](0); + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); + router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( + amountOutExpected, path, address(this), block.timestamp + 1 + ); + } + + function _buildPath(IERC20 tokenIn, IERC20 tokenOut) private pure returns (ILBRouter.Path memory path) { + path.pairBinSteps = new uint256[](1); + path.pairBinSteps[0] = DEFAULT_BIN_STEP; + + path.revisions = new uint256[](1); + path.revisions[0] = 1; + + path.tokenPath = new IERC20[](2); + path.tokenPath[0] = tokenIn; + path.tokenPath[1] = tokenOut; + } + + receive() external payable {} +} diff --git a/test/helpers/TestHelper.sol b/test/helpers/TestHelper.sol index 4d7a0624..4091a961 100644 --- a/test/helpers/TestHelper.sol +++ b/test/helpers/TestHelper.sol @@ -250,6 +250,7 @@ abstract contract TestHelper is Test { distributionX: distributionX, distributionY: distributionY, to: DEV, + refundTo: DEV, deadline: block.timestamp + 1000 }); } From 88e4b06fa76b34c11838144923e867c5dcb08b6f Mon Sep 17 00:00:00 2001 From: Louis <107303182+0x0Louis@users.noreply.github.com> Date: Sat, 4 Feb 2023 21:51:07 +0100 Subject: [PATCH 15/47] New shares and last add (#80) * new shares with more precision * typo * fix tests * add max fee check * missing test --- src/LBPair.sol | 17 +++- src/interfaces/ILBPair.sol | 1 + src/libraries/BinHelper.sol | 55 +++++++++---- src/libraries/PairParameterHelper.sol | 14 +++- test/LBPairFees.t.sol | 23 ++++++ test/LBPairOracle.t.sol | 10 +++ test/libraries/BinHelper.t.sol | 100 ++++++++++++++++------- test/libraries/PairParameterHelper.t.sol | 20 +++++ 8 files changed, 189 insertions(+), 51 deletions(-) diff --git a/src/LBPair.sol b/src/LBPair.sol index 9d522fb0..1c8f1c20 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -53,6 +53,8 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { _; } + uint256 private constant _MAX_TOTAL_FEE = 0.1e18; // 10% + ILBFactory private immutable _factory; bytes32 private _parameters; @@ -891,7 +893,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { revert LBPair__InvalidStaticFeeParameters(); } - _parameters = parameters.setStaticFeeParameters( + parameters = parameters.setStaticFeeParameters( baseFactor, filterPeriod, decayPeriod, @@ -901,6 +903,17 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { maxVolatilityAccumulator ); + { + uint8 binStep = _binStep(); + bytes32 maxParameters = parameters.setVolatilityAccumulator(maxVolatilityAccumulator); + uint256 totalFee = maxParameters.getBaseFee(binStep) + maxParameters.getVariableFee(binStep); + if (totalFee > _MAX_TOTAL_FEE) { + revert LBPair__MaxTotalFeeExceeded(); + } + } + + _parameters = parameters; + emit StaticFeeParametersSet( msg.sender, baseFactor, @@ -933,7 +946,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { uint256 price = id.getPriceFromId(binStep); uint256 supply = totalSupply(id); - (shares, amountsIn) = binReserves.getShareAndEffectiveAmountsIn(maxAmountsInToBin, price, supply); + (shares, amountsIn) = binReserves.getSharesAndEffectiveAmountsIn(maxAmountsInToBin, price, supply); amountsInToBin = amountsIn; if (id == activeId) { diff --git a/src/interfaces/ILBPair.sol b/src/interfaces/ILBPair.sol index b176a80c..25d9993d 100644 --- a/src/interfaces/ILBPair.sol +++ b/src/interfaces/ILBPair.sol @@ -26,6 +26,7 @@ interface ILBPair is ILBToken { error LBPair__ZeroAmount(uint24 id); error LBPair__ZeroAmountsOut(uint24 id); error LBPair__ZeroShares(uint24 id); + error LBPair__MaxTotalFeeExceeded(); event DepositedToBins(address indexed sender, address indexed to, uint256[] ids, bytes32[] amounts); diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index f74d097b..87608a4c 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -28,7 +28,8 @@ library BinHelper { using FeeHelper for uint128; using TokenHelper for IERC20; - error BinMath__CompositionFactorFlawed(uint24 id); + error BinHelper__CompositionFactorFlawed(uint24 id); + error BinHelper__LiquidityOverflow(); /** * @dev Returns the amount of tokens that will be received when burning the given amount of liquidity @@ -69,14 +70,13 @@ library BinHelper { * This is the amount of tokens that the user will actually add to the liquidity book, * and will always be less than or equal to the amountsIn. */ - function getShareAndEffectiveAmountsIn(bytes32 binReserves, bytes32 amountsIn, uint256 price, uint256 totalSupply) + function getSharesAndEffectiveAmountsIn(bytes32 binReserves, bytes32 amountsIn, uint256 price, uint256 totalSupply) internal pure returns (uint256 shares, bytes32 effectiveAmountsIn) { uint256 userLiquidity = getLiquidity(amountsIn, price); - if (totalSupply == 0) return (userLiquidity, amountsIn); - if (userLiquidity == 0) return (0, 0); + if (totalSupply == 0 || userLiquidity == 0) return (userLiquidity, amountsIn); uint256 binLiquidity = getLiquidity(binReserves, price); if (binLiquidity == 0) return (userLiquidity, amountsIn); @@ -84,20 +84,31 @@ library BinHelper { shares = userLiquidity.mulDivRoundDown(totalSupply, binLiquidity); uint256 effectiveLiquidity = shares.mulDivRoundUp(binLiquidity, totalSupply); - if (userLiquidity == effectiveLiquidity) return (shares, amountsIn); + if (userLiquidity > effectiveLiquidity) { + uint256 deltaLiquidity = userLiquidity - effectiveLiquidity; - uint256 deltaLiquidity = userLiquidity - effectiveLiquidity; + (uint256 x, uint256 y) = amountsIn.decode(); - (uint128 amountX, uint128 amountY) = amountsIn.decode(); + // The other way might be more efficient, but as y is the quote asset, it is more valuable + if (deltaLiquidity >= Constants.SCALE) { + uint256 deltaY = deltaLiquidity >> Constants.SCALE_OFFSET; + deltaY = deltaY > y ? y : deltaY; - if (amountY > deltaLiquidity) { - amountY -= uint128(deltaLiquidity); - } else { - amountY = 0; - amountX = effectiveLiquidity.shiftDivRoundUp(Constants.SCALE_OFFSET, price).safe128(); + y -= deltaY; + deltaLiquidity -= deltaY << Constants.SCALE_OFFSET; + } + + if (deltaLiquidity >= price) { + uint256 deltaX = deltaLiquidity / price; + deltaX = deltaX > x ? x : deltaX; + + x -= deltaX; + } + + amountsIn = uint128(x).encode(uint128(y)); } - effectiveAmountsIn = amountX.encode(amountY); + return (shares, amountsIn); } /** @@ -107,13 +118,23 @@ library BinHelper { * @return liquidity The amount of liquidity */ function getLiquidity(bytes32 amounts, uint256 price) internal pure returns (uint256 liquidity) { - (uint128 x, uint128 y) = amounts.decode(); + (uint256 x, uint256 y) = amounts.decode(); if (x > 0) { - liquidity = price.mulShiftRoundDown(x, Constants.SCALE_OFFSET); + unchecked { + liquidity = price * x; + if (x != 0 && liquidity / x != price) revert BinHelper__LiquidityOverflow(); + } } if (y > 0) { - liquidity += y; + unchecked { + y <<= Constants.SCALE_OFFSET; + liquidity += y; + + if (liquidity < y) revert BinHelper__LiquidityOverflow(); + } } + + return liquidity; } /** @@ -124,7 +145,7 @@ library BinHelper { */ function verifyAmounts(bytes32 amounts, uint24 activeId, uint24 id) internal pure { if (id < activeId && (amounts << 128) > 0 || id > activeId && uint256(amounts) > type(uint128).max) { - revert BinMath__CompositionFactorFlawed(id); + revert BinHelper__CompositionFactorFlawed(id); } } diff --git a/src/libraries/PairParameterHelper.sol b/src/libraries/PairParameterHelper.sol index 09d23159..114ea017 100644 --- a/src/libraries/PairParameterHelper.sol +++ b/src/libraries/PairParameterHelper.sol @@ -283,6 +283,18 @@ library PairParameterHelper { return params.set(volRef, Encoded.MASK_UINT20, OFFSET_VOL_REF); } + /** + * @dev Set the volatility accumulator in the encoded pair parameters + * @param params The encoded pair parameters + * @param volAcc The volatility accumulator + * @return The updated encoded pair parameters + */ + function setVolatilityAccumulator(bytes32 params, uint24 volAcc) internal pure returns (bytes32) { + if (volAcc > Encoded.MASK_UINT20) revert PairParametersHelper__InvalidParameter(); + + return params.set(volAcc, Encoded.MASK_UINT20, OFFSET_VOL_ACC); + } + /** * @dev Set the active id in the encoded pair parameters * @param params The encoded pair parameters @@ -391,7 +403,7 @@ library PairParameterHelper { volAcc = volAcc > maxVolAcc ? maxVolAcc : volAcc; - return params.set(volAcc, Encoded.MASK_UINT20, OFFSET_VOL_ACC); + return setVolatilityAccumulator(params, uint24(volAcc)); } /** diff --git a/test/LBPairFees.t.sol b/test/LBPairFees.t.sol index 12c3c369..c9c47c44 100644 --- a/test/LBPairFees.t.sol +++ b/test/LBPairFees.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.10; import "./helpers/TestHelper.sol"; +import "../src/libraries/ImmutableClone.sol"; contract LBPairFeesTest is TestHelper { using PackedUint128Math for uint128; @@ -543,4 +544,26 @@ contract LBPairFeesTest is TestHelper { assertEq(wavax.balanceOf(feeRecipient), previousProtocolFeeX - 1, "test_CollectProtocolFees::19"); assertEq(usdc.balanceOf(feeRecipient), previousProtocolFeeY - 1, "test_CollectProtocolFees::20"); } + + function test_revert_TotalFeeExceeded( + uint8 binStep, + uint16 baseFactor, + uint24 variableFeeControl, + uint24 maxVolatilityAccumulator + ) external { + vm.assume(maxVolatilityAccumulator <= Encoded.MASK_UINT20); + + uint256 baseFee = uint256(baseFactor) * binStep * 5e9; + uint256 varFee = ((uint256(binStep) * maxVolatilityAccumulator) ** 2 * variableFeeControl + 399) / 400; + + vm.assume(baseFee + varFee > 1e17); + + bytes memory data = abi.encodePacked(wavax, usdc, binStep); + + pairWavax = LBPair(ImmutableClone.cloneDeterministic(address(pairImplementation), data, keccak256(data))); + + vm.expectRevert(ILBPair.LBPair__MaxTotalFeeExceeded.selector); + vm.prank(address(factory)); + pairWavax.setStaticFeeParameters(baseFactor, 1, 1, 1, variableFeeControl, 1, maxVolatilityAccumulator); + } } diff --git a/test/LBPairOracle.t.sol b/test/LBPairOracle.t.sol index e1953bba..5af68c59 100644 --- a/test/LBPairOracle.t.sol +++ b/test/LBPairOracle.t.sol @@ -262,4 +262,14 @@ contract LBPairOracleTest is TestHelper { assertEq(cumulativeVolatilityNow, (newActiveId - activeId) * 10_000 * 1000, "Test_MaxLengthOracle::19"); assertEq(cumulativeBinCrossedNow, (newActiveId - activeId) * 1000, "Test_MaxLengthOracle::20"); } + + function test_GetOracleParametersEmptyOracle() external { + (, uint256 size, uint256 activeSize, uint256 lastUpdated, uint256 firstTimestamp) = + pairWavax.getOracleParameters(); + + assertEq(size, 0, "Test_GetOracleParametersEmptyOracle::1"); + assertEq(activeSize, 0, "Test_GetOracleParametersEmptyOracle::2"); + assertEq(lastUpdated, 0, "Test_GetOracleParametersEmptyOracle::3"); + assertEq(firstTimestamp, 0, "Test_GetOracleParametersEmptyOracle::4"); + } } diff --git a/test/libraries/BinHelper.t.sol b/test/libraries/BinHelper.t.sol index 4315eadd..9dccb878 100644 --- a/test/libraries/BinHelper.t.sol +++ b/test/libraries/BinHelper.t.sol @@ -36,19 +36,25 @@ contract BinHelperTest is TestHelper { } function testFuzz_GetLiquidity(uint128 amountInX, uint128 amountInY, uint256 price) external { - uint256 px = price.mulShiftRoundDown(amountInX, Constants.SCALE_OFFSET); + bytes32 amountsIn = amountInX.encode(amountInY); + + (uint256 px, uint256 L) = (0, 0); + + unchecked { + px = price * uint256(amountInX); + L = px + (uint256(amountInY) << 128); + } - bytes32 amountIn = amountInX.encode(amountInY); - if (px > type(uint256).max - amountInY) { - vm.expectRevert(); - amountIn.getLiquidity(price); + if (amountInX != 0 && px / amountInX != price || L < px) { + vm.expectRevert(BinHelper.BinHelper__LiquidityOverflow.selector); + amountsIn.getLiquidity(price); } else { - uint256 liquidity = amountIn.getLiquidity(price); - assertEq(liquidity, px + amountInY, "test_GetLiquidity::1"); + uint256 liquidity = amountsIn.getLiquidity(price); + assertEq(liquidity, price * amountInX + (uint256(amountInY) << 128), "test_GetLiquidity::1"); } } - function testFuzz_GetShareAndEffectiveAmountsIn( + function testFuzz_getSharesAndEffectiveAmountsIn( uint128 binReserveX, uint128 binReserveY, uint128 amountInX, @@ -56,6 +62,21 @@ contract BinHelperTest is TestHelper { uint256 price, uint256 totalSupply ) external { + vm.assume( + price > 0 + && ( + binReserveX == 0 + || ( + price <= type(uint256).max / binReserveX + && price * binReserveX <= type(uint256).max - binReserveY << 128 + ) + ) + && ( + amountInX == 0 + || (price <= type(uint256).max / amountInX && price * amountInX <= type(uint256).max - amountInY << 128) + ) + ); + bytes32 binReserves = binReserveX.encode(binReserveY); uint256 binLiquidity = binReserves.getLiquidity(price); @@ -64,43 +85,52 @@ contract BinHelperTest is TestHelper { bytes32 amountsIn = amountInX.encode(amountInY); (uint256 shares, bytes32 effectiveAmountsIn) = - binReserves.getShareAndEffectiveAmountsIn(amountsIn, price, totalSupply); + binReserves.getSharesAndEffectiveAmountsIn(amountsIn, price, totalSupply); - assertLe(uint256(effectiveAmountsIn), uint256(amountsIn), "test_GetShareAndEffectiveAmountsIn::1"); + assertLe(uint256(effectiveAmountsIn), uint256(amountsIn), "test_getSharesAndEffectiveAmountsIn::1"); uint256 userLiquidity = amountsIn.getLiquidity(price); uint256 expectedShares = binLiquidity == 0 || totalSupply == 0 ? userLiquidity : userLiquidity.mulDivRoundDown(totalSupply, binLiquidity); - assertEq(shares, expectedShares, "test_GetShareAndEffectiveAmountsIn::2"); + assertEq(shares, expectedShares, "test_getSharesAndEffectiveAmountsIn::2"); } - function testFuzz_TryExploitShares(uint128 amountX, uint128 amountY, uint256 price) external { - vm.assume(price > 0 && amountX > 0 && amountX < type(uint128).max && amountY > 0 && amountY < type(uint128).max); - - // exploiter front run the tx and mint 1 of liquidity - uint256 totalSupply = 1; + function testFuzz_TryExploitShares( + uint128 amountX1, + uint128 amountY1, + uint128 amountX2, + uint128 amountY2, + uint256 price + ) external { + vm.assume( + price > 0 && amountX1 > 0 && amountY1 > 0 && amountX2 > 0 && amountY2 > 0 + && uint256(amountX1) + amountX2 <= type(uint128).max && uint256(amountY1) + amountY2 <= type(uint128).max + && price <= type(uint256).max / (uint256(amountX1) + amountX2) + && uint256(amountY1) + amountY2 <= type(uint128).max + && price * (uint256(amountX1) + amountX2) <= type(uint256).max - ((uint256(amountY1) + amountY2) << 128) + ); - // exploiter increase the reserve to amounts added by user + 1 - bytes32 binReserves = uint128(amountX + 1).encode(uint128(amountY + 1)); + // exploiter front run the tx and mint the min amount of shares, so the total supply is 2^128 + uint256 totalSupply = 1 << 128; + bytes32 binReserves = amountX1.encode(amountY1); - // user add liquidity + bytes32 amountsIn = amountX2.encode(amountY2); (uint256 shares, bytes32 effectiveAmountsIn) = - binReserves.getShareAndEffectiveAmountsIn(amountX.encode(amountY), price, totalSupply); + binReserves.getSharesAndEffectiveAmountsIn(amountsIn, price, totalSupply); - assertEq(shares, 0, "test_TryExploitShares::1"); + binReserves = binReserves.add(effectiveAmountsIn); + totalSupply += shares; - (uint128 effectiveX, uint128 effectiveY) = effectiveAmountsIn.decode(); - assertEq(effectiveX, 0, "test_TryExploitShares::2"); - assertEq(effectiveY, 0, "test_TryExploitShares::3"); + uint256 userReceivedX = shares.mulDivRoundDown(binReserves.decodeFirst(), totalSupply); + uint256 userReceivedY = shares.mulDivRoundDown(binReserves.decodeSecond(), totalSupply); - // If user added liquidity with 1 wei more, he will get 1 share - (shares, effectiveAmountsIn) = - binReserves.getShareAndEffectiveAmountsIn((amountX + 1).encode(amountY + 1), price, totalSupply); + uint256 receivedInY = userReceivedX.mulShiftRoundDown(price, Constants.SCALE_OFFSET) + userReceivedY; + uint256 sentInY = price.mulShiftRoundDown(effectiveAmountsIn.decodeFirst(), Constants.SCALE_OFFSET) + + effectiveAmountsIn.decodeSecond(); - assertEq(shares, 1, "test_TryExploitShares::3"); - assertEq(effectiveAmountsIn, (amountX + 1).encode(amountY + 1), "test_TryExploitShares::4"); + assertApproxEqAbs(receivedInY, sentInY, ((price - 1) >> 128) + 2, "test_TryExploitShares::1"); } function testFuzz_VerifyAmountsNeqIds(uint128 amountX, uint128 amountY, uint24 activeId, uint24 id) external { @@ -109,7 +139,7 @@ contract BinHelperTest is TestHelper { bytes32 amounts = amountX.encode(amountY); if (id < activeId && amountX > 0 || id > activeId && amountY > 0) { - vm.expectRevert(abi.encodeWithSelector(BinHelper.BinMath__CompositionFactorFlawed.selector, id)); + vm.expectRevert(abi.encodeWithSelector(BinHelper.BinHelper__CompositionFactorFlawed.selector, id)); } amounts.verifyAmounts(activeId, id); @@ -129,6 +159,14 @@ contract BinHelperTest is TestHelper { uint256 price, uint256 totalSupply ) external { + // make sure p*x+y doesn't overflow + vm.assume( + price > 0 && amountXIn > 0 && amountYIn > 0 && price <= type(uint256).max / amountXIn + && price * amountXIn <= type(uint256).max - uint256(amountYIn) << 128 + && (reserveX == 0 || price <= type(uint256).max / reserveX) + && price * reserveX <= type(uint256).max - uint256(reserveY) << 128 + ); + bytes32 binReserves = reserveX.encode(reserveY); uint256 binLiquidity = binReserves.getLiquidity(price); @@ -138,7 +176,7 @@ contract BinHelperTest is TestHelper { ); (uint256 shares, bytes32 amountsIn) = - binReserves.getShareAndEffectiveAmountsIn(amountXIn.encode(amountYIn), price, totalSupply); + binReserves.getSharesAndEffectiveAmountsIn(amountXIn.encode(amountYIn), price, totalSupply); vm.assume( !binReserves.gt(bytes32(type(uint256).max).sub(amountsIn)) && totalSupply <= type(uint256).max - shares diff --git a/test/libraries/PairParameterHelper.t.sol b/test/libraries/PairParameterHelper.t.sol index 9424df06..ab9d593e 100644 --- a/test/libraries/PairParameterHelper.t.sol +++ b/test/libraries/PairParameterHelper.t.sol @@ -105,6 +105,26 @@ contract PairParameterHelperTest is Test { params.setVolatilityReference(volatilityReference); } + function testFuzz_SetVolatilityAccumulator(bytes32 params, uint24 volatilityAccumulator) external { + vm.assume(volatilityAccumulator <= Encoded.MASK_UINT20); + + bytes32 newParams = params.setVolatilityAccumulator(volatilityAccumulator); + + assertEq(newParams.getVolatilityAccumulator(), volatilityAccumulator, "testFuzz_SetVolatilityAccumulator::1"); + assertEq( + newParams & bytes32(~Encoded.MASK_UINT20 << PairParameterHelper.OFFSET_VOL_ACC), + params & bytes32(~Encoded.MASK_UINT20 << PairParameterHelper.OFFSET_VOL_ACC), + "testFuzz_SetVolatilityAccumulator::2" + ); + } + + function testFuzz_revert_SetVolatilityAccumulator(bytes32 params, uint24 volatilityAccumulator) external { + vm.assume(volatilityAccumulator > Encoded.MASK_UINT20); + + vm.expectRevert(PairParameterHelper.PairParametersHelper__InvalidParameter.selector); + params.setVolatilityAccumulator(volatilityAccumulator); + } + function testFuzz_SetActiveId(bytes32 params, uint24 activeId) external { uint24 previousActiveId = params.getActiveId(); uint24 deltaId = previousActiveId > activeId ? previousActiveId - activeId : activeId - previousActiveId; From 772cae04f8746385306bda496509b5c6e350853d Mon Sep 17 00:00:00 2001 From: Mathieu <85969303+Mathieu-Be@users.noreply.github.com> Date: Sun, 5 Feb 2023 20:26:55 +0100 Subject: [PATCH 16/47] Remove revisions and add a new lock mechanism for pair creation (#82) * remove pair revisions * change lock mechanism to unlock individual bin steps * fix typo * test and fixes for swaps through V1 and V2 pair * Use the Encoded library in the factory * replace V3 version by V2_1 --- src/LBFactory.sol | 248 +++++++++---------------------- src/LBQuoter.sol | 14 +- src/LBRouter.sol | 187 +++++++++++++---------- src/interfaces/ILBFactory.sol | 23 +-- src/interfaces/ILBLegacyPair.sol | 2 + src/interfaces/ILBRouter.sol | 16 +- test/LBFactory.t.sol | 235 +++++++++++------------------ test/LBRouter.Liquidity.t.sol | 34 ++--- test/LBRouter.Swap.t.sol | 24 +-- test/helpers/TestHelper.sol | 3 +- test/integration/LBQuoter.t.sol | 42 +++--- test/integration/LBRouter.t.sol | 140 +++++++++++++++++ 12 files changed, 482 insertions(+), 486 deletions(-) create mode 100644 test/integration/LBRouter.t.sol diff --git a/src/LBFactory.sol b/src/LBFactory.sol index 0c7ba4ab..19442959 100644 --- a/src/LBFactory.sol +++ b/src/LBFactory.sol @@ -20,7 +20,7 @@ import {ILBPair} from "./interfaces/ILBPair.sol"; /// @author Trader Joe /// @notice Contract used to deploy and register new LBPairs. /// Enables setting fee parameters, flashloan fees and LBPair implementation. -/// Unless the `_creationUnlocked` is `true`, only the owner of the factory can create pairs. +/// Unless the `_isPresetOpen` is `true`, only the owner of the factory can create pairs. contract LBFactory is PendingOwnable, ILBFactory { using SafeCast for uint256; using Encoded for bytes32; @@ -30,9 +30,6 @@ contract LBFactory is PendingOwnable, ILBFactory { uint256 private constant _MIN_BIN_STEP = 1; // 0.01% uint256 private constant _MAX_BIN_STEP = 200; // 1%, can't be greater than 247 for indexing reasons - /// @notice Whether the createLBPair function is unlocked and can be called by anyone (true) or only by owner (false) - bool private _creationUnlocked; - uint256 private constant _MAX_FEE = 0.1e18; // 10% uint256 private constant _MAX_PROTOCOL_SHARE = 2_500; // 25% address private _feeRecipient; @@ -44,12 +41,14 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @dev Mapping from a (tokenA, tokenB, binStep) to a LBPair. The tokens are ordered to save gas, but they can be /// in the reverse order in the actual pair. Always query one of the 2 tokens of the pair to assert the order of the 2 tokens - mapping(IERC20 => mapping(IERC20 => mapping(uint256 => LBPairInformation[]))) private _lbPairsInfos; + mapping(IERC20 => mapping(IERC20 => mapping(uint256 => LBPairInformation))) private _lbPairsInfo; /// @dev Whether a preset was set or not, if the bit at `index` is 1, it means that the binStep `index` was set /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas bytes32 private _availablePresets; + bytes32 private _openPresets; + // The parameters presets mapping(uint256 => bytes32) private _presets; @@ -59,8 +58,6 @@ contract LBFactory is PendingOwnable, ILBFactory { /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas mapping(IERC20 => mapping(IERC20 => bytes32)) private _availableLBPairBinSteps; - uint256 private constant _REVISION_START_INDEX = 1; - /// @notice Constructor /// @param feeRecipient The address of the fee recipient /// @param flashLoanFee The value of the fee for flash loan @@ -74,9 +71,6 @@ contract LBFactory is PendingOwnable, ILBFactory { } // TODO: Natspecs - function isCreationUnlocked() external view returns (bool unlocked) { - return _creationUnlocked; - } function getMinBinStep() external pure returns (uint256 minBinStep) { return _MIN_BIN_STEP; @@ -136,34 +130,18 @@ contract LBFactory is PendingOwnable, ILBFactory { return _quoteAssetWhitelist.contains(address(token)); } - /// @notice View function to return the number of revisions of a LBPair - /// @param tokenA The address of the first token of the pair. The order doesn't matter - /// @param tokenB The address of the second token of the pair - /// @param binStep The bin step of the LBPair - /// @return The number of revisions - function getNumberOfRevisions(IERC20 tokenA, IERC20 tokenB, uint256 binStep) - external - view - override - returns (uint256) - { - (tokenA, tokenB) = _sortTokens(tokenA, tokenB); - - return _lbPairsInfos[tokenA][tokenB][binStep].length; - } - /// @notice Returns the LBPairInformation if it exists, /// if not, then the address 0 is returned. The order doesn't matter /// @param tokenA The address of the first token of the pair /// @param tokenB The address of the second token of the pair /// @param binStep The bin step of the LBPair /// @return The LBPairInformation - function getLBPairInformation(IERC20 tokenA, IERC20 tokenB, uint256 binStep, uint256 revision) + function getLBPairInformation(IERC20 tokenA, IERC20 tokenB, uint256 binStep) external view returns (LBPairInformation memory) { - return _getLBPairInformation(tokenA, tokenB, binStep, revision); + return _getLBPairInformation(tokenA, tokenB, binStep); } /// @notice View function to return the different parameters of the preset @@ -201,19 +179,25 @@ contract LBFactory is PendingOwnable, ILBFactory { maxVolatilityAccumulator = preset.getMaxVolatilityAccumulator(); } + function getIsPresetOpen(uint8 binStep) external view returns (bool) { + bytes32 openPresets = _openPresets; + + return _isPresetOpen(openPresets, binStep); + } + /// @notice View function to return the list of available binStep with a preset /// @return presetsBinStep The list of binStep function getAllBinSteps() external view override returns (uint256[] memory presetsBinStep) { unchecked { bytes32 avPresets = _availablePresets; - uint256 nbPresets = avPresets.decode(type(uint8).max, 248); + uint256 nbPresets = avPresets.decodeUint8(248); if (nbPresets > 0) { presetsBinStep = new uint256[](nbPresets); uint256 index; for (uint256 i = _MIN_BIN_STEP; i <= _MAX_BIN_STEP; ++i) { - if (avPresets.decode(1, i) == 1) { + if (avPresets.decodeUint1(i) == 1) { presetsBinStep[index] = i; if (++index == nbPresets) break; } @@ -236,42 +220,23 @@ contract LBFactory is PendingOwnable, ILBFactory { (IERC20 tokenA, IERC20 tokenB) = _sortTokens(tokenX, tokenY); bytes32 avLBPairBinSteps = _availableLBPairBinSteps[tokenA][tokenB]; - uint256 nbExistingBinSteps = avLBPairBinSteps.decode(type(uint8).max, 248); + uint256 nbAvailable = avLBPairBinSteps.decodeUint8(248); - uint256 totalPairs; + if (nbAvailable > 0) { + lbPairsAvailable = new LBPairInformation[](nbAvailable); - if (nbExistingBinSteps > 0) { uint256 index; - // Loops a first time to know how many pairs are available - for (uint256 i = _MIN_BIN_STEP; i <= _MAX_BIN_STEP; ++i) { - if (avLBPairBinSteps.decode(1, i) == 1) { - totalPairs += _lbPairsInfos[tokenA][tokenB][i].length; - - if (++index == nbExistingBinSteps) break; - } - } - - lbPairsAvailable = new LBPairInformation[](totalPairs); - - index = 0; - // Loops a second time to fill the array for (uint256 i = _MIN_BIN_STEP; i <= _MAX_BIN_STEP; ++i) { - if (avLBPairBinSteps.decode(1, i) == 1) { - uint256 revisionNumber = _lbPairsInfos[tokenA][tokenB][i].length; - for (uint256 j = 0; j < revisionNumber; ++j) { - LBPairInformation memory lbPairInformation = _lbPairsInfos[tokenA][tokenB][i][j]; - - lbPairsAvailable[index++] = LBPairInformation({ - binStep: i.safe8(), - LBPair: lbPairInformation.LBPair, - createdByOwner: lbPairInformation.createdByOwner, - ignoredForRouting: lbPairInformation.ignoredForRouting, - revisionIndex: lbPairInformation.revisionIndex, - implementation: lbPairInformation.implementation - }); - } - - if (index == totalPairs) break; + if (avLBPairBinSteps.decodeUint1(i) == 1) { + LBPairInformation memory pairInformation = _lbPairsInfo[tokenA][tokenB][i]; + + lbPairsAvailable[index] = LBPairInformation({ + binStep: i.safe8(), + LBPair: pairInformation.LBPair, + createdByOwner: pairInformation.createdByOwner, + ignoredForRouting: pairInformation.ignoredForRouting + }); + if (++index == nbAvailable) break; } } } @@ -307,9 +272,9 @@ contract LBFactory is PendingOwnable, ILBFactory { override returns (ILBPair pair) { - // TODO: fix stack too deep to cache owner - // address _owner = owner(); - if (!_creationUnlocked && msg.sender != owner()) revert LBFactory__FunctionIsLockedForUsers(msg.sender); + if (!_isPresetOpen(_openPresets, binStep) && msg.sender != owner()) { + revert LBFactory__FunctionIsLockedForUsers(msg.sender, binStep); + } address implementation = _lbPairImplementation; @@ -326,13 +291,13 @@ contract LBFactory is PendingOwnable, ILBFactory { (IERC20 tokenA, IERC20 tokenB) = _sortTokens(tokenX, tokenY); // single check is sufficient if (address(tokenA) == address(0)) revert LBFactory__AddressZero(); - if (_lbPairsInfos[tokenA][tokenB][binStep].length != 0) { + if (address(_lbPairsInfo[tokenA][tokenB][binStep].LBPair) != address(0)) { revert LBFactory__LBPairAlreadyExists(tokenX, tokenY, binStep); } // We remove the bits that are not part of the feeParameters { - bytes32 salt = keccak256(abi.encode(tokenA, tokenB, binStep, _REVISION_START_INDEX)); + bytes32 salt = keccak256(abi.encode(tokenA, tokenB, binStep)); pair = ILBPair( ImmutableClone.cloneDeterministic(implementation, abi.encodePacked(tokenX, tokenY, binStep), salt) ); @@ -355,23 +320,19 @@ contract LBFactory is PendingOwnable, ILBFactory { ); } - _lbPairsInfos[tokenA][tokenB][binStep].push( - LBPairInformation({ - binStep: binStep, - LBPair: pair, - createdByOwner: msg.sender == owner(), - ignoredForRouting: false, - revisionIndex: uint16(_REVISION_START_INDEX), - implementation: implementation - }) - ); + _lbPairsInfo[tokenA][tokenB][binStep] = LBPairInformation({ + binStep: binStep, + LBPair: pair, + createdByOwner: msg.sender == owner(), + ignoredForRouting: false + }); _allLBPairs.push(pair); { bytes32 avLBPairBinSteps = _availableLBPairBinSteps[tokenA][tokenB]; // We add a 1 at bit `binStep` as this binStep is now set - avLBPairBinSteps = bytes32(uint256(avLBPairBinSteps) | (1 << binStep)); + avLBPairBinSteps = avLBPairBinSteps.set(1, 1, binStep); // Increase the number of lb pairs by 1 avLBPairBinSteps = bytes32(uint256(avLBPairBinSteps) + (1 << 248)); @@ -383,94 +344,24 @@ contract LBFactory is PendingOwnable, ILBFactory { emit LBPairCreated(tokenX, tokenY, binStep, pair, _allLBPairs.length - 1); } - /// @notice Function to create a new revision of a pair - /// Restricted to the owner - /// @param tokenX The first token of the pair - /// @param tokenY The second token of the pair - /// @param binStep The binStep of the pair - /// @return pair The new LBPair - function createLBPairRevision(IERC20 tokenX, IERC20 tokenY, uint8 binStep) - external - override - onlyOwner - returns (ILBPair pair) - { - (IERC20 tokenA, IERC20 tokenB) = _sortTokens(tokenX, tokenY); - - uint256 currentVersionNumber = _lbPairsInfos[tokenA][tokenB][binStep].length; - if (currentVersionNumber == 0) revert LBFactory__LBPairDoesNotExists(tokenX, tokenY, binStep); - - address implementation = _lbPairImplementation; - - // Get latest version - LBPairInformation memory latestVersionPairInformation = - _lbPairsInfos[tokenA][tokenB][binStep][currentVersionNumber - _REVISION_START_INDEX]; - - if (latestVersionPairInformation.implementation == implementation) { - revert LBFactory__SameImplementation(implementation); - } - - ILBPair oldLBPair = latestVersionPairInformation.LBPair; - - bytes32 preset = _presets[binStep]; - - bytes32 salt = keccak256(abi.encode(tokenA, tokenB, binStep, ++currentVersionNumber)); - pair = - ILBPair(ImmutableClone.cloneDeterministic(implementation, abi.encodePacked(tokenX, tokenY, binStep), salt)); - - { - pair.initialize( - preset.getBaseFactor(), - preset.getFilterPeriod(), - preset.getDecayPeriod(), - preset.getReductionFactor(), - preset.getVariableFeeControl(), - preset.getProtocolShare(), - preset.getMaxVolatilityAccumulator(), - oldLBPair.getActiveId() - ); - - _lbPairsInfos[tokenA][tokenB][binStep].push( - LBPairInformation({ - binStep: binStep, - LBPair: pair, - createdByOwner: true, - ignoredForRouting: false, - revisionIndex: uint16(currentVersionNumber), - implementation: implementation - }) - ); - } - - _allLBPairs.push(pair); - - emit LBPairCreated(tokenX, tokenY, binStep, pair, _allLBPairs.length - 1); - } - /// @notice Function to set whether the pair is ignored or not for routing, it will make the pair unusable by the router /// @param tokenX The address of the first token of the pair /// @param tokenY The address of the second token of the pair /// @param binStep The bin step in basis point of the pair - /// @param revision The revision of the pair /// @param ignored Whether to ignore (true) or not (false) the pair for routing - function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, uint256 revision, bool ignored) + function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) external override onlyOwner { (IERC20 tokenA, IERC20 tokenB) = _sortTokens(tokenX, tokenY); - uint256 revisionAmount = _lbPairsInfos[tokenA][tokenB][binStep].length; - if (revisionAmount == 0 || revision > revisionAmount) { - revert LBFactory__AddressZero(); - } - - LBPairInformation memory pairInformation = - _lbPairsInfos[tokenA][tokenB][binStep][revision - _REVISION_START_INDEX]; + LBPairInformation memory pairInformation = _lbPairsInfo[tokenA][tokenB][binStep]; + if (address(pairInformation.LBPair) == address(0)) revert LBFactory__AddressZero(); if (pairInformation.ignoredForRouting == ignored) revert LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); - _lbPairsInfos[tokenA][tokenB][binStep][revision - _REVISION_START_INDEX].ignoredForRouting = ignored; + _lbPairsInfo[tokenA][tokenB][binStep].ignoredForRouting = ignored; emit LBPairIgnoredStateChanged(pairInformation.LBPair, ignored); } @@ -510,9 +401,9 @@ contract LBFactory is PendingOwnable, ILBFactory { _presets[binStep] = preset; bytes32 avPresets = _availablePresets; - if (avPresets.decode(1, binStep) == 0) { + if (avPresets.decodeUint1(binStep) == 0) { // We add a 1 at bit `binStep` as this binStep is now set - avPresets = bytes32(uint256(avPresets) | (1 << binStep)); + avPresets = avPresets.set(1, 1, binStep); // Increase the number of preset by 1 avPresets = bytes32(uint256(avPresets) + (1 << 248)); @@ -533,6 +424,25 @@ contract LBFactory is PendingOwnable, ILBFactory { ); } + function setOpenPreset(uint8 binStep, bool isOpen) external onlyOwner { + bytes32 openPresets = _openPresets; + bool isPresetOpen = _isPresetOpen(openPresets, binStep); + + if (isOpen) { + if (isPresetOpen) revert LBFactory__SamePresetOpenState(); + + // We add a 1 at bit `binStep` as this binStep is now open + _openPresets = _openPresets.set(1, 1, binStep); + } else { + if (!isPresetOpen) revert LBFactory__SamePresetOpenState(); + + // We remove a 1 at bit `binStep` as this binStep is now closed + _openPresets = _openPresets.set(0, 1, binStep); + } + + emit OpenPresetChanged(binStep, isOpen); + } + /// @notice Remove the preset linked to a binStep /// @param binStep The bin step to remove function removePreset(uint8 binStep) external override onlyOwner { @@ -541,7 +451,7 @@ contract LBFactory is PendingOwnable, ILBFactory { // Set the bit `binStep` to 0 bytes32 avPresets = _availablePresets; - avPresets &= bytes32(type(uint256).max - (1 << binStep)); + avPresets = avPresets.set(0, 1, binStep); avPresets = bytes32(uint256(avPresets) - (1 << 248)); // Save the changes @@ -555,7 +465,6 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @param tokenX The address of the first token /// @param tokenY The address of the second token /// @param binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @param revision The revision of the LBPair /// @param baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep /// @param filterPeriod The period where the accumulator value is untouched, prevent spam /// @param decayPeriod The period where the accumulator value is halved @@ -567,7 +476,6 @@ contract LBFactory is PendingOwnable, ILBFactory { IERC20 tokenX, IERC20 tokenY, uint8 binStep, - uint16 revision, uint16 baseFactor, uint16 filterPeriod, uint16 decayPeriod, @@ -576,7 +484,9 @@ contract LBFactory is PendingOwnable, ILBFactory { uint16 protocolShare, uint24 maxVolatilityAccumulator ) external override onlyOwner { - ILBPair lbPair = _getLBPairInformation(tokenX, tokenY, binStep, revision).LBPair; + ILBPair lbPair = _getLBPairInformation(tokenX, tokenY, binStep).LBPair; + + if (address(lbPair) == address(0)) revert LBFactory__LBPairNotCreated(tokenX, tokenY, binStep); lbPair.setStaticFeeParameters( baseFactor, @@ -620,14 +530,6 @@ contract LBFactory is PendingOwnable, ILBFactory { emit FlashLoanFeeSet(oldFlashLoanFee, flashLoanFee); } - /// @notice Function to set the creation restriction of the Factory - /// @param locked If the creation is restricted (true) or not (false) - function setFactoryLockedState(bool locked) external override onlyOwner { - if (_creationUnlocked != locked) revert LBFactory__FactoryLockIsAlreadyInTheSameState(); - _creationUnlocked = !locked; - emit FactoryLockedStatusUpdated(locked); - } - /// @notice Function to add an asset to the whitelist of quote assets /// @param quoteAsset The quote asset (e.g: AVAX, USDC...) function addQuoteAsset(IERC20 quoteAsset) external override onlyOwner { @@ -646,6 +548,10 @@ contract LBFactory is PendingOwnable, ILBFactory { emit QuoteAssetRemoved(quoteAsset); } + function _isPresetOpen(bytes32 openPresets, uint8 binStep) internal pure returns (bool) { + return openPresets.decodeUint1(binStep) == 1; + } + /// @notice Internal function to set the recipient of the fee /// @param feeRecipient The address of the recipient function _setFeeRecipient(address feeRecipient) internal { @@ -725,20 +631,14 @@ contract LBFactory is PendingOwnable, ILBFactory { /// @param tokenA The address of the first token of the pair /// @param tokenB The address of the second token of the pair /// @param binStep The bin step of the LBPair - /// @param revision The revision of the LBPair /// @return The LBPairInformation - function _getLBPairInformation(IERC20 tokenA, IERC20 tokenB, uint256 binStep, uint256 revision) + function _getLBPairInformation(IERC20 tokenA, IERC20 tokenB, uint256 binStep) private view returns (LBPairInformation memory) { (tokenA, tokenB) = _sortTokens(tokenA, tokenB); - - if (_lbPairsInfos[tokenA][tokenB][binStep].length == 0) { - revert LBFactory__LBPairNotCreated(tokenA, tokenB, binStep); - } - - return _lbPairsInfos[tokenA][tokenB][binStep][revision - _REVISION_START_INDEX]; + return _lbPairsInfo[tokenA][tokenB][binStep]; } /// @notice Private view function to sort 2 tokens in ascending order diff --git a/src/LBQuoter.sol b/src/LBQuoter.sol index eaf1752d..64ae9660 100644 --- a/src/LBQuoter.sol +++ b/src/LBQuoter.sol @@ -41,7 +41,7 @@ contract LBQuoter { address[] route; address[] pairs; uint256[] binSteps; - uint256[] revisions; + ILBRouter.Version[] versions; uint128[] amounts; uint128[] virtualAmountsWithoutSlippage; uint128[] fees; @@ -115,7 +115,7 @@ contract LBQuoter { uint256 swapLength = route.length - 1; quote.pairs = new address[](swapLength); quote.binSteps = new uint256[](swapLength); - quote.revisions = new uint256[](swapLength); + quote.versions = new ILBRouter.Version[](swapLength); quote.fees = new uint128[](swapLength); quote.amounts = new uint128[](route.length); quote.virtualAmountsWithoutSlippage = new uint128[](route.length); @@ -136,6 +136,7 @@ contract LBQuoter { JoeLibrary.quote(quote.virtualAmountsWithoutSlippage[i] * 997, reserveIn * 1000, reserveOut) ); quote.fees[i] = 0.003e18; // 0.3% + quote.versions[i] = ILBRouter.Version.V1; } } @@ -154,6 +155,7 @@ contract LBQuoter { quote.amounts[i + 1] = uint128(swapAmountOut); quote.pairs[i] = address(legacyLBPairsAvailable[j].LBPair); quote.binSteps[i] = legacyLBPairsAvailable[j].binStep; + quote.versions[i] = ILBRouter.Version.V2; // Getting current price (,, uint256 activeId) = legacyLBPairsAvailable[j].LBPair.getReservesAndId(); @@ -186,7 +188,7 @@ contract LBQuoter { quote.amounts[i + 1] = swapAmountOut; quote.pairs[i] = address(LBPairsAvailable[j].LBPair); quote.binSteps[i] = uint8(LBPairsAvailable[j].binStep); - quote.revisions[i] = LBPairsAvailable[j].revisionIndex; + quote.versions[i] = ILBRouter.Version.V2_1; // Getting current price uint24 activeId = LBPairsAvailable[j].LBPair.getActiveId(); @@ -225,7 +227,7 @@ contract LBQuoter { uint256 swapLength = route.length - 1; quote.pairs = new address[](swapLength); quote.binSteps = new uint256[](swapLength); - quote.revisions = new uint256[](swapLength); + quote.versions = new ILBRouter.Version[](swapLength); quote.fees = new uint128[](swapLength); quote.amounts = new uint128[](route.length); quote.virtualAmountsWithoutSlippage = new uint128[](route.length); @@ -246,6 +248,7 @@ contract LBQuoter { ); quote.fees[i - 1] = 0.003e18; // 0.3% + quote.versions[i - 1] = ILBRouter.Version.V1; } } @@ -265,6 +268,7 @@ contract LBQuoter { quote.amounts[i - 1] = uint128(swapAmountIn); quote.pairs[i - 1] = address(legacyLBPairsAvailable[j].LBPair); quote.binSteps[i - 1] = legacyLBPairsAvailable[j].binStep; + quote.versions[i - 1] = ILBRouter.Version.V2; // Getting current price (,, uint256 activeId) = legacyLBPairsAvailable[j].LBPair.getReservesAndId(); @@ -300,7 +304,7 @@ contract LBQuoter { quote.amounts[i - 1] = swapAmountIn; quote.pairs[i - 1] = address(LBPairsAvailable[j].LBPair); quote.binSteps[i - 1] = uint8(LBPairsAvailable[j].binStep); - quote.revisions[i - 1] = LBPairsAvailable[j].revisionIndex; + quote.versions[i - 1] = ILBRouter.Version.V2_1; // Getting current price uint24 activeId = LBPairsAvailable[j].LBPair.getActiveId(); diff --git a/src/LBRouter.sol b/src/LBRouter.sol index 27964e52..833f7aaa 100644 --- a/src/LBRouter.sol +++ b/src/LBRouter.sol @@ -16,8 +16,10 @@ import {Uint256x256Math} from "./libraries/math/Uint256x256Math.sol"; import {IJoePair} from "./interfaces/IJoePair.sol"; import {ILBPair} from "./interfaces/ILBPair.sol"; +import {ILBLegacyPair} from "./interfaces/ILBLegacyPair.sol"; import {ILBToken} from "./interfaces/ILBToken.sol"; import {ILBRouter} from "./interfaces/ILBRouter.sol"; +import {ILBLegacyRouter} from "./interfaces/ILBLegacyRouter.sol"; import {IJoeFactory} from "./interfaces/IJoeFactory.sol"; import {ILBLegacyFactory} from "./interfaces/ILBLegacyFactory.sol"; import {ILBFactory} from "./interfaces/ILBFactory.sol"; @@ -33,8 +35,9 @@ contract LBRouter is ILBRouter { using PackedUint128Math for bytes32; ILBFactory private immutable _factory; + IJoeFactory private immutable _factoryV1; ILBLegacyFactory private immutable _legacyFactory; - IJoeFactory private immutable _oldFactory; + ILBLegacyRouter private immutable _legacyRouter; IWAVAX private immutable _wavax; modifier onlyFactoryOwner() { @@ -49,22 +52,29 @@ contract LBRouter is ILBRouter { modifier verifyPathValidity(Path memory path) { if ( - path.pairBinSteps.length == 0 || path.revisions.length != path.pairBinSteps.length + path.pairBinSteps.length == 0 || path.versions.length != path.pairBinSteps.length || path.pairBinSteps.length + 1 != path.tokenPath.length ) revert LBRouter__LengthsMismatch(); _; } /// @notice Constructor - /// @param factory_ LBFactory address - /// @param legacyFactory_ V2 LBFactory address - /// @param oldFactory_ Address of old factory (Joe V1) - /// @param wavax_ Address of WAVAX - constructor(ILBFactory factory_, ILBLegacyFactory legacyFactory_, IJoeFactory oldFactory_, IWAVAX wavax_) { - _factory = factory_; - _legacyFactory = legacyFactory_; - _oldFactory = oldFactory_; - _wavax = wavax_; + /// @param factory Address of Joe V2.1 factory + /// @param factoryV1 Address of Joe V1 factory + /// @param legacyFactory Address of Joe V2 factory + /// @param wavax Address of WAVAX + constructor( + ILBFactory factory, + IJoeFactory factoryV1, + ILBLegacyFactory legacyFactory, + ILBLegacyRouter legacyRouter, + IWAVAX wavax + ) { + _factory = factory; + _factoryV1 = factoryV1; + _legacyFactory = legacyFactory; + _legacyRouter = legacyRouter; + _wavax = wavax; } /// @dev Receive function that only accept AVAX from the WAVAX contract @@ -80,8 +90,12 @@ contract LBRouter is ILBRouter { return _legacyFactory; } - function getOldFactory() external view override returns (IJoeFactory) { - return _oldFactory; + function getV1Factory() external view override returns (IJoeFactory) { + return _factoryV1; + } + + function getLegacyRouter() external view override returns (ILBLegacyRouter) { + return _legacyRouter; } function getWAVAX() external view override returns (IWAVAX) { @@ -166,11 +180,10 @@ contract LBRouter is ILBRouter { uint256[] memory liquidityMinted ) { - ILBPair lbPair = _getLBPairInformation( - liquidityParameters.tokenX, - liquidityParameters.tokenY, - liquidityParameters.binStep, - liquidityParameters.revision + ILBPair lbPair = ILBPair( + _getLBPairInformation( + liquidityParameters.tokenX, liquidityParameters.tokenY, liquidityParameters.binStep, Version.V2_1 + ) ); if (liquidityParameters.tokenX != lbPair.getTokenX()) revert LBRouter__WrongTokenOrder(); @@ -197,11 +210,10 @@ contract LBRouter is ILBRouter { uint256[] memory liquidityMinted ) { - ILBPair _LBPair = _getLBPairInformation( - liquidityParameters.tokenX, - liquidityParameters.tokenY, - liquidityParameters.binStep, - liquidityParameters.revision + ILBPair _LBPair = ILBPair( + _getLBPairInformation( + liquidityParameters.tokenX, liquidityParameters.tokenY, liquidityParameters.binStep, Version.V2_1 + ) ); if (liquidityParameters.tokenX != _LBPair.getTokenX()) revert LBRouter__WrongTokenOrder(); @@ -242,7 +254,6 @@ contract LBRouter is ILBRouter { IERC20 tokenX, IERC20 tokenY, uint8 binStep, - uint256 revision, uint256 amountXMin, uint256 amountYMin, uint256[] memory ids, @@ -250,7 +261,7 @@ contract LBRouter is ILBRouter { address to, uint256 deadline ) external override ensure(deadline) returns (uint256 amountX, uint256 amountY) { - ILBPair _LBPair = _getLBPairInformation(tokenX, tokenY, binStep, revision); + ILBPair _LBPair = ILBPair(_getLBPairInformation(tokenX, tokenY, binStep, Version.V2_1)); bool isWrongOrder = tokenX != _LBPair.getTokenX(); if (isWrongOrder) (amountXMin, amountYMin) = (amountYMin, amountXMin); @@ -277,7 +288,6 @@ contract LBRouter is ILBRouter { function removeLiquidityAVAX( IERC20 token, uint8 binStep, - uint256 revision, uint256 amountTokenMin, uint256 amountAVAXMin, uint256[] memory ids, @@ -288,7 +298,7 @@ contract LBRouter is ILBRouter { // TODO - avoid stack too deep and cache wavax // IWAVAX wavax_ = _wavax; - ILBPair lbPair = _getLBPairInformation(token, IERC20(_wavax), binStep, revision); + ILBPair lbPair = ILBPair(_getLBPairInformation(token, IERC20(_wavax), binStep, Version.V2_1)); { bool isAVAXTokenY = IERC20(_wavax) == lbPair.getTokenY(); @@ -322,11 +332,11 @@ contract LBRouter is ILBRouter { address to, uint256 deadline ) external override ensure(deadline) verifyPathValidity(path) returns (uint256 amountOut) { - address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); + address[] memory pairs = _getPairs(path.pairBinSteps, path.versions, path.tokenPath); path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountIn); - amountOut = _swapExactTokensForTokens(amountIn, pairs, path.pairBinSteps, path.tokenPath, to); + amountOut = _swapExactTokensForTokens(amountIn, pairs, path.versions, path.tokenPath, to); if (amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMin, amountOut); } @@ -348,11 +358,11 @@ contract LBRouter is ILBRouter { revert LBRouter__InvalidTokenPath(address(path.tokenPath[path.pairBinSteps.length])); } - address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); + address[] memory pairs = _getPairs(path.pairBinSteps, path.versions, path.tokenPath); path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountIn); - amountOut = _swapExactTokensForTokens(amountIn, pairs, path.pairBinSteps, path.tokenPath, address(this)); + amountOut = _swapExactTokensForTokens(amountIn, pairs, path.versions, path.tokenPath, address(this)); if (amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMinAVAX, amountOut); @@ -375,11 +385,11 @@ contract LBRouter is ILBRouter { { if (path.tokenPath[0] != IERC20(_wavax)) revert LBRouter__InvalidTokenPath(address(path.tokenPath[0])); - address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); + address[] memory pairs = _getPairs(path.pairBinSteps, path.versions, path.tokenPath); _wavaxDepositAndTransfer(pairs[0], msg.value); - amountOut = _swapExactTokensForTokens(msg.value, pairs, path.pairBinSteps, path.tokenPath, to); + amountOut = _swapExactTokensForTokens(msg.value, pairs, path.versions, path.tokenPath, to); if (amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMin, amountOut); } @@ -392,16 +402,16 @@ contract LBRouter is ILBRouter { address to, uint256 deadline ) external override ensure(deadline) verifyPathValidity(path) returns (uint256[] memory amountsIn) { - address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); + address[] memory pairs = _getPairs(path.pairBinSteps, path.versions, path.tokenPath); { - amountsIn = _getAmountsIn(path.pairBinSteps, pairs, path.tokenPath, amountOut); + amountsIn = _getAmountsIn(path.versions, pairs, path.tokenPath, amountOut); if (amountsIn[0] > amountInMax) revert LBRouter__MaxAmountInExceeded(amountInMax, amountsIn[0]); path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountsIn[0]); - uint256 _amountOutReal = _swapTokensForExactTokens(pairs, path.pairBinSteps, path.tokenPath, amountsIn, to); + uint256 _amountOutReal = _swapTokensForExactTokens(pairs, path.versions, path.tokenPath, amountsIn, to); if (_amountOutReal < amountOut) revert LBRouter__InsufficientAmountOut(amountOut, _amountOutReal); } @@ -424,15 +434,15 @@ contract LBRouter is ILBRouter { revert LBRouter__InvalidTokenPath(address(path.tokenPath[path.pairBinSteps.length])); } - address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); - amountsIn = _getAmountsIn(path.pairBinSteps, pairs, path.tokenPath, amountAVAXOut); + address[] memory pairs = _getPairs(path.pairBinSteps, path.versions, path.tokenPath); + amountsIn = _getAmountsIn(path.versions, pairs, path.tokenPath, amountAVAXOut); if (amountsIn[0] > amountInMax) revert LBRouter__MaxAmountInExceeded(amountInMax, amountsIn[0]); path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountsIn[0]); uint256 _amountOutReal = - _swapTokensForExactTokens(pairs, path.pairBinSteps, path.tokenPath, amountsIn, address(this)); + _swapTokensForExactTokens(pairs, path.versions, path.tokenPath, amountsIn, address(this)); if (_amountOutReal < amountAVAXOut) revert LBRouter__InsufficientAmountOut(amountAVAXOut, _amountOutReal); @@ -456,14 +466,14 @@ contract LBRouter is ILBRouter { { if (path.tokenPath[0] != IERC20(_wavax)) revert LBRouter__InvalidTokenPath(address(path.tokenPath[0])); - address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); - amountsIn = _getAmountsIn(path.pairBinSteps, pairs, path.tokenPath, amountOut); + address[] memory pairs = _getPairs(path.pairBinSteps, path.versions, path.tokenPath); + amountsIn = _getAmountsIn(path.versions, pairs, path.tokenPath, amountOut); if (amountsIn[0] > msg.value) revert LBRouter__MaxAmountInExceeded(msg.value, amountsIn[0]); _wavaxDepositAndTransfer(pairs[0], amountsIn[0]); - uint256 amountOutReal = _swapTokensForExactTokens(pairs, path.pairBinSteps, path.tokenPath, amountsIn, to); + uint256 amountOutReal = _swapTokensForExactTokens(pairs, path.versions, path.tokenPath, amountsIn, to); if (amountOutReal < amountOut) revert LBRouter__InsufficientAmountOut(amountOut, amountOutReal); @@ -483,7 +493,7 @@ contract LBRouter is ILBRouter { address to, uint256 deadline ) external override ensure(deadline) verifyPathValidity(path) returns (uint256 amountOut) { - address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); + address[] memory pairs = _getPairs(path.pairBinSteps, path.versions, path.tokenPath); IERC20 targetToken = path.tokenPath[pairs.length]; @@ -491,7 +501,7 @@ contract LBRouter is ILBRouter { path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountIn); - _swapSupportingFeeOnTransferTokens(pairs, path.pairBinSteps, path.tokenPath, to); + _swapSupportingFeeOnTransferTokens(pairs, path.versions, path.tokenPath, to); amountOut = targetToken.balanceOf(to) - balanceBefore; if (amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMin, amountOut); @@ -514,13 +524,13 @@ contract LBRouter is ILBRouter { revert LBRouter__InvalidTokenPath(address(path.tokenPath[path.pairBinSteps.length])); } - address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); + address[] memory pairs = _getPairs(path.pairBinSteps, path.versions, path.tokenPath); uint256 balanceBefore = _wavax.balanceOf(address(this)); path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountIn); - _swapSupportingFeeOnTransferTokens(pairs, path.pairBinSteps, path.tokenPath, address(this)); + _swapSupportingFeeOnTransferTokens(pairs, path.versions, path.tokenPath, address(this)); amountOut = _wavax.balanceOf(address(this)) - balanceBefore; if (amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMinAVAX, amountOut); @@ -542,7 +552,7 @@ contract LBRouter is ILBRouter { ) external payable override ensure(deadline) verifyPathValidity(path) returns (uint256 amountOut) { if (path.tokenPath[0] != IERC20(_wavax)) revert LBRouter__InvalidTokenPath(address(path.tokenPath[0])); - address[] memory pairs = _getPairs(path.pairBinSteps, path.revisions, path.tokenPath); + address[] memory pairs = _getPairs(path.pairBinSteps, path.versions, path.tokenPath); IERC20 targetToken = path.tokenPath[pairs.length]; @@ -550,7 +560,7 @@ contract LBRouter is ILBRouter { _wavaxDepositAndTransfer(pairs[0], msg.value); - _swapSupportingFeeOnTransferTokens(pairs, path.pairBinSteps, path.tokenPath, to); + _swapSupportingFeeOnTransferTokens(pairs, path.versions, path.tokenPath, to); amountOut = targetToken.balanceOf(to) - balanceBefore; if (amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMin, amountOut); @@ -647,13 +657,12 @@ contract LBRouter is ILBRouter { } /// @notice Helper function to return the amounts in - /// @param pairBinSteps The bin step of the pairs (0: V1, other values will use V2) /// @param pairs The list of pairs /// @param tokenPath The swap path /// @param amountOut The amount out /// @return amountsIn The list of amounts in function _getAmountsIn( - uint256[] memory pairBinSteps, + Version[] memory versions, address[] memory pairs, IERC20[] memory tokenPath, uint256 amountOut @@ -664,10 +673,10 @@ contract LBRouter is ILBRouter { for (uint256 i = pairs.length; i != 0; i--) { IERC20 token = tokenPath[i - 1]; - uint256 binStep = pairBinSteps[i - 1]; + Version version = versions[i - 1]; address pair = pairs[i - 1]; - if (binStep == 0) { + if (version == Version.V1) { (uint256 reserveIn, uint256 reserveOut,) = IJoePair(pair).getReserves(); if (token > tokenPath[i]) { (reserveIn, reserveOut) = (reserveOut, reserveIn); @@ -675,6 +684,10 @@ contract LBRouter is ILBRouter { uint256 amountOut_ = amountsIn[i]; amountsIn[i - 1] = uint128(amountOut_.getAmountIn(reserveIn, reserveOut)); + } else if (version == Version.V2) { + (amountsIn[i - 1],) = _legacyRouter.getSwapIn( + ILBLegacyPair(pair), uint128(amountsIn[i]), ILBLegacyPair(pair).tokenX() == token + ); } else { (amountsIn[i - 1],,) = getSwapIn(ILBPair(pair), uint128(amountsIn[i]), ILBPair(pair).getTokenX() == token); @@ -714,19 +727,18 @@ contract LBRouter is ILBRouter { /// @notice Helper function to swap exact tokens for tokens /// @param amountIn The amount of token sent /// @param pairs The list of pairs - /// @param pairBinSteps The bin step of the pairs (0: V1, other values will use V2) /// @param tokenPath The swap path using the binSteps following `pairBinSteps` /// @param to The address of the recipient /// @return amountOut The amount of token sent to `to` function _swapExactTokensForTokens( uint256 amountIn, address[] memory pairs, - uint256[] memory pairBinSteps, + Version[] memory versions, IERC20[] memory tokenPath, address to ) private returns (uint256 amountOut) { IERC20 token; - uint256 binStep; + Version version; address recipient; address pair; @@ -736,14 +748,14 @@ contract LBRouter is ILBRouter { unchecked { for (uint256 i; i < pairs.length; ++i) { pair = pairs[i]; - binStep = pairBinSteps[i]; + version = versions[i]; token = tokenNext; tokenNext = tokenPath[i + 1]; recipient = i + 1 == pairs.length ? to : pairs[i + 1]; - if (binStep == 0) { + if (version == Version.V1) { (uint256 reserve0, uint256 reserve1,) = IJoePair(pair).getReserves(); if (token < tokenNext) { @@ -753,6 +765,13 @@ contract LBRouter is ILBRouter { amountOut = amountOut.getAmountOut(reserve1, reserve0); IJoePair(pair).swap(amountOut, 0, recipient, ""); } + } else if (version == Version.V2) { + bool swapForY = tokenNext == ILBLegacyPair(pair).tokenY(); + + (uint256 amountXOut, uint256 amountYOut) = ILBLegacyPair(pair).swap(swapForY, recipient); + + if (swapForY) amountOut = amountYOut; + else amountOut = amountXOut; } else { bool swapForY = tokenNext == ILBPair(pair).getTokenY(); @@ -767,42 +786,48 @@ contract LBRouter is ILBRouter { /// @notice Helper function to swap tokens for exact tokens /// @param pairs The array of pairs - /// @param pairBinSteps The bin step of the pairs (0: V1, other values will use V2) /// @param tokenPath The swap path using the binSteps following `pairBinSteps` /// @param amountsIn The list of amounts in /// @param to The address of the recipient /// @return amountOut The amount of token sent to `to` function _swapTokensForExactTokens( address[] memory pairs, - uint256[] memory pairBinSteps, + Version[] memory versions, IERC20[] memory tokenPath, uint256[] memory amountsIn, address to ) private returns (uint256 amountOut) { IERC20 token; - uint256 binStep; address recipient; address pair; + Version version; IERC20 tokenNext = tokenPath[0]; unchecked { for (uint256 i; i < pairs.length; ++i) { pair = pairs[i]; - binStep = pairBinSteps[i]; + version = versions[i]; token = tokenNext; tokenNext = tokenPath[i + 1]; recipient = i + 1 == pairs.length ? to : pairs[i + 1]; - if (binStep == 0) { + if (version == Version.V1) { amountOut = amountsIn[i + 1]; if (token < tokenNext) { IJoePair(pair).swap(0, amountOut, recipient, ""); } else { IJoePair(pair).swap(amountOut, 0, recipient, ""); } + } else if (version == Version.V2) { + bool swapForY = tokenNext == ILBLegacyPair(pair).tokenY(); + + (uint256 amountXOut, uint256 amountYOut) = ILBLegacyPair(pair).swap(swapForY, recipient); + + if (swapForY) amountOut = amountYOut; + else amountOut = amountXOut; } else { bool swapForY = tokenNext == ILBPair(pair).getTokenY(); @@ -817,17 +842,16 @@ contract LBRouter is ILBRouter { /// @notice Helper function to swap exact tokens supporting for fee on transfer tokens /// @param pairs The list of pairs - /// @param pairBinSteps The bin step of the pairs (0: V1, other values will use V2) /// @param tokenPath The swap path using the binSteps following `pairBinSteps` /// @param to The address of the recipient function _swapSupportingFeeOnTransferTokens( address[] memory pairs, - uint256[] memory pairBinSteps, + Version[] memory versions, IERC20[] memory tokenPath, address to ) private { IERC20 token; - uint256 binStep; + Version version; address recipient; address pair; @@ -836,14 +860,14 @@ contract LBRouter is ILBRouter { unchecked { for (uint256 i; i < pairs.length; ++i) { pair = pairs[i]; - binStep = pairBinSteps[i]; + version = versions[i]; token = tokenNext; tokenNext = tokenPath[i + 1]; recipient = i + 1 == pairs.length ? to : pairs[i + 1]; - if (binStep == 0) { + if (version == Version.V1) { (uint256 _reserve0, uint256 _reserve1,) = IJoePair(pair).getReserves(); if (token < tokenNext) { uint256 amountIn = token.balanceOf(pair) - _reserve0; @@ -856,6 +880,8 @@ contract LBRouter is ILBRouter { IJoePair(pair).swap(amountOut, 0, recipient, ""); } + } else if (version == Version.V2) { + ILBLegacyPair(pair).swap(tokenNext == ILBLegacyPair(pair).tokenY(), recipient); } else { ILBPair(pair).swap(tokenNext == ILBPair(pair).getTokenY(), recipient); } @@ -868,18 +894,21 @@ contract LBRouter is ILBRouter { /// @param tokenX The address of the tokenX /// @param tokenY The address of the tokenY /// @param binStep The bin step of the LBPair - /// @return The address of the LBPair - function _getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep, uint256 revision) + /// @return lbPair The address of the LBPair + function _getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep, Version version) private view - returns (ILBPair) + returns (address lbPair) { - ILBPair lbPair = _factory.getLBPairInformation(tokenX, tokenY, binStep, revision).LBPair; + if (version == Version.V2) { + lbPair = address(_legacyFactory.getLBPairInformation(tokenX, tokenY, binStep).LBPair); + } else { + lbPair = address(_factory.getLBPairInformation(tokenX, tokenY, binStep).LBPair); + } - if (address(lbPair) == address(0)) { + if (lbPair == address(0)) { revert LBRouter__PairNotCreated(address(tokenX), address(tokenY), binStep); } - return lbPair; } /// @notice Helper function to return the address of the pair (v1 or v2, according to `binStep`) @@ -888,20 +917,20 @@ contract LBRouter is ILBRouter { /// @param tokenX The address of the tokenX /// @param tokenY The address of the tokenY /// @return pair The address of the pair of binStep `binStep` - function _getPair(IERC20 tokenX, IERC20 tokenY, uint256 binStep, uint256 revision) + function _getPair(IERC20 tokenX, IERC20 tokenY, uint256 binStep, Version version) private view returns (address pair) { - if (binStep == 0) { - pair = _oldFactory.getPair(address(tokenX), address(tokenY)); + if (version == Version.V1) { + pair = _factoryV1.getPair(address(tokenX), address(tokenY)); if (pair == address(0)) revert LBRouter__PairNotCreated(address(tokenX), address(tokenY), binStep); } else { - pair = address(_getLBPairInformation(tokenX, tokenY, binStep, revision)); + pair = address(_getLBPairInformation(tokenX, tokenY, binStep, version)); } } - function _getPairs(uint256[] memory pairBinSteps, uint256[] memory revisions, IERC20[] memory tokenPath) + function _getPairs(uint256[] memory pairBinSteps, Version[] memory versions, IERC20[] memory tokenPath) private view returns (address[] memory pairs) @@ -915,7 +944,7 @@ contract LBRouter is ILBRouter { token = tokenNext; tokenNext = tokenPath[i + 1]; - pairs[i] = _getPair(token, tokenNext, pairBinSteps[i], revisions[i]); + pairs[i] = _getPair(token, tokenNext, pairBinSteps[i], versions[i]); } } } diff --git a/src/interfaces/ILBFactory.sol b/src/interfaces/ILBFactory.sol index 21bc3b63..8f9747af 100644 --- a/src/interfaces/ILBFactory.sol +++ b/src/interfaces/ILBFactory.sol @@ -26,8 +26,7 @@ interface ILBFactory is IPendingOwnable { error LBFactory__FlashLoanFeeAboveMax(uint256 fees, uint256 maxFees); error LBFactory__BinStepRequirementsBreached(uint256 lowerBound, uint16 binStep, uint256 higherBound); error LBFactory__ProtocolShareOverflows(uint16 protocolShare, uint256 max); - error LBFactory__FunctionIsLockedForUsers(address user); - error LBFactory__FactoryLockIsAlreadyInTheSameState(); + error LBFactory__FunctionIsLockedForUsers(address user, uint8 binStep); error LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); error LBFactory__BinStepHasNoPreset(uint256 binStep); error LBFactory__SameFeeRecipient(address feeRecipient); @@ -35,6 +34,7 @@ interface ILBFactory is IPendingOwnable { error LBFactory__LBPairSafetyCheckFailed(address LBPairImplementation); error LBFactory__SameImplementation(address LBPairImplementation); error LBFactory__ImplementationNotSet(); + error LBFactory__SamePresetOpenState(); /// @dev Structure to store the LBPair information, such as: /// - binStep: The bin step of the LBPair @@ -48,8 +48,6 @@ interface ILBFactory is IPendingOwnable { ILBPair LBPair; bool createdByOwner; bool ignoredForRouting; - uint16 revisionIndex; - address implementation; } event LBPairCreated( @@ -73,8 +71,6 @@ interface ILBFactory is IPendingOwnable { uint256 maxVolatilityAccumulator ); - event FactoryLockedStatusUpdated(bool unlocked); - event LBPairImplementationSet(address oldLBPairImplementation, address LBPairImplementation); event LBPairIgnoredStateChanged(ILBPair indexed LBPair, bool ignored); @@ -96,6 +92,8 @@ interface ILBFactory is IPendingOwnable { event QuoteAssetRemoved(IERC20 indexed quoteAsset); + event OpenPresetChanged(uint8 indexed binStep, bool open); + function getMaxFee() external pure returns (uint256); function getMinBinStep() external pure returns (uint256); @@ -116,15 +114,11 @@ interface ILBFactory is IPendingOwnable { function getFlashLoanFee() external view returns (uint256); - function isCreationUnlocked() external view returns (bool); - function getLBPairAtIndex(uint256 id) external returns (ILBPair); function getNumberOfLBPairs() external view returns (uint256); - function getNumberOfRevisions(IERC20 tokenX, IERC20 tokenY, uint256 binStep) external view returns (uint256); - - function getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep, uint256 revision) + function getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep) external view returns (LBPairInformation memory); @@ -155,9 +149,7 @@ interface ILBFactory is IPendingOwnable { external returns (ILBPair pair); - function createLBPairRevision(IERC20 tokenX, IERC20 tokenY, uint8 binStep) external returns (ILBPair pair); - - function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, uint256 revision, bool ignored) external; + function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) external; function setPreset( uint8 binStep, @@ -176,7 +168,6 @@ interface ILBFactory is IPendingOwnable { IERC20 tokenX, IERC20 tokenY, uint8 binStep, - uint16 revision, uint16 baseFactor, uint16 filterPeriod, uint16 decayPeriod, @@ -190,8 +181,6 @@ interface ILBFactory is IPendingOwnable { function setFlashLoanFee(uint256 flashLoanFee) external; - function setFactoryLockedState(bool locked) external; - function addQuoteAsset(IERC20 quoteAsset) external; function removeQuoteAsset(IERC20 quoteAsset) external; diff --git a/src/interfaces/ILBLegacyPair.sol b/src/interfaces/ILBLegacyPair.sol index 622e7828..64859c01 100644 --- a/src/interfaces/ILBLegacyPair.sol +++ b/src/interfaces/ILBLegacyPair.sol @@ -13,4 +13,6 @@ interface ILBLegacyPair { function tokenY() external view returns (IERC20); function getReservesAndId() external view returns (uint256 reserveX, uint256 reserveY, uint256 activeId); + + function swap(bool sentTokenY, address to) external returns (uint256 amountXOut, uint256 amountYOut); } diff --git a/src/interfaces/ILBRouter.sol b/src/interfaces/ILBRouter.sol index 0849c7a0..7e259df8 100644 --- a/src/interfaces/ILBRouter.sol +++ b/src/interfaces/ILBRouter.sol @@ -7,6 +7,7 @@ import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {IJoeFactory} from "./IJoeFactory.sol"; import {ILBFactory} from "./ILBFactory.sol"; import {ILBLegacyFactory} from "./ILBLegacyFactory.sol"; +import {ILBLegacyRouter} from "./ILBLegacyRouter.sol"; import {ILBPair} from "./ILBPair.sol"; import {ILBToken} from "./ILBToken.sol"; import {IWAVAX} from "./IWAVAX.sol"; @@ -40,6 +41,12 @@ interface ILBRouter { address tokenX, address tokenY, uint256 amountX, uint256 amountY, uint256 msgValue ); + enum Version { + V1, + V2, + V2_1 + } + /// @dev The liquidity parameters, such as: /// - tokenX: The address of token X /// - tokenY: The address of token Y @@ -60,7 +67,6 @@ interface ILBRouter { IERC20 tokenX; IERC20 tokenY; uint256 binStep; - uint256 revision; uint256 amountX; uint256 amountY; uint256 amountXMin; @@ -81,7 +87,7 @@ interface ILBRouter { /// - tokenPath: The list of tokens in the path to go through struct Path { uint256[] pairBinSteps; - uint256[] revisions; + Version[] versions; IERC20[] tokenPath; } @@ -89,7 +95,9 @@ interface ILBRouter { function getLegacyFactory() external view returns (ILBLegacyFactory); - function getOldFactory() external view returns (IJoeFactory); + function getV1Factory() external view returns (IJoeFactory); + + function getLegacyRouter() external view returns (ILBLegacyRouter); function getWAVAX() external view returns (IWAVAX); @@ -138,7 +146,6 @@ interface ILBRouter { IERC20 tokenX, IERC20 tokenY, uint8 binStep, - uint256 revision, uint256 amountXMin, uint256 amountYMin, uint256[] memory ids, @@ -150,7 +157,6 @@ interface ILBRouter { function removeLiquidityAVAX( IERC20 token, uint8 binStep, - uint256 revision, uint256 amountTokenMin, uint256 amountAVAXMin, uint256[] memory ids, diff --git a/test/LBFactory.t.sol b/test/LBFactory.t.sol index c6222d1c..c3299c23 100644 --- a/test/LBFactory.t.sol +++ b/test/LBFactory.t.sol @@ -65,6 +65,8 @@ contract LiquidityBinFactoryTest is TestHelper { event FactoryLockedStatusUpdated(bool unlocked); + event OpenPresetChanged(uint8 indexed binStep, bool open); + struct LBPairInformation { uint256 binStep; ILBPair LBPair; @@ -129,7 +131,7 @@ contract LiquidityBinFactoryTest is TestHelper { address expectedPairAddress = ImmutableClone.predictDeterministicAddress( address(pairImplementation), abi.encodePacked(usdt, usdc, DEFAULT_BIN_STEP), - keccak256(abi.encode(usdc, usdt, DEFAULT_BIN_STEP, 1)), + keccak256(abi.encode(usdc, usdt, DEFAULT_BIN_STEP)), address(factory) ); @@ -154,15 +156,12 @@ contract LiquidityBinFactoryTest is TestHelper { assertEq(factory.getNumberOfLBPairs(), 1, "test_createLBPair::1"); - LBFactory.LBPairInformation memory pairInfo = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 1); + LBFactory.LBPairInformation memory pairInfo = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP); assertEq(pairInfo.binStep, DEFAULT_BIN_STEP, "test_createLBPair::2"); assertEq(address(pairInfo.LBPair), address(pair), "test_createLBPair::2"); assertTrue(pairInfo.createdByOwner); assertFalse(pairInfo.ignoredForRouting); - assertEq(pairInfo.revisionIndex, 1); - assertEq(pairInfo.implementation, address(pairImplementation), "test_createLBPair::2"); - assertEq(factory.getNumberOfRevisions(usdt, usdc, DEFAULT_BIN_STEP), 1, "test_createLBPair::3"); assertEq(factory.getAllLBPairs(usdt, usdc).length, 1, "test_createLBPair::4"); assertEq(address(factory.getAllLBPairs(usdt, usdc)[0].LBPair), address(pair), "test_createLBPair::5"); @@ -184,28 +183,37 @@ contract LiquidityBinFactoryTest is TestHelper { } function test_createLBPairFactoryUnlocked() public { - factory.setFactoryLockedState(false); + // Users should not be able to create pairs by default + vm.prank(ALICE); + vm.expectRevert( + abi.encodeWithSelector(ILBFactory.LBFactory__FunctionIsLockedForUsers.selector, ALICE, DEFAULT_BIN_STEP) + ); + factory.createLBPair(link, usdc, ID_ONE, DEFAULT_BIN_STEP); + + factory.setOpenPreset(DEFAULT_BIN_STEP, true); // Any user should be able to create pairs vm.prank(ALICE); factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); - assertFalse(factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 1).createdByOwner); + assertFalse(factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP).createdByOwner); vm.prank(BOB); factory.createLBPair(weth, usdc, ID_ONE, DEFAULT_BIN_STEP); - assertFalse(factory.getLBPairInformation(weth, usdc, DEFAULT_BIN_STEP, 1).createdByOwner); + assertFalse(factory.getLBPairInformation(weth, usdc, DEFAULT_BIN_STEP).createdByOwner); factory.createLBPair(bnb, usdc, ID_ONE, DEFAULT_BIN_STEP); - assertTrue(factory.getLBPairInformation(bnb, usdc, DEFAULT_BIN_STEP, 1).createdByOwner); + assertTrue(factory.getLBPairInformation(bnb, usdc, DEFAULT_BIN_STEP).createdByOwner); // Should close pair creations again - factory.setFactoryLockedState(true); + factory.setOpenPreset(DEFAULT_BIN_STEP, false); vm.prank(ALICE); - vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__FunctionIsLockedForUsers.selector, ALICE)); + vm.expectRevert( + abi.encodeWithSelector(ILBFactory.LBFactory__FunctionIsLockedForUsers.selector, ALICE, DEFAULT_BIN_STEP) + ); factory.createLBPair(link, usdc, ID_ONE, DEFAULT_BIN_STEP); factory.createLBPair(wbtc, usdc, ID_ONE, DEFAULT_BIN_STEP); @@ -214,7 +222,9 @@ contract LiquidityBinFactoryTest is TestHelper { function test_reverts_createLBPair() public { // Alice can't create a pair if the factory is locked vm.prank(ALICE); - vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__FunctionIsLockedForUsers.selector, ALICE)); + vm.expectRevert( + abi.encodeWithSelector(ILBFactory.LBFactory__FunctionIsLockedForUsers.selector, ALICE, DEFAULT_BIN_STEP) + ); factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); // Can't create pair if the implementation is not set @@ -263,119 +273,25 @@ contract LiquidityBinFactoryTest is TestHelper { newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); } - function test_CreateRevision() public { - ILBPair pair = factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); - - // Updates the pair implementation - pairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(pairImplementation)); - - address expectedPairAddress = ImmutableClone.predictDeterministicAddress( - address(pairImplementation), - abi.encodePacked(usdt, usdc, DEFAULT_BIN_STEP), - keccak256(abi.encode(usdc, usdt, DEFAULT_BIN_STEP, 2)), - address(factory) - ); - - // Check for the correct events - vm.expectEmit(true, true, true, true); - emit LBPairCreated(usdt, usdc, DEFAULT_BIN_STEP, ILBPair(expectedPairAddress), 1); - - // vm.expectEmit(true, true, true, true, expectedPairAddress); - // emit StaticFeeParametersSet( - // address(factory), - // DEFAULT_BASE_FACTOR, - // DEFAULT_FILTER_PERIOD, - // DEFAULT_DECAY_PERIOD, - // DEFAULT_REDUCTION_FACTOR, - // DEFAULT_VARIABLE_FEE_CONTROL, - // DEFAULT_PROTOCOL_SHARE, - // DEFAULT_MAX_VOLATILITY_ACCUMULATOR - // ); - - ILBPair revision = factory.createLBPairRevision(usdt, usdc, DEFAULT_BIN_STEP); - - assertEq(factory.getNumberOfLBPairs(), 2, "test_createLBPair::1"); - - LBFactory.LBPairInformation memory pairInfo = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 2); - assertEq(pairInfo.binStep, DEFAULT_BIN_STEP, "test_createLBPair::2"); - assertEq(address(pairInfo.LBPair), address(revision), "test_createLBPair::2"); - assertTrue(pairInfo.createdByOwner); - assertFalse(pairInfo.ignoredForRouting); - assertEq(pairInfo.revisionIndex, 2); - assertEq(pairInfo.implementation, address(pairImplementation), "test_createLBPair::2"); - - // Revision and previous pair should have active bin - uint256 pairActiveId = pair.getActiveId(); - uint256 revisionActiveId = revision.getActiveId(); - assertEq(pairActiveId, revisionActiveId); - - assertEq(factory.getNumberOfRevisions(usdt, usdc, DEFAULT_BIN_STEP), 2, "test_createLBPair::3"); - assertEq(factory.getAllLBPairs(usdt, usdc).length, 2, "test_createLBPair::4"); - assertEq(address(factory.getAllLBPairs(usdt, usdc)[0].LBPair), address(pair), "test_createLBPair::5"); - assertEq(address(factory.getAllLBPairs(usdt, usdc)[1].LBPair), address(revision), "test_createLBPair::5"); - - assertEq(address(revision.getFactory()), address(factory), "test_createLBPair::6"); - assertEq(address(revision.getTokenX()), address(usdt), "test_createLBPair::7"); - assertEq(address(revision.getTokenY()), address(usdc), "test_createLBPair::8"); - - // FeeHelper.FeeParameters memory feeParameters = revision.feeParameters(); - // assertEq(feeParameters.volatilityAccumulator, 0, "test_createLBPair::9"); - // assertEq(feeParameters.volatilityReference, 0, "test_createLBPair::10"); - // assertEq(feeParameters.indexRef, 0, "test_createLBPair::11"); - // assertEq(feeParameters.time, 0, "test_createLBPair::12"); - // assertEq(feeParameters.maxVolatilityAccumulator, DEFAULT_MAX_VOLATILITY_ACCUMULATOR, "test_createLBPair::13"); - // assertEq(feeParameters.filterPeriod, DEFAULT_FILTER_PERIOD, "test_createLBPair::14"); - // assertEq(feeParameters.decayPeriod, DEFAULT_DECAY_PERIOD, "test_createLBPair::15"); - // assertEq(feeParameters.binStep, DEFAULT_BIN_STEP, "test_createLBPair::16"); - // assertEq(feeParameters.baseFactor, DEFAULT_BASE_FACTOR, "test_createLBPair::17"); - // assertEq(feeParameters.protocolShare, DEFAULT_PROTOCOL_SHARE, "test_createLBPair::18"); - } - - function test_reverts_CreateRevision() public { - // Can't create a revision if not the owner - vm.prank(ALICE); - vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); - factory.createLBPairRevision(usdt, usdc, DEFAULT_BIN_STEP); - - // Can't create a revision if the pair doesn't exist - vm.expectRevert( - abi.encodeWithSelector(ILBFactory.LBFactory__LBPairDoesNotExists.selector, usdt, usdc, DEFAULT_BIN_STEP) - ); - factory.createLBPairRevision(usdt, usdc, DEFAULT_BIN_STEP); - - factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); - - // Can't create a revision if the pair implementation hasn't changed - vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__SameImplementation.selector, pairImplementation)); - factory.createLBPairRevision(usdt, usdc, DEFAULT_BIN_STEP); - } - function test_setLBPairIgnored() public { - factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); - factory.createLBPair(weth, usdc, ID_ONE, DEFAULT_BIN_STEP); - factory.setLBPairImplementation(address(new LBPair(factory))); - ILBPair revision2 = ILBPair(factory.createLBPairRevision(usdt, usdc, DEFAULT_BIN_STEP)); - factory.setLBPairImplementation(address(new LBPair(factory))); - factory.createLBPairRevision(usdt, usdc, DEFAULT_BIN_STEP); + ILBPair pair = factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); // Ignoring the USDT-USDC rev 2 pair vm.expectEmit(true, true, true, true); - emit LBPairIgnoredStateChanged(revision2, true); - factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 2, true); + emit LBPairIgnoredStateChanged(pair, true); + factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, true); - ILBFactory.LBPairInformation memory revision2Info = - factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 2); - assertEq(address(revision2Info.LBPair), address(revision2), "test_setLBPairIgnored::0"); - assertEq(revision2Info.ignoredForRouting, true, "test_setLBPairIgnored::1"); + ILBFactory.LBPairInformation memory pairInfo = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP); + assertEq(address(pairInfo.LBPair), address(pair), "test_setLBPairIgnored::0"); + assertEq(pairInfo.ignoredForRouting, true, "test_setLBPairIgnored::1"); // Put it back to normal vm.expectEmit(true, true, true, true); - emit LBPairIgnoredStateChanged(revision2, false); - factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 2, false); + emit LBPairIgnoredStateChanged(pair, false); + factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, false); assertEq( - factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 2).ignoredForRouting, + factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP).ignoredForRouting, false, "test_setLBPairIgnored::1" ); @@ -385,22 +301,22 @@ contract LiquidityBinFactoryTest is TestHelper { // Can't ignore for routing if not the owner vm.prank(ALICE); vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); - factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 1, true); + factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, true); // Can't update a non existing pair vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__AddressZero.selector)); - factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 1, true); + factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, true); factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); // Can't update a pair to the same state vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__LBPairIgnoredIsAlreadyInTheSameState.selector)); - factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 1, false); + factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, false); - factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 1, true); + factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, true); vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__LBPairIgnoredIsAlreadyInTheSameState.selector)); - factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, 1, true); + factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, true); } function todoTestFuzz_setPreset( @@ -686,7 +602,6 @@ contract LiquidityBinFactoryTest is TestHelper { usdt, usdc, DEFAULT_BIN_STEP, - 1, newBaseFactor, newFilterPeriod, newDecayPeriod, @@ -719,7 +634,6 @@ contract LiquidityBinFactoryTest is TestHelper { usdt, usdc, DEFAULT_BIN_STEP, - 1, newBaseFactor, newFilterPeriod, newDecayPeriod, @@ -731,13 +645,12 @@ contract LiquidityBinFactoryTest is TestHelper { // Can't update a pair that does not exist vm.expectRevert( - abi.encodeWithSelector(ILBFactory.LBFactory__LBPairNotCreated.selector, usdc, weth, DEFAULT_BIN_STEP) + abi.encodeWithSelector(ILBFactory.LBFactory__LBPairNotCreated.selector, weth, usdc, DEFAULT_BIN_STEP) ); factory.setFeesParametersOnPair( weth, usdc, DEFAULT_BIN_STEP, - 1, newBaseFactor, newFilterPeriod, newDecayPeriod, @@ -796,26 +709,60 @@ contract LiquidityBinFactoryTest is TestHelper { factory.setFlashLoanFee(maxFlashLoanFee + 1); } - function test_setFactoryLockedState() public { - assertEq(factory.isCreationUnlocked(), false); + function testFuzz_openPresets(uint8 binStep) public { + uint256 minBinStep = factory.getMinBinStep(); + uint256 maxBinStep = factory.getMaxBinStep(); + + binStep = uint8(bound(binStep, minBinStep, maxBinStep)); + + // Preset are not open to the public by default + assertFalse(factory.getIsPresetOpen(binStep)); + // Can be opened vm.expectEmit(true, true, true, true); - emit FactoryLockedStatusUpdated(false); - factory.setFactoryLockedState(false); + emit OpenPresetChanged(binStep, true); + factory.setOpenPreset(binStep, true); + + for (uint256 i = minBinStep; i < maxBinStep; i++) { + if (i == binStep) { + assertTrue(factory.getIsPresetOpen(uint8(i))); + } else { + assertFalse(factory.getIsPresetOpen(uint8(i))); + } + } - assertEq(factory.isCreationUnlocked(), true); + // Setting neighboring presets to true does not change the state of the preset + uint8 nextBinStep = uint8(bound(binStep + 1, minBinStep, maxBinStep)); + uint8 previousBinStep = uint8(bound(binStep + maxBinStep - minBinStep - 1, minBinStep, maxBinStep)); - factory.setFactoryLockedState(true); - assertEq(factory.isCreationUnlocked(), false); + factory.setOpenPreset(nextBinStep, true); + factory.setOpenPreset(previousBinStep, true); - // Can't set if not the owner + assertTrue(factory.getIsPresetOpen(binStep)); + assertTrue(factory.getIsPresetOpen(nextBinStep)); + assertTrue(factory.getIsPresetOpen(previousBinStep)); + + // Can't set to the same state + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__SamePresetOpenState.selector)); + factory.setOpenPreset(binStep, true); + + // Can be closed + vm.expectEmit(true, true, true, true); + emit OpenPresetChanged(binStep, false); + factory.setOpenPreset(binStep, false); + + assertFalse(factory.getIsPresetOpen(binStep)); + assertTrue(factory.getIsPresetOpen(nextBinStep)); + assertTrue(factory.getIsPresetOpen(previousBinStep)); + + // Can't open if not the owner vm.prank(ALICE); vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); - factory.setFactoryLockedState(true); + factory.setOpenPreset(binStep, true); // Can't set to the same state - vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__FactoryLockIsAlreadyInTheSameState.selector)); - factory.setFactoryLockedState(true); + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__SamePresetOpenState.selector)); + factory.setOpenPreset(binStep, false); } function test_addQuoteAsset() public { @@ -880,7 +827,6 @@ contract LiquidityBinFactoryTest is TestHelper { /* Create pairs: - WETH/USDC with bin step = 5 - WETH/USDC with bin step = 20 - - WETH/USDC revision with bin step = 5 - USDT/USDC with bin step = 5 */ @@ -908,31 +854,18 @@ contract LiquidityBinFactoryTest is TestHelper { ILBPair pair1 = factory.createLBPair(weth, usdc, ID_ONE, 5); ILBPair pair2 = factory.createLBPair(weth, usdc, ID_ONE, 20); - - factory.setLBPairImplementation(address(new LBPair(factory))); - ILBPair pair3 = factory.createLBPairRevision(weth, usdc, 5); factory.createLBPair(usdt, usdc, ID_ONE, 5); ILBFactory.LBPairInformation[] memory LBPairsAvailable = factory.getAllLBPairs(weth, usdc); - assertEq(LBPairsAvailable.length, 3); + assertEq(LBPairsAvailable.length, 2); ILBFactory.LBPairInformation memory pair1Info = LBPairsAvailable[0]; assertEq(address(pair1Info.LBPair), address(pair1)); assertEq(pair1Info.binStep, 5); - assertEq(pair1Info.revisionIndex, 1); - assertEq(pair1Info.implementation, address(pairImplementation)); - ILBFactory.LBPairInformation memory pair2Info = LBPairsAvailable[2]; + ILBFactory.LBPairInformation memory pair2Info = LBPairsAvailable[1]; assertEq(address(pair2Info.LBPair), address(pair2)); assertEq(pair2Info.binStep, 20); - assertEq(pair2Info.revisionIndex, 1); - assertEq(pair2Info.implementation, address(pairImplementation)); - - ILBFactory.LBPairInformation memory pair3Info = LBPairsAvailable[1]; - assertEq(address(pair3Info.LBPair), address(pair3)); - assertEq(pair3Info.binStep, 5); - assertEq(pair3Info.revisionIndex, 2); - assertEq(pair3Info.implementation, address(factory.getLBPairImplementation())); } } diff --git a/test/LBRouter.Liquidity.t.sol b/test/LBRouter.Liquidity.t.sol index 6aea9cc4..08e15b91 100644 --- a/test/LBRouter.Liquidity.t.sol +++ b/test/LBRouter.Liquidity.t.sol @@ -21,7 +21,7 @@ contract LiquidityBinRouterTest is TestHelper { function setUp() public override { super.setUp(); - factory.setFactoryLockedState(false); + factory.setOpenPreset(DEFAULT_BIN_STEP, true); // Create necessary pairs router.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); @@ -51,10 +51,12 @@ contract LiquidityBinRouterTest is TestHelper { function test_CreatePair() public { router.createLBPair(weth, usdc, ID_ONE, DEFAULT_BIN_STEP); - factory.setFactoryLockedState(true); + factory.setOpenPreset(DEFAULT_BIN_STEP, false); vm.expectRevert( - abi.encodeWithSelector(ILBFactory.LBFactory__FunctionIsLockedForUsers.selector, address(router)) + abi.encodeWithSelector( + ILBFactory.LBFactory__FunctionIsLockedForUsers.selector, address(router), DEFAULT_BIN_STEP + ) ); router.createLBPair(bnb, usdc, ID_ONE, DEFAULT_BIN_STEP); } @@ -289,12 +291,12 @@ contract LiquidityBinRouterTest is TestHelper { (uint256 amountXAdded, uint256 amountYAdded,,, uint256[] memory depositIds, uint256[] memory liquidityMinted) = router.addLiquidity(liquidityParameters); - ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 1).LBPair; + ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP).LBPair; pair.setApprovalForAll(address(router), true); (uint256 amountXOut, uint256 amountYOut) = router.removeLiquidity( - usdt, usdc, DEFAULT_BIN_STEP, 1, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp + usdt, usdc, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp ); assertApproxEqAbs(amountXOut, amountXAdded, 10, "amountXOut"); @@ -311,7 +313,7 @@ contract LiquidityBinRouterTest is TestHelper { (amountXAdded, amountYAdded,,, depositIds, liquidityMinted) = router.addLiquidity(liquidityParameters); (amountYOut, amountXOut) = router.removeLiquidity( - usdc, usdt, DEFAULT_BIN_STEP, 1, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp + usdc, usdt, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp ); assertApproxEqAbs(amountXOut, amountXAdded, 10, "amountXOut"); @@ -325,7 +327,7 @@ contract LiquidityBinRouterTest is TestHelper { } (amountXOut, amountYOut) = router.removeLiquidity( - usdt, usdc, DEFAULT_BIN_STEP, 1, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp + usdt, usdc, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp ); assertApproxEqAbs(amountXOut, amountXAdded / 2, 10, "amountXOut"); @@ -344,7 +346,7 @@ contract LiquidityBinRouterTest is TestHelper { (uint256 amountXAdded, uint256 amountYAdded,,, uint256[] memory depositIds, uint256[] memory liquidityMinted) = router.addLiquidity(liquidityParameters); - ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 1).LBPair; + ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP).LBPair; pair.setApprovalForAll(address(router), true); // Revert if the deadline is passed @@ -352,7 +354,7 @@ contract LiquidityBinRouterTest is TestHelper { abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) ); router.removeLiquidity( - usdt, usdc, DEFAULT_BIN_STEP, 1, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp - 1 + usdt, usdc, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp - 1 ); // Revert if the slippage is caught @@ -365,7 +367,6 @@ contract LiquidityBinRouterTest is TestHelper { usdt, usdc, DEFAULT_BIN_STEP, - 1, amountXAdded + 1, 0, depositIds, @@ -383,7 +384,6 @@ contract LiquidityBinRouterTest is TestHelper { usdt, usdc, DEFAULT_BIN_STEP, - 1, 0, amountYAdded + 1, depositIds, @@ -405,14 +405,14 @@ contract LiquidityBinRouterTest is TestHelper { (uint256 amountXAdded, uint256 amountYAdded,,, uint256[] memory depositIds, uint256[] memory liquidityMinted) = router.addLiquidityAVAX{value: liquidityParameters.amountX}(liquidityParameters); - ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP, 1).LBPair; + ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP).LBPair; pair.setApprovalForAll(address(router), true); uint256 balanceAVAXBefore = address(this).balance; uint256 balanceUSDCBefore = usdc.balanceOf(address(this)); (uint256 amountToken, uint256 amountAVAX) = router.removeLiquidityAVAX( - usdc, DEFAULT_BIN_STEP, 1, 0, 0, depositIds, liquidityMinted, payable(address(this)), block.timestamp + usdc, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, payable(address(this)), block.timestamp ); assertApproxEqAbs(amountAVAX, amountXAdded, 10, "amountXOut"); @@ -434,7 +434,7 @@ contract LiquidityBinRouterTest is TestHelper { (uint256 amountXAdded,,,, uint256[] memory depositIds, uint256[] memory liquidityMinted) = router.addLiquidityAVAX{value: liquidityParameters.amountX}(liquidityParameters); - ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP, 1).LBPair; + ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP).LBPair; pair.setApprovalForAll(address(router), true); // Revert if the deadline is passed @@ -442,7 +442,7 @@ contract LiquidityBinRouterTest is TestHelper { abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) ); router.removeLiquidityAVAX( - usdc, DEFAULT_BIN_STEP, 1, 0, 0, depositIds, liquidityMinted, payable(address(this)), block.timestamp - 1 + usdc, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, payable(address(this)), block.timestamp - 1 ); // Revert if the contract does not have a receive function @@ -451,7 +451,7 @@ contract LiquidityBinRouterTest is TestHelper { abi.encodeWithSelector(ILBRouter.LBRouter__FailedToSendAVAX.selector, address(this), amountXAdded - 2) ); router.removeLiquidityAVAX( - usdc, DEFAULT_BIN_STEP, 1, 0, 0, depositIds, liquidityMinted, payable(address(this)), block.timestamp + usdc, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, payable(address(this)), block.timestamp ); } @@ -477,7 +477,7 @@ contract LiquidityBinRouterTest is TestHelper { liquidityParameters.to = address(router); (,,,, uint256[] memory depositIds, uint256[] memory liquidityMinted) = router.addLiquidity(liquidityParameters); - ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 1).LBPair; + ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP).LBPair; uint256[] memory balancesBefore = new uint256[](depositIds.length); for (uint256 i = 0; i < depositIds.length; i++) { diff --git a/test/LBRouter.Swap.t.sol b/test/LBRouter.Swap.t.sol index 7bb3dd91..94b3c3be 100644 --- a/test/LBRouter.Swap.t.sol +++ b/test/LBRouter.Swap.t.sol @@ -21,7 +21,7 @@ contract LiquidityBinRouterSwapTest is TestHelper { function setUp() public override { super.setUp(); - factory.setFactoryLockedState(false); + factory.setOpenPreset(DEFAULT_BIN_STEP, true); // Create necessary pairs router.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); @@ -47,7 +47,7 @@ contract LiquidityBinRouterSwapTest is TestHelper { function test_SwapExactTokensForTokens() public { uint128 amountIn = 20e18; - ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 1).LBPair; + ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP).LBPair; (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, true); ILBRouter.Path memory path = _buildPath(usdt, usdc); @@ -84,7 +84,7 @@ contract LiquidityBinRouterSwapTest is TestHelper { function test_SwapExactTokensForAVAX() public { uint128 amountIn = 20e18; - ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP, 1).LBPair; + ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP).LBPair; (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, false); ILBRouter.Path memory path = _buildPath(usdc, wavax); @@ -131,7 +131,7 @@ contract LiquidityBinRouterSwapTest is TestHelper { function test_SwapExactAVAXForTokens() public { uint128 amountIn = 20e18; - ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP, 1).LBPair; + ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP).LBPair; (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, true); ILBRouter.Path memory path = _buildPath(wavax, usdc); @@ -174,7 +174,7 @@ contract LiquidityBinRouterSwapTest is TestHelper { function test_SwapTokensForExactTokens() public { uint128 amountOut = 20e18; - ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP, 1).LBPair; + ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP).LBPair; (uint128 amountInExpected,,) = router.getSwapIn(pair, amountOut, true); ILBRouter.Path memory path = _buildPath(usdt, usdc); @@ -214,7 +214,7 @@ contract LiquidityBinRouterSwapTest is TestHelper { function test_SwapTokensForExactAVAX() public { uint128 amountOut = 20e18; - ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP, 1).LBPair; + ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP).LBPair; (uint128 amountInExpected,,) = router.getSwapIn(pair, amountOut, false); ILBRouter.Path memory path = _buildPath(usdc, wavax); @@ -260,7 +260,7 @@ contract LiquidityBinRouterSwapTest is TestHelper { function test_SwapAVAXForExactTokens() public { uint128 amountOut = 20e18; - ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP, 1).LBPair; + ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP).LBPair; (uint128 amountInExpected,,) = router.getSwapIn(pair, amountOut, true); ILBRouter.Path memory path = _buildPath(wavax, usdc); @@ -307,7 +307,7 @@ contract LiquidityBinRouterSwapTest is TestHelper { function test_swapExactTokensForAVAXSupportingFeeOnTransferTokens() public { uint128 amountIn = 20e18; - ILBPair pair = factory.getLBPairInformation(taxToken, wavax, DEFAULT_BIN_STEP, 1).LBPair; + ILBPair pair = factory.getLBPairInformation(taxToken, wavax, DEFAULT_BIN_STEP).LBPair; (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, true); ILBRouter.Path memory path = _buildPath(taxToken, wavax); @@ -358,7 +358,7 @@ contract LiquidityBinRouterSwapTest is TestHelper { function test_SwapExactTokensForAVAXSupportingFeeOnTransferTokens() public { uint128 amountIn = 20e18; - ILBPair pair = factory.getLBPairInformation(taxToken, wavax, DEFAULT_BIN_STEP, 1).LBPair; + ILBPair pair = factory.getLBPairInformation(taxToken, wavax, DEFAULT_BIN_STEP).LBPair; (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, true); ILBRouter.Path memory path = _buildPath(taxToken, wavax); @@ -408,7 +408,7 @@ contract LiquidityBinRouterSwapTest is TestHelper { function test_SwapExactAVAXForTokensSupportingFeeOnTransferTokens() public { uint128 amountIn = 20e18; - ILBPair pair = factory.getLBPairInformation(taxToken, wavax, DEFAULT_BIN_STEP, 1).LBPair; + ILBPair pair = factory.getLBPairInformation(taxToken, wavax, DEFAULT_BIN_STEP).LBPair; (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, false); ILBRouter.Path memory path = _buildPath(wavax, taxToken); @@ -459,8 +459,8 @@ contract LiquidityBinRouterSwapTest is TestHelper { path.pairBinSteps = new uint256[](1); path.pairBinSteps[0] = DEFAULT_BIN_STEP; - path.revisions = new uint256[](1); - path.revisions[0] = 1; + path.versions = new ILBRouter.Version[](1); + path.versions[0] = ILBRouter.Version.V2_1; path.tokenPath = new IERC20[](2); path.tokenPath[0] = tokenIn; diff --git a/test/helpers/TestHelper.sol b/test/helpers/TestHelper.sol index 4091a961..ac9a5782 100644 --- a/test/helpers/TestHelper.sol +++ b/test/helpers/TestHelper.sol @@ -126,7 +126,7 @@ abstract contract TestHelper is Test { setDefaultFactoryPresets(DEFAULT_BIN_STEP); // Create router - router = new LBRouter(factory, legacyFactoryV2, factoryV1, IWAVAX(address(wavax))); + router = new LBRouter(factory, factoryV1, legacyFactoryV2, legacyRouterV2, IWAVAX(address(wavax))); // Create quoter quoter = @@ -239,7 +239,6 @@ abstract contract TestHelper is Test { tokenX: tokenX, tokenY: tokenY, binStep: DEFAULT_BIN_STEP, - revision: 1, amountX: amountXIn, amountY: amountYIn, amountXMin: 0, diff --git a/test/integration/LBQuoter.t.sol b/test/integration/LBQuoter.t.sol index c69b28a7..29e3d22e 100644 --- a/test/integration/LBQuoter.t.sol +++ b/test/integration/LBQuoter.t.sol @@ -8,7 +8,7 @@ import "../helpers/TestHelper.sol"; * Market deployed: * - USDT/USDC, V1 with low liquidity, V2 with high liquidity * - WAVAX/USDC, V1 with high liquidity, V2 with low liquidity -* - WETH/USDC, V1 with low liquidity, V2.1.rev1 with low liquidity, V2.1.rev2 with high liquidity +* - WETH/USDC, V1 with low liquidity, V2.1 with high liquidity * - BNB/USDC, V2 with high liquidity, V2.1 with low liquidity * * Every market with low liquidity has a slighly higher price. @@ -78,8 +78,6 @@ contract LiquidityBinQuoterTest is TestHelper { factory.createLBPair(weth, usdc, ID_ONE, DEFAULT_BIN_STEP); // 1 WETH = 1 USDC factory.createLBPair(bnb, usdc, ID_ONE + 50, DEFAULT_BIN_STEP); // 1 BNB > 1 USDC - factory.setLBPairImplementation(address(new LBPair(factory))); - factory.createLBPairRevision(weth, usdc, DEFAULT_BIN_STEP); // 1 WETH = 1 USDC // Add liquidity to V2 ILBRouter.LiquidityParameters memory liquidityParameters = @@ -89,11 +87,7 @@ contract LiquidityBinQuoterTest is TestHelper { liquidityParameters = getLiquidityParameters(wavax, usdc, lowLiquidityAmount, ID_ONE + 50, 7, 0); legacyRouterV2.addLiquidity(liquidityParameters.toLegacy()); - liquidityParameters = getLiquidityParameters(weth, usdc, lowLiquidityAmount, ID_ONE, 7, 0); - router.addLiquidity(liquidityParameters); - liquidityParameters = getLiquidityParameters(weth, usdc, highLiquidityAmount, ID_ONE, 7, 0); - liquidityParameters.revision = 2; router.addLiquidity(liquidityParameters); liquidityParameters = getLiquidityParameters(bnb, usdc, highLiquidityAmount, ID_ONE, 7, 0); @@ -132,7 +126,7 @@ contract LiquidityBinQuoterTest is TestHelper { assertEq(quote.amounts[0], amountIn, "test_Scenario1::1"); assertApproxEqRel(quote.amounts[1], amountIn * 2, 5e16, "test_Scenario1::2"); assertEq(quote.binSteps[0], 0, "test_Scenario1::3"); - assertEq(quote.revisions[0], 0, "test_Scenario1::4"); + assertEq(uint256(quote.versions[0]), 0, "test_Scenario1::4"); // Large amountIn amountIn = 100e18; @@ -141,7 +135,7 @@ contract LiquidityBinQuoterTest is TestHelper { assertEq(quote.amounts[0], amountIn, "test_Scenario1::5"); assertApproxEqRel(quote.amounts[1], amountIn, 5e16, "test_Scenario1::6"); assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario1::7"); - assertEq(quote.revisions[0], 0, "test_Scenario1::8"); + assertEq(uint256(quote.versions[0]), 1, "test_Scenario1::8"); // Small amountOut uint128 amountOut = 1e16; @@ -150,7 +144,7 @@ contract LiquidityBinQuoterTest is TestHelper { assertApproxEqRel(quote.amounts[0], amountOut / 2, 5e16, "test_Scenario1::9"); assertEq(quote.amounts[1], amountOut, "test_Scenario1::10"); assertEq(quote.binSteps[0], 0, "test_Scenario1::11"); - assertEq(quote.revisions[0], 0, "test_Scenario1::12"); + assertEq(uint256(quote.versions[0]), 0, "test_Scenario1::12"); // Large amountOut amountOut = 100e18; @@ -159,7 +153,7 @@ contract LiquidityBinQuoterTest is TestHelper { assertApproxEqRel(quote.amounts[0], amountOut, 5e16, "test_Scenario1::13"); assertEq(quote.amounts[1], amountOut, "test_Scenario1::14"); assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario1::15"); - assertEq(quote.revisions[0], 0, "test_Scenario1::16"); + assertEq(uint256(quote.versions[0]), 1, "test_Scenario1::16"); } function test_Scenario2() public { @@ -175,7 +169,7 @@ contract LiquidityBinQuoterTest is TestHelper { assertEq(quote.amounts[0], amountIn, "test_Scenario2::1"); assertGt(quote.amounts[1], amountIn, "test_Scenario2::2"); assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario2::3"); - assertEq(quote.revisions[0], 0, "test_Scenario2::4"); + assertEq(uint256(quote.versions[0]), 1, "test_Scenario2::4"); // Large amountIn amountIn = 100e18; @@ -184,7 +178,7 @@ contract LiquidityBinQuoterTest is TestHelper { assertEq(quote.amounts[0], amountIn, "test_Scenario2::5"); assertApproxEqRel(quote.amounts[1], amountIn, 5e16, "test_Scenario2::6"); assertEq(quote.binSteps[0], 0, "test_Scenario2::7"); - assertEq(quote.revisions[0], 0, "test_Scenario2::8"); + assertEq(uint256(quote.versions[0]), 0, "test_Scenario2::8"); // Small amountOut uint128 amountOut = 1e16; @@ -193,7 +187,7 @@ contract LiquidityBinQuoterTest is TestHelper { assertLt(quote.amounts[0], amountOut, "test_Scenario2::9"); assertEq(quote.amounts[1], amountOut, "test_Scenario2::10"); assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario2::11"); - assertEq(quote.revisions[0], 0, "test_Scenario2::12"); + assertEq(uint256(quote.versions[0]), 1, "test_Scenario2::12"); // Large amountOut amountOut = 100e18; @@ -202,11 +196,11 @@ contract LiquidityBinQuoterTest is TestHelper { assertApproxEqRel(quote.amounts[0], amountOut, 5e16, "test_Scenario2::13"); assertEq(quote.amounts[1], amountOut, "test_Scenario2::14"); assertEq(quote.binSteps[0], 0, "test_Scenario2::15"); - assertEq(quote.revisions[0], 0, "test_Scenario2::16"); + assertEq(uint256(quote.versions[0]), 0, "test_Scenario2::16"); } function test_Scenario3() public { - // WETH/USDC, V1 with low liquidity, V2.1.rev1 with low liquidity, V2.1.rev2 with high liquidity + // WETH/USDC, V1 with low liquidity, V2.1 with high liquidity address[] memory route = new address[](2); route[0] = address(weth); route[1] = address(usdc); @@ -218,7 +212,7 @@ contract LiquidityBinQuoterTest is TestHelper { assertEq(quote.amounts[0], amountIn, "test_Scenario3::1"); assertApproxEqRel(quote.amounts[1], amountIn * 2, 5e16, "test_Scenario3::2"); assertEq(quote.binSteps[0], 0, "test_Scenario3::3"); - assertEq(quote.revisions[0], 0, "test_Scenario3::4"); + assertEq(uint256(quote.versions[0]), 0, "test_Scenario3::4"); // Large amountIn amountIn = 100e18; @@ -227,7 +221,7 @@ contract LiquidityBinQuoterTest is TestHelper { assertEq(quote.amounts[0], amountIn, "test_Scenario3::5"); assertApproxEqRel(quote.amounts[1], amountIn, 5e16, "test_Scenario3::6"); assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario3::7"); - assertEq(quote.revisions[0], 2, "test_Scenario3::8"); + assertEq(uint256(quote.versions[0]), 2, "test_Scenario3::8"); // Small amountOut uint128 amountOut = 1e16; @@ -236,7 +230,7 @@ contract LiquidityBinQuoterTest is TestHelper { assertApproxEqRel(quote.amounts[0], amountOut / 2, 5e16, "test_Scenario3::9"); assertEq(quote.amounts[1], amountOut, "test_Scenario3::10"); assertEq(quote.binSteps[0], 0, "test_Scenario3::11"); - assertEq(quote.revisions[0], 0, "test_Scenario3::12"); + assertEq(uint256(quote.versions[0]), 0, "test_Scenario3::12"); // Large amountOut amountOut = 100e18; @@ -245,7 +239,7 @@ contract LiquidityBinQuoterTest is TestHelper { assertApproxEqRel(quote.amounts[0], amountOut, 5e16, "test_Scenario3::13"); assertEq(quote.amounts[1], amountOut, "test_Scenario3::14"); assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario3::15"); - assertEq(quote.revisions[0], 2, "test_Scenario3::16"); + assertEq(uint256(quote.versions[0]), 2, "test_Scenario3::16"); } function test_Scenario4() public { @@ -262,7 +256,7 @@ contract LiquidityBinQuoterTest is TestHelper { assertEq(quote.amounts[0], amountIn, "test_Scenario4::1"); assertGt(quote.amounts[1], amountIn, "test_Scenario4::2"); assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario4::3"); - assertEq(quote.revisions[0], 1, "test_Scenario4::4"); + assertEq(uint256(quote.versions[0]), 2, "test_Scenario4::4"); // Large amountIn amountIn = 100e18; @@ -271,7 +265,7 @@ contract LiquidityBinQuoterTest is TestHelper { assertEq(quote.amounts[0], amountIn, "test_Scenario4::5"); assertApproxEqRel(quote.amounts[1], amountIn, 5e16, "test_Scenario4::6"); assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario4::7"); - assertEq(quote.revisions[0], 0, "test_Scenario4::8"); + assertEq(uint256(quote.versions[0]), 1, "test_Scenario4::8"); // Small amountOut uint128 amountOut = 1e16; @@ -280,7 +274,7 @@ contract LiquidityBinQuoterTest is TestHelper { assertLt(quote.amounts[0], amountOut, "test_Scenario4::9"); assertEq(quote.amounts[1], amountOut, "test_Scenario4::10"); assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario4::11"); - assertEq(quote.revisions[0], 1, "test_Scenario4::12"); + assertEq(uint256(quote.versions[0]), 2, "test_Scenario4::12"); // Large amountOut amountOut = 100e18; @@ -289,6 +283,6 @@ contract LiquidityBinQuoterTest is TestHelper { assertApproxEqRel(quote.amounts[0], amountOut, 5e16, "test_Scenario4::13"); assertEq(quote.amounts[1], amountOut, "test_Scenario4::14"); assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "test_Scenario4::15"); - assertEq(quote.revisions[0], 0, "test_Scenario4::16"); + assertEq(uint256(quote.versions[0]), 1, "test_Scenario4::16"); } } diff --git a/test/integration/LBRouter.t.sol b/test/integration/LBRouter.t.sol new file mode 100644 index 00000000..d1c858f4 --- /dev/null +++ b/test/integration/LBRouter.t.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.10; + +import "../helpers/TestHelper.sol"; + +/** + * Pairs created: + * USDT/USDC V1 + * AVAX/USDC V2 + * WETH/AVAX V2.1 + * TaxToken/AVAX V2.1 + */ +contract LiquidityBinRouterForkTest is TestHelper { + using Utils for ILBRouter.LiquidityParameters; + + function setUp() public override { + vm.createSelectFork(vm.rpcUrl("avalanche"), 25_396_630); + super.setUp(); + + uint256 liquidityAmount = 1e24; + + // Get tokens to add liquidity + deal(address(usdc), address(this), 10 * liquidityAmount); + deal(address(usdt), address(this), 10 * liquidityAmount); + deal(address(weth), address(this), 10 * liquidityAmount); + deal(address(taxToken), address(this), 10 * liquidityAmount); + + // Add liquidity to V1 + routerV1.addLiquidity( + address(usdt), + address(usdc), + liquidityAmount, // 1 USDT = 1 USDC + liquidityAmount, + 0, + 0, + address(this), + block.timestamp + 1 + ); + + vm.startPrank(AvalancheAddresses.V2_FACTORY_OWNER); + legacyFactoryV2.addQuoteAsset(usdc); + legacyFactoryV2.createLBPair(wavax, usdc, ID_ONE, DEFAULT_BIN_STEP); // 1 AVAX = 1 USDC + vm.stopPrank(); + + factory.createLBPair(weth, wavax, ID_ONE, DEFAULT_BIN_STEP); // 1 WETH = 1 AVAX + factory.createLBPair(taxToken, wavax, ID_ONE, DEFAULT_BIN_STEP); // 1 TaxToken = 1 AVAX + + // Add liquidity to V2 + ILBRouter.LiquidityParameters memory liquidityParameters = + getLiquidityParameters(wavax, usdc, liquidityAmount, ID_ONE, 7, 0); + legacyRouterV2.addLiquidityAVAX{value: liquidityParameters.amountX}(liquidityParameters.toLegacy()); + + liquidityParameters = getLiquidityParameters(weth, wavax, liquidityAmount, ID_ONE, 7, 0); + router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); + + liquidityParameters = getLiquidityParameters(taxToken, wavax, liquidityAmount, ID_ONE, 7, 0); + router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); + } + + function test_swapExactTokensForTokens() public { + uint256 amountIn = 1e18; + + ILBRouter.Path memory path = _buildPath(usdt, weth); + LBQuoter.Quote memory quote = + quoter.findBestPathFromAmountIn(_convertToAddresses(path.tokenPath), uint128(amountIn)); + + uint256 amountOut = router.swapExactTokensForTokens(amountIn, 0, path, address(this), block.timestamp + 1); + + assertEq(amountOut, quote.amounts[3]); + } + + function test_swapTokensForExactTokens() public { + uint256 amountOut = 1e18; + + ILBRouter.Path memory path = _buildPath(weth, usdt); + LBQuoter.Quote memory quote = + quoter.findBestPathFromAmountOut(_convertToAddresses(path.tokenPath), uint128(amountOut)); + + uint256[] memory amountsIn = + router.swapTokensForExactTokens(amountOut, quote.amounts[0], path, address(this), block.timestamp + 1); + + assertEq(amountsIn[0], quote.amounts[0]); + } + + function test_swapExactTokensForTokensSupportingFeeOnTransferTokens() public { + uint256 amountIn = 1e18; + + ILBRouter.Path memory path = _buildPath(taxToken, usdt); + LBQuoter.Quote memory quote = + quoter.findBestPathFromAmountIn(_convertToAddresses(path.tokenPath), uint128(amountIn)); + + uint256 amountOut = router.swapExactTokensForTokensSupportingFeeOnTransferTokens( + amountIn, 0, path, address(this), block.timestamp + 1 + ); + + assertApproxEqRel(amountOut, quote.amounts[3] / 2, 1e12); + } + + function _buildPath(IERC20 tokenIn, IERC20 tokenOut) private view returns (ILBRouter.Path memory path) { + path.pairBinSteps = new uint256[](3); + path.versions = new ILBRouter.Version[](3); + path.tokenPath = new IERC20[](4); + + if (tokenIn == usdt) { + path.tokenPath[0] = tokenIn; + path.tokenPath[1] = usdc; + path.tokenPath[2] = wavax; + path.tokenPath[3] = tokenOut; + + path.pairBinSteps[0] = 0; + path.pairBinSteps[1] = DEFAULT_BIN_STEP; + path.pairBinSteps[2] = DEFAULT_BIN_STEP; + + path.versions[0] = ILBRouter.Version.V1; + path.versions[1] = ILBRouter.Version.V2; + path.versions[2] = ILBRouter.Version.V2_1; + } else { + path.tokenPath[0] = tokenIn; + path.tokenPath[1] = wavax; + path.tokenPath[2] = usdc; + path.tokenPath[3] = tokenOut; + + path.pairBinSteps[0] = DEFAULT_BIN_STEP; + path.pairBinSteps[1] = DEFAULT_BIN_STEP; + path.pairBinSteps[2] = 0; + + path.versions[0] = ILBRouter.Version.V2_1; + path.versions[1] = ILBRouter.Version.V2; + path.versions[2] = ILBRouter.Version.V1; + } + } + + function _convertToAddresses(IERC20[] memory tokens) private pure returns (address[] memory addresses) { + addresses = new address[](tokens.length); + for (uint256 i = 0; i < tokens.length; i++) { + addresses[i] = address(tokens[i]); + } + } +} From 13f15c3197c1de61edc1375c51bc32ad4ad4d85b Mon Sep 17 00:00:00 2001 From: Mathieu <85969303+Mathieu-Be@users.noreply.github.com> Date: Mon, 6 Feb 2023 15:42:27 +0100 Subject: [PATCH 17/47] Formatting and cleanup (#83) * remove checks on static fee parameters in the factory * add better test on setFeesParametersOnPair * improve test coverage * format factory tests * format router tests * format quoter tests * add natspecs on factory * add natspecs for the router * add natspec on quoter * fix typo * remove magic numbers * cache wavax in removeLiquidityAVAX * fix setPreset test * fix typo in quoter * Remove _MAX_PROTOCOL_SHARE and rename _MAX_FEE * better functions names * remove duplicate event --- foundry.toml | 2 - src/LBFactory.sol | 443 ++++++++++---------- src/LBQuoter.sol | 123 +++--- src/LBRouter.sol | 511 ++++++++++++++--------- src/interfaces/ILBFactory.sol | 45 +- src/interfaces/ILBLegacyFactory.sol | 2 +- src/interfaces/ILBRouter.sol | 57 ++- test/LBFactory.t.sol | 622 ++++++++++++---------------- test/LBRouter.Liquidity.t.sol | 125 ++++-- test/LBRouter.Swap.t.sol | 130 +++--- test/integration/LBQuoter.t.sol | 32 +- test/integration/LBRouter.t.sol | 43 +- 12 files changed, 1123 insertions(+), 1012 deletions(-) diff --git a/foundry.toml b/foundry.toml index e8dae739..4f8acd4d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,8 +5,6 @@ libs = ['lib'] optimizer = true optimizer_runs = 800 -no_match_contract = "TODO" - [fuzz] runs = 1024 diff --git a/src/LBFactory.sol b/src/LBFactory.sol index 19442959..f0c01715 100644 --- a/src/LBFactory.sol +++ b/src/LBFactory.sol @@ -16,11 +16,13 @@ import {SafeCast} from "./libraries/math/SafeCast.sol"; import {ILBFactory} from "./interfaces/ILBFactory.sol"; import {ILBPair} from "./interfaces/ILBPair.sol"; -/// @title Liquidity Book Factory -/// @author Trader Joe -/// @notice Contract used to deploy and register new LBPairs. -/// Enables setting fee parameters, flashloan fees and LBPair implementation. -/// Unless the `_isPresetOpen` is `true`, only the owner of the factory can create pairs. +/** + * @title Liquidity Book Factory + * @author Trader Joe + * @notice Contract used to deploy and register new LBPairs. + * Enables setting fee parameters, flashloan fees and LBPair implementation. + * Unless the `_isPresetOpen` is `true`, only the owner of the factory can create pairs. + */ contract LBFactory is PendingOwnable, ILBFactory { using SafeCast for uint256; using Encoded for bytes32; @@ -30,8 +32,7 @@ contract LBFactory is PendingOwnable, ILBFactory { uint256 private constant _MIN_BIN_STEP = 1; // 0.01% uint256 private constant _MAX_BIN_STEP = 200; // 1%, can't be greater than 247 for indexing reasons - uint256 private constant _MAX_FEE = 0.1e18; // 10% - uint256 private constant _MAX_PROTOCOL_SHARE = 2_500; // 25% + uint256 private constant _MAX_FLASHLOAN_FEE = 0.1e18; // 10% address private _feeRecipient; uint256 private _flashLoanFee; @@ -39,30 +40,44 @@ contract LBFactory is PendingOwnable, ILBFactory { ILBPair[] private _allLBPairs; - /// @dev Mapping from a (tokenA, tokenB, binStep) to a LBPair. The tokens are ordered to save gas, but they can be - /// in the reverse order in the actual pair. Always query one of the 2 tokens of the pair to assert the order of the 2 tokens + uint256 private constant _TRUE = 1; + uint256 private constant _FALSE = 0; + + /** + * @dev Mapping from a (tokenA, tokenB, binStep) to a LBPair. The tokens are ordered to save gas, but they can be + * in the reverse order in the actual pair. Always query one of the 2 tokens of the pair to assert the order of the 2 tokens + */ mapping(IERC20 => mapping(IERC20 => mapping(uint256 => LBPairInformation))) private _lbPairsInfo; - /// @dev Whether a preset was set or not, if the bit at `index` is 1, it means that the binStep `index` was set - /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas + /** + * @dev Whether a preset was set or not, if the bit at `index` is 1, it means that the binStep `index` was set + * The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas + */ bytes32 private _availablePresets; + /** + * @dev Whether a preset is open to anyone to create pairs, if the bit at `index` is 1, it means that the binStep `index` was set + */ bytes32 private _openPresets; - // The parameters presets mapping(uint256 => bytes32) private _presets; EnumerableSet.AddressSet private _quoteAssetWhitelist; - /// @dev Whether a LBPair was created with a bin step, if the bit at `index` is 1, it means that the LBPair with binStep `index` exists - /// The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas + /** + * @dev Whether a LBPair was created with a bin step, if the bit at `index` is 1, it means that the LBPair with binStep `index` exists + * The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas + */ mapping(IERC20 => mapping(IERC20 => bytes32)) private _availableLBPairBinSteps; - /// @notice Constructor - /// @param feeRecipient The address of the fee recipient - /// @param flashLoanFee The value of the fee for flash loan + /** + * @notice Constructor + * @param feeRecipient The address of the fee recipient + * @param flashLoanFee The value of the fee for flash loan + * + */ constructor(address feeRecipient, uint256 flashLoanFee) { - if (flashLoanFee > _MAX_FEE) revert LBFactory__FlashLoanFeeAboveMax(flashLoanFee, _MAX_FEE); + if (flashLoanFee > _MAX_FLASHLOAN_FEE) revert LBFactory__FlashLoanFeeAboveMax(flashLoanFee, _MAX_FLASHLOAN_FEE); _setFeeRecipient(feeRecipient); @@ -70,89 +85,124 @@ contract LBFactory is PendingOwnable, ILBFactory { emit FlashLoanFeeSet(0, flashLoanFee); } - // TODO: Natspecs - + /** + * @notice Get the minimum bin step a pair can have + * @return minBinStep + */ function getMinBinStep() external pure returns (uint256 minBinStep) { return _MIN_BIN_STEP; } + /** + * @notice Get the maximum bin step a pair can have + * @return maxBinStep + */ function getMaxBinStep() external pure returns (uint256 maxBinStep) { return _MAX_BIN_STEP; } + /** + * @notice Get the protocol fee recipient + * @return feeRecipient + */ function getFeeRecipient() external view returns (address feeRecipient) { return _feeRecipient; } - function getMaxFee() external pure returns (uint256 maxFee) { - return _MAX_FEE; - } - - function getMaxProtocolShare() external pure returns (uint256 maxProtocolShare) { - return _MAX_PROTOCOL_SHARE; + /** + * @notice Get the maximum fee percentage for flashLoans + * @return maxFee + */ + function getMaxFlashLoanFee() external pure returns (uint256 maxFee) { + return _MAX_FLASHLOAN_FEE; } + /** + * @notice Get the fee for flash loans + * @return flashloanFee + */ function getFlashLoanFee() external view returns (uint256 flashloanFee) { return _flashLoanFee; } - function getLBPairImplementation() external view returns (address LBPairImplementation) { + /** + * @notice Get the address of the LBPair implementation + * @return lbPairImplementation + */ + function getLBPairImplementation() external view returns (address lbPairImplementation) { return _lbPairImplementation; } - /// @notice View function to return the number of LBPairs created - /// @return The number of LBPair - function getNumberOfLBPairs() external view override returns (uint256) { + /** + * @notice View function to return the number of LBPairs created + * @return lbPairNumber + */ + function getNumberOfLBPairs() external view override returns (uint256 lbPairNumber) { return _allLBPairs.length; } - function getLBPairAtIndex(uint256 index) external view returns (ILBPair pair) { + /** + * @notice View function to return the LBPair created at index `index` + * @param index The index + * @return lbPair The address of the LBPair at index `index` + */ + function getLBPairAtIndex(uint256 index) external view returns (ILBPair lbPair) { return _allLBPairs[index]; } - /// @notice View function to return the number of quote assets whitelisted - /// @return The number of quote assets - function getNumberOfQuoteAssets() external view override returns (uint256) { + /** + * @notice View function to return the number of quote assets whitelisted + * @return numberOfQuoteAssets The number of quote assets + */ + function getNumberOfQuoteAssets() external view override returns (uint256 numberOfQuoteAssets) { return _quoteAssetWhitelist.length(); } - /// @notice View function to return the quote asset whitelisted at index `index` - /// @param index The index - /// @return The address of the quoteAsset at index `index` - function getQuoteAsset(uint256 index) external view override returns (IERC20) { + /** + * @notice View function to return the quote asset whitelisted at index `index` + * @param index The index + * @return asset The address of the quoteAsset at index `index` + */ + function getQuoteAssetAtIndex(uint256 index) external view override returns (IERC20 asset) { return IERC20(_quoteAssetWhitelist.at(index)); } - /// @notice View function to return whether a token is a quotedAsset (true) or not (false) - /// @param token The address of the asset - /// @return Whether the token is a quote asset or not - function isQuoteAsset(IERC20 token) external view override returns (bool) { + /** + * @notice View function to return whether a token is a quotedAsset (true) or not (false) + * @param token The address of the asset + * @return isQuote Whether the token is a quote asset or not + */ + function isQuoteAsset(IERC20 token) external view override returns (bool isQuote) { return _quoteAssetWhitelist.contains(address(token)); } - /// @notice Returns the LBPairInformation if it exists, - /// if not, then the address 0 is returned. The order doesn't matter - /// @param tokenA The address of the first token of the pair - /// @param tokenB The address of the second token of the pair - /// @param binStep The bin step of the LBPair - /// @return The LBPairInformation + /** + * @notice Returns the LBPairInformation if it exists, + * if not, then the address 0 is returned. The order doesn't matter + * @param tokenA The address of the first token of the pair + * @param tokenB The address of the second token of the pair + * @param binStep The bin step of the LBPair + * @return lbPairInformation The LBPairInformation + */ function getLBPairInformation(IERC20 tokenA, IERC20 tokenB, uint256 binStep) external view - returns (LBPairInformation memory) + returns (LBPairInformation memory lbPairInformation) { return _getLBPairInformation(tokenA, tokenB, binStep); } - /// @notice View function to return the different parameters of the preset - /// @param binStep The bin step of the preset - /// @return baseFactor The base factor - /// @return filterPeriod The filter period of the preset - /// @return decayPeriod The decay period of the preset - /// @return reductionFactor The reduction factor of the preset - /// @return variableFeeControl The variable fee control of the preset - /// @return protocolShare The protocol share of the preset - /// @return maxVolatilityAccumulator The max volatility accumulator of the preset + /** + * @notice View function to return the different parameters of the preset + * @param binStep The bin step of the preset + * @return baseFactor The base factor + * @return filterPeriod The filter period of the preset + * @return decayPeriod The decay period of the preset + * @return reductionFactor The reduction factor of the preset + * @return variableFeeControl The variable fee control of the preset + * @return protocolShare The protocol share of the preset + * @return maxVolatilityAccumulator The max volatility accumulator of the preset + */ function getPreset(uint256 binStep) external view @@ -179,26 +229,33 @@ contract LBFactory is PendingOwnable, ILBFactory { maxVolatilityAccumulator = preset.getMaxVolatilityAccumulator(); } - function getIsPresetOpen(uint8 binStep) external view returns (bool) { + /** + * @notice View function to return whether a preset is available to anyone for pair creation (true) or not (false) + * @param binStep The bin step of the preset + * @return isAvailable Whether the preset is available or not + */ + function isPresetOpen(uint8 binStep) external view returns (bool isAvailable) { bytes32 openPresets = _openPresets; return _isPresetOpen(openPresets, binStep); } - /// @notice View function to return the list of available binStep with a preset - /// @return presetsBinStep The list of binStep - function getAllBinSteps() external view override returns (uint256[] memory presetsBinStep) { + /** + * @notice View function to return the list of available binStep with a preset + * @return binStepWithPreset The list of binStep + */ + function getAllBinSteps() external view override returns (uint256[] memory binStepWithPreset) { unchecked { bytes32 avPresets = _availablePresets; uint256 nbPresets = avPresets.decodeUint8(248); if (nbPresets > 0) { - presetsBinStep = new uint256[](nbPresets); + binStepWithPreset = new uint256[](nbPresets); uint256 index; for (uint256 i = _MIN_BIN_STEP; i <= _MAX_BIN_STEP; ++i) { - if (avPresets.decodeUint1(i) == 1) { - presetsBinStep[index] = i; + if (avPresets.decodeUint1(i) == _TRUE) { + binStepWithPreset[index] = i; if (++index == nbPresets) break; } } @@ -206,10 +263,12 @@ contract LBFactory is PendingOwnable, ILBFactory { } } - /// @notice View function to return all the LBPair of a pair of tokens - /// @param tokenX The first token of the pair - /// @param tokenY The second token of the pair - /// @return lbPairsAvailable The list of available LBPairs + /** + * @notice View function to return all the LBPair of a pair of tokens + * @param tokenX The first token of the pair + * @param tokenY The second token of the pair + * @return lbPairsAvailable The list of available LBPairs + */ function getAllLBPairs(IERC20 tokenX, IERC20 tokenY) external view @@ -227,7 +286,7 @@ contract LBFactory is PendingOwnable, ILBFactory { uint256 index; for (uint256 i = _MIN_BIN_STEP; i <= _MAX_BIN_STEP; ++i) { - if (avLBPairBinSteps.decodeUint1(i) == 1) { + if (avLBPairBinSteps.decodeUint1(i) == _TRUE) { LBPairInformation memory pairInformation = _lbPairsInfo[tokenA][tokenB][i]; lbPairsAvailable[index] = LBPairInformation({ @@ -243,9 +302,11 @@ contract LBFactory is PendingOwnable, ILBFactory { } } - /// @notice Set the LBPair implementation address - /// @dev Needs to be called by the owner - /// @param newLBPairImplementation The address of the implementation + /** + * @notice Set the LBPair implementation address + * @dev Needs to be called by the owner + * @param newLBPairImplementation The address of the implementation + */ function setLBPairImplementation(address newLBPairImplementation) external override onlyOwner { if (ILBPair(newLBPairImplementation).getFactory() != this) { revert LBFactory__LBPairSafetyCheckFailed(newLBPairImplementation); @@ -261,12 +322,14 @@ contract LBFactory is PendingOwnable, ILBFactory { emit LBPairImplementationSet(oldLBPairImplementation, newLBPairImplementation); } - /// @notice Create a liquidity bin LBPair for tokenX and tokenY - /// @param tokenX The address of the first token - /// @param tokenY The address of the second token - /// @param activeId The active id of the pair - /// @param binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @return pair The address of the newly created LBPair + /** + * @notice Create a liquidity bin LBPair for tokenX and tokenY + * @param tokenX The address of the first token + * @param tokenY The address of the second token + * @param activeId The active id of the pair + * @param binStep The bin step in basis point, used to calculate log(1 + binStep) + * @return pair The address of the newly created LBPair + */ function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint8 binStep) external override @@ -332,7 +395,7 @@ contract LBFactory is PendingOwnable, ILBFactory { { bytes32 avLBPairBinSteps = _availableLBPairBinSteps[tokenA][tokenB]; // We add a 1 at bit `binStep` as this binStep is now set - avLBPairBinSteps = avLBPairBinSteps.set(1, 1, binStep); + avLBPairBinSteps = avLBPairBinSteps.set(_TRUE, Encoded.MASK_UINT1, binStep); // Increase the number of lb pairs by 1 avLBPairBinSteps = bytes32(uint256(avLBPairBinSteps) + (1 << 248)); @@ -344,11 +407,13 @@ contract LBFactory is PendingOwnable, ILBFactory { emit LBPairCreated(tokenX, tokenY, binStep, pair, _allLBPairs.length - 1); } - /// @notice Function to set whether the pair is ignored or not for routing, it will make the pair unusable by the router - /// @param tokenX The address of the first token of the pair - /// @param tokenY The address of the second token of the pair - /// @param binStep The bin step in basis point of the pair - /// @param ignored Whether to ignore (true) or not (false) the pair for routing + /** + * @notice Function to set whether the pair is ignored or not for routing, it will make the pair unusable by the router + * @param tokenX The address of the first token of the pair + * @param tokenY The address of the second token of the pair + * @param binStep The bin step in basis point of the pair + * @param ignored Whether to ignore (true) or not (false) the pair for routing + */ function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) external override @@ -366,15 +431,17 @@ contract LBFactory is PendingOwnable, ILBFactory { emit LBPairIgnoredStateChanged(pairInformation.LBPair, ignored); } - /// @notice Sets the preset parameters of a bin step - /// @param binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @param baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep - /// @param filterPeriod The period where the accumulator value is untouched, prevent spam - /// @param decayPeriod The period where the accumulator value is halved - /// @param reductionFactor The reduction factor, used to calculate the reduction of the accumulator - /// @param variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it - /// @param protocolShare The share of the fees received by the protocol - /// @param maxVolatilityAccumulator The max value of the volatility accumulator + /** + * @notice Sets the preset parameters of a bin step + * @param binStep The bin step in basis point, used to calculate log(1 + binStep) + * @param baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep + * @param filterPeriod The period where the accumulator value is untouched, prevent spam + * @param decayPeriod The period where the accumulator value is halved + * @param reductionFactor The reduction factor, used to calculate the reduction of the accumulator + * @param variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it + * @param protocolShare The share of the fees received by the protocol + * @param maxVolatilityAccumulator The max value of the volatility accumulator + */ function setPreset( uint8 binStep, uint16 baseFactor, @@ -385,8 +452,9 @@ contract LBFactory is PendingOwnable, ILBFactory { uint16 protocolShare, uint24 maxVolatilityAccumulator ) external override onlyOwner { - bytes32 packedFeeParameters = _getPackedFeeParameters( - binStep, + bytes32 preset; + + _presets[binStep] = preset.setStaticFeeParameters( baseFactor, filterPeriod, decayPeriod, @@ -396,14 +464,10 @@ contract LBFactory is PendingOwnable, ILBFactory { maxVolatilityAccumulator ); - bytes32 preset = bytes32((uint256(packedFeeParameters))); - - _presets[binStep] = preset; - bytes32 avPresets = _availablePresets; if (avPresets.decodeUint1(binStep) == 0) { // We add a 1 at bit `binStep` as this binStep is now set - avPresets = avPresets.set(1, 1, binStep); + avPresets = avPresets.set(_TRUE, Encoded.MASK_UINT1, binStep); // Increase the number of preset by 1 avPresets = bytes32(uint256(avPresets) + (1 << 248)); @@ -424,34 +488,42 @@ contract LBFactory is PendingOwnable, ILBFactory { ); } + /** + * @notice Sets the open state of a preset. If true, anyone can create a pair with this preset, + * if false, it is restricted to the owner of the factory + * @param binStep The bin step to open or close + * @param isOpen Whether the preset will be open or not + */ function setOpenPreset(uint8 binStep, bool isOpen) external onlyOwner { bytes32 openPresets = _openPresets; - bool isPresetOpen = _isPresetOpen(openPresets, binStep); + bool isPresetOpenCurrent = _isPresetOpen(openPresets, binStep); if (isOpen) { - if (isPresetOpen) revert LBFactory__SamePresetOpenState(); + if (isPresetOpenCurrent) revert LBFactory__SamePresetOpenState(); // We add a 1 at bit `binStep` as this binStep is now open - _openPresets = _openPresets.set(1, 1, binStep); + _openPresets = _openPresets.set(_TRUE, Encoded.MASK_UINT1, binStep); } else { - if (!isPresetOpen) revert LBFactory__SamePresetOpenState(); + if (!isPresetOpenCurrent) revert LBFactory__SamePresetOpenState(); // We remove a 1 at bit `binStep` as this binStep is now closed - _openPresets = _openPresets.set(0, 1, binStep); + _openPresets = _openPresets.set(_FALSE, Encoded.MASK_UINT1, binStep); } emit OpenPresetChanged(binStep, isOpen); } - /// @notice Remove the preset linked to a binStep - /// @param binStep The bin step to remove + /** + * @notice Remove the preset linked to a binStep + * @param binStep The bin step to remove + */ function removePreset(uint8 binStep) external override onlyOwner { if (_presets[binStep] == bytes32(0)) revert LBFactory__BinStepHasNoPreset(binStep); // Set the bit `binStep` to 0 bytes32 avPresets = _availablePresets; - avPresets = avPresets.set(0, 1, binStep); + avPresets = avPresets.set(_FALSE, Encoded.MASK_UINT1, binStep); avPresets = bytes32(uint256(avPresets) - (1 << 248)); // Save the changes @@ -461,17 +533,19 @@ contract LBFactory is PendingOwnable, ILBFactory { emit PresetRemoved(binStep); } - /// @notice Function to set the fee parameter of a LBPair - /// @param tokenX The address of the first token - /// @param tokenY The address of the second token - /// @param binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @param baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep - /// @param filterPeriod The period where the accumulator value is untouched, prevent spam - /// @param decayPeriod The period where the accumulator value is halved - /// @param reductionFactor The reduction factor, used to calculate the reduction of the accumulator - /// @param variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it - /// @param protocolShare The share of the fees received by the protocol - /// @param maxVolatilityAccumulator The max value of volatility accumulator + /** + * @notice Function to set the fee parameter of a LBPair + * @param tokenX The address of the first token + * @param tokenY The address of the second token + * @param binStep The bin step in basis point, used to calculate log(1 + binStep) + * @param baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep + * @param filterPeriod The period where the accumulator value is untouched, prevent spam + * @param decayPeriod The period where the accumulator value is halved + * @param reductionFactor The reduction factor, used to calculate the reduction of the accumulator + * @param variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it + * @param protocolShare The share of the fees received by the protocol + * @param maxVolatilityAccumulator The max value of volatility accumulator + */ function setFeesParametersOnPair( IERC20 tokenX, IERC20 tokenY, @@ -497,41 +571,34 @@ contract LBFactory is PendingOwnable, ILBFactory { protocolShare, maxVolatilityAccumulator ); - - emit FeeParametersSet( - msg.sender, - lbPair, - binStep, - baseFactor, - filterPeriod, - decayPeriod, - reductionFactor, - variableFeeControl, - protocolShare, - maxVolatilityAccumulator - ); } - /// @notice Function to set the recipient of the fees. This address needs to be able to receive ERC20s - /// @param feeRecipient The address of the recipient + /** + * @notice Function to set the recipient of the fees. This address needs to be able to receive ERC20s + * @param feeRecipient The address of the recipient + */ function setFeeRecipient(address feeRecipient) external override onlyOwner { _setFeeRecipient(feeRecipient); } - /// @notice Function to set the flash loan fee - /// @param flashLoanFee The value of the fee for flash loan + /** + * @notice Function to set the flash loan fee + * @param flashLoanFee The value of the fee for flash loan + */ function setFlashLoanFee(uint256 flashLoanFee) external override onlyOwner { uint256 oldFlashLoanFee = _flashLoanFee; if (oldFlashLoanFee == flashLoanFee) revert LBFactory__SameFlashLoanFee(flashLoanFee); - if (flashLoanFee > _MAX_FEE) revert LBFactory__FlashLoanFeeAboveMax(flashLoanFee, _MAX_FEE); + if (flashLoanFee > _MAX_FLASHLOAN_FEE) revert LBFactory__FlashLoanFeeAboveMax(flashLoanFee, _MAX_FLASHLOAN_FEE); _flashLoanFee = flashLoanFee; emit FlashLoanFeeSet(oldFlashLoanFee, flashLoanFee); } - /// @notice Function to add an asset to the whitelist of quote assets - /// @param quoteAsset The quote asset (e.g: AVAX, USDC...) + /** + * @notice Function to add an asset to the whitelist of quote assets + * @param quoteAsset The quote asset (e.g: AVAX, USDC...) + */ function addQuoteAsset(IERC20 quoteAsset) external override onlyOwner { if (!_quoteAssetWhitelist.add(address(quoteAsset))) { revert LBFactory__QuoteAssetAlreadyWhitelisted(quoteAsset); @@ -540,8 +607,10 @@ contract LBFactory is PendingOwnable, ILBFactory { emit QuoteAssetAdded(quoteAsset); } - /// @notice Function to remove an asset from the whitelist of quote assets - /// @param quoteAsset The quote asset (e.g: AVAX, USDC...) + /** + * @notice Function to remove an asset from the whitelist of quote assets + * @param quoteAsset The quote asset (e.g: AVAX, USDC...) + */ function removeQuoteAsset(IERC20 quoteAsset) external override onlyOwner { if (!_quoteAssetWhitelist.remove(address(quoteAsset))) revert LBFactory__QuoteAssetNotWhitelisted(quoteAsset); @@ -552,8 +621,10 @@ contract LBFactory is PendingOwnable, ILBFactory { return openPresets.decodeUint1(binStep) == 1; } - /// @notice Internal function to set the recipient of the fee - /// @param feeRecipient The address of the recipient + /** + * @notice Internal function to set the recipient of the fee + * @param feeRecipient The address of the recipient + */ function _setFeeRecipient(address feeRecipient) internal { if (feeRecipient == address(0)) revert LBFactory__AddressZero(); @@ -568,70 +639,14 @@ contract LBFactory is PendingOwnable, ILBFactory { pair.forceDecay(); } - /// @notice Internal function to set the fee parameter of a LBPair - /// @param binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @param baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep - /// @param filterPeriod The period where the accumulator value is untouched, prevent spam - /// @param decayPeriod The period where the accumulator value is halved - /// @param reductionFactor The reduction factor, used to calculate the reduction of the accumulator - /// @param variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it - /// @param protocolShare The share of the fees received by the protocol - /// @param maxVolatilityAccumulator The max value of volatility accumulator - function _getPackedFeeParameters( - uint8 binStep, - uint16 baseFactor, - uint16 filterPeriod, - uint16 decayPeriod, - uint16 reductionFactor, - uint24 variableFeeControl, - uint16 protocolShare, - uint24 maxVolatilityAccumulator - ) private pure returns (bytes32 preset) { - if (binStep < _MIN_BIN_STEP || binStep > _MAX_BIN_STEP) { - revert LBFactory__BinStepRequirementsBreached(_MIN_BIN_STEP, binStep, _MAX_BIN_STEP); - } - - if (filterPeriod >= decayPeriod) revert LBFactory__DecreasingPeriods(filterPeriod, decayPeriod); - - if (reductionFactor > Constants.BASIS_POINT_MAX) { - revert LBFactory__ReductionFactorOverflows(reductionFactor, Constants.BASIS_POINT_MAX); - } - - if (protocolShare > _MAX_PROTOCOL_SHARE) { - revert LBFactory__ProtocolShareOverflows(protocolShare, _MAX_PROTOCOL_SHARE); - } - - { - uint256 baseFee = (uint256(baseFactor) * binStep) * 1e10; - - // Can't overflow as the max value is `max(uint24) * (max(uint24) * max(uint16)) ** 2 < max(uint104)` - // It returns 18 decimals as: - // decimals(variableFeeControl * (volatilityAccumulator * binStep)**2 / 100) = 4 + (4 + 4) * 2 - 2 = 18 - uint256 prod = uint256(maxVolatilityAccumulator) * binStep; - uint256 maxVariableFee = (prod * prod * variableFeeControl) / 100; - - if (baseFee + maxVariableFee > _MAX_FEE) { - revert LBFactory__FeesAboveMax(baseFee + maxVariableFee, _MAX_FEE); - } - } - - return preset.setStaticFeeParameters( - baseFactor, - filterPeriod, - decayPeriod, - reductionFactor, - variableFeeControl, - protocolShare, - maxVolatilityAccumulator - ); - } - - /// @notice Returns the LBPairInformation if it exists, - /// if not, then the address 0 is returned. The order doesn't matter - /// @param tokenA The address of the first token of the pair - /// @param tokenB The address of the second token of the pair - /// @param binStep The bin step of the LBPair - /// @return The LBPairInformation + /** + * @notice Returns the LBPairInformation if it exists, + * if not, then the address 0 is returned. The order doesn't matter + * @param tokenA The address of the first token of the pair + * @param tokenB The address of the second token of the pair + * @param binStep The bin step of the LBPair + * @return The LBPairInformation + */ function _getLBPairInformation(IERC20 tokenA, IERC20 tokenB, uint256 binStep) private view @@ -641,11 +656,13 @@ contract LBFactory is PendingOwnable, ILBFactory { return _lbPairsInfo[tokenA][tokenB][binStep]; } - /// @notice Private view function to sort 2 tokens in ascending order - /// @param tokenA The first token - /// @param tokenB The second token - /// @return The sorted first token - /// @return The sorted second token + /** + * @notice Private view function to sort 2 tokens in ascending order + * @param tokenA The first token + * @param tokenB The second token + * @return The sorted first token + * @return The sorted second token + */ function _sortTokens(IERC20 tokenA, IERC20 tokenB) private pure returns (IERC20, IERC20) { if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); return (tokenA, tokenB); diff --git a/src/LBQuoter.sol b/src/LBQuoter.sol index 64ae9660..07290c69 100644 --- a/src/LBQuoter.sol +++ b/src/LBQuoter.sol @@ -18,25 +18,32 @@ import {ILBLegacyPair} from "./interfaces/ILBLegacyPair.sol"; import {ILBPair} from "./interfaces/ILBPair.sol"; import {ILBRouter} from "./interfaces/ILBRouter.sol"; -/// @title Liquidity Book Quoter -/// @author Trader Joe -/// @notice Helper contract to determine best path through multiple markets +/** + * @title Liquidity Book Quoter + * @author Trader Joe + * @notice Helper contract to determine best path through multiple markets + */ contract LBQuoter { using Uint256x256Math for uint256; error LBQuoter_InvalidLength(); - /// @notice Dex V2 router address - address private immutable _legacyRouterV2; - /// @notice Dex V2.1 router address - address private immutable _routerV2; - /// @notice Dex V1 factory address address private immutable _factoryV1; - /// @notice Dex V2 factory address address private immutable _legacyFactoryV2; - /// @notice Dex V2.1 factory address address private immutable _factoryV2; + address private immutable _legacyRouterV2; + address private immutable _routerV2; + /** + * @dev The quote struct returned by the quoter + * - route: address array of the token to go through + * - pairs: address array of the pairs to go through + * - binSteps: The bin step to use for each pair + * - versions: The version to use for each pair + * - amounts: The amounts of every step of the swap + * - virtualAmountsWithoutSlippage: The virtual amounts of every step of the swap without slippage + * - fees: The fees to pay for every step of the swap + */ struct Quote { address[] route; address[] pairs; @@ -47,12 +54,14 @@ contract LBQuoter { uint128[] fees; } - /// @notice Constructor - /// @param routerV2 Dex V2 router address - /// @param factoryV1 Dex V1 factory address - /// @param legacyFactoryV2 Dex V2 factory address - /// @param legacyRouterV2 Dex V2 router address - /// @param factoryV2 Dex V2.1 factory address + /** + * @notice Constructor + * @param factoryV1 Dex V1 factory address + * @param legacyFactoryV2 Dex V2 factory address + * @param factoryV2 Dex V2.1 factory address + * @param legacyRouterV2 Dex V2 router address + * @param routerV2 Dex V2 router address + */ constructor( address factoryV1, address legacyFactoryV2, @@ -67,40 +76,52 @@ contract LBQuoter { _legacyRouterV2 = legacyRouterV2; } - /// @notice Returns the Dex V1 factory address - /// @return factoryV1 Dex V1 factory address + /** + * @notice Returns the Dex V1 factory address + * @return factoryV1 Dex V1 factory address + */ function getFactoryV1() public view returns (address factoryV1) { factoryV1 = _factoryV1; } - /// @notice Returns the Dex V2 factory address - /// @return legacyFactoryV2 Dex V2 factory address + /** + * @notice Returns the Dex V2 factory address + * @return legacyFactoryV2 Dex V2 factory address + */ function getLegacyFactoryV2() public view returns (address legacyFactoryV2) { legacyFactoryV2 = _legacyFactoryV2; } - /// @notice Returns the Dex V2.1 factory address - /// @return factoryV2 Dex V2.1 factory address + /** + * @notice Returns the Dex V2.1 factory address + * @return factoryV2 Dex V2.1 factory address + */ function getFactoryV2() public view returns (address factoryV2) { factoryV2 = _factoryV2; } - /// @notice Returns the Dex V2 router address - /// @return legacyRouterV2 Dex V2 router address - function getLEgacyRouteractoryV2() public view returns (address legacyRouterV2) { + /** + * @notice Returns the Dex V2 router address + * @return legacyRouterV2 Dex V2 router address + */ + function getLegacyRouterV2() public view returns (address legacyRouterV2) { legacyRouterV2 = _legacyRouterV2; } - /// @notice Returns the Dex V2 router address - /// @return routerV2 Dex V2 router address + /** + * @notice Returns the Dex V2 router address + * @return routerV2 Dex V2 router address + */ function getRouterV2() public view returns (address routerV2) { routerV2 = _routerV2; } - /// @notice Finds the best path given a list of tokens and the input amount wanted from the swap - /// @param route List of the tokens to go through - /// @param amountIn Swap amount in - /// @return quote The Quote structure containing the necessary element to perform the swap + /** + * @notice Finds the best path given a list of tokens and the input amount wanted from the swap + * @param route List of the tokens to go through + * @param amountIn Swap amount in + * @return quote The Quote structure containing the necessary element to perform the swap + */ function findBestPathFromAmountIn(address[] calldata route, uint128 amountIn) public view @@ -210,10 +231,12 @@ contract LBQuoter { } } - /// @notice Finds the best path given a list of tokens and the output amount wanted from the swap - /// @param route List of the tokens to go through - /// @param amountOut Swap amount out - /// @return quote The Quote structure containing the necessary element to perform the swap + /** + * @notice Finds the best path given a list of tokens and the output amount wanted from the swap + * @param route List of the tokens to go through + * @param amountOut Swap amount out + * @return quote The Quote structure containing the necessary element to perform the swap + */ function findBestPathFromAmountOut(address[] calldata route, uint128 amountOut) public view @@ -326,13 +349,15 @@ contract LBQuoter { } } - /// @dev Forked from JoeLibrary - /// @dev Doesn't rely on the init code hash of the factory - /// @param pair Address of the pair - /// @param tokenA Address of token A - /// @param tokenB Address of token B - /// @return reserveA Reserve of token A in the pair - /// @return reserveB Reserve of token B in the pair + /** + * @dev Forked from JoeLibrary + * @dev Doesn't rely on the init code hash of the factory + * @param pair Address of the pair + * @param tokenA Address of token A + * @param tokenB Address of token B + * @return reserveA Reserve of token A in the pair + * @return reserveB Reserve of token B in the pair + */ function _getReserves(address pair, address tokenA, address tokenB) internal view @@ -343,12 +368,14 @@ contract LBQuoter { (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); } - /// @dev Calculates a quote for a V2 pair - /// @param amount Amount in to consider - /// @param activeId Current active Id of the considred pair - /// @param binStep Bin step of the considered pair - /// @param swapForY Boolean describing if we are swapping from X to Y or the opposite - /// @return quote Amount Out if _amount was swapped with no slippage and no fees + /** + * @dev Calculates a quote for a V2 pair + * @param amount Amount in to consider + * @param activeId Current active Id of the considred pair + * @param binStep Bin step of the considered pair + * @param swapForY Boolean describing if we are swapping from X to Y or the opposite + * @return quote Amount Out if _amount was swapped with no slippage and no fees + */ function _getV2Quote(uint256 amount, uint24 activeId, uint256 binStep, bool swapForY) internal pure diff --git a/src/LBRouter.sol b/src/LBRouter.sol index 833f7aaa..36c99974 100644 --- a/src/LBRouter.sol +++ b/src/LBRouter.sol @@ -25,9 +25,11 @@ import {ILBLegacyFactory} from "./interfaces/ILBLegacyFactory.sol"; import {ILBFactory} from "./interfaces/ILBFactory.sol"; import {IWAVAX} from "./interfaces/IWAVAX.sol"; -/// @title Liquidity Book Router -/// @author Trader Joe -/// @notice Main contract to interact with to swap and manage liquidity on Joe V2 exchange. +/** + * @title Liquidity Book Router + * @author Trader Joe + * @notice Main contract to interact with to swap and manage liquidity on Joe V2 exchange. + */ contract LBRouter is ILBRouter { using TokenHelper for IERC20; using TokenHelper for IWAVAX; @@ -58,11 +60,14 @@ contract LBRouter is ILBRouter { _; } - /// @notice Constructor - /// @param factory Address of Joe V2.1 factory - /// @param factoryV1 Address of Joe V1 factory - /// @param legacyFactory Address of Joe V2 factory - /// @param wavax Address of WAVAX + /** + * @notice Constructor + * @param factory Address of Joe V2.1 factory + * @param factoryV1 Address of Joe V1 factory + * @param legacyFactory Address of Joe V2 factory + * @param legacyRouter Address of Joe V2 router + * @param wavax Address of WAVAX + */ constructor( ILBFactory factory, IJoeFactory factoryV1, @@ -77,55 +82,83 @@ contract LBRouter is ILBRouter { _wavax = wavax; } - /// @dev Receive function that only accept AVAX from the WAVAX contract + /** + * @dev Receive function that only accept AVAX from the WAVAX contract + */ receive() external payable { if (msg.sender != address(_wavax)) revert LBRouter__SenderIsNotWAVAX(); } - function getFactory() external view override returns (ILBFactory) { + /** + * View function to get the factory V2.1 address + * @return lbFactory The address of the factory V2.1 + */ + function getFactory() external view override returns (ILBFactory lbFactory) { return _factory; } - function getLegacyFactory() external view override returns (ILBLegacyFactory) { + /** + * View function to get the factory V2 address + * @return legacyLBfactory The address of the factory V2 + */ + function getLegacyFactory() external view override returns (ILBLegacyFactory legacyLBfactory) { return _legacyFactory; } - function getV1Factory() external view override returns (IJoeFactory) { + /** + * View function to get the factory V1 address + * @return factoryV1 The address of the factory V1 + */ + function getV1Factory() external view override returns (IJoeFactory factoryV1) { return _factoryV1; } - function getLegacyRouter() external view override returns (ILBLegacyRouter) { + /** + * View function to get the router V2 address + * @return legacyRouter The address of the router V2 + */ + function getLegacyRouter() external view override returns (ILBLegacyRouter legacyRouter) { return _legacyRouter; } - function getWAVAX() external view override returns (IWAVAX) { + /** + * View function to get the WAVAX address + * @return wavax The address of WAVAX + */ + function getWAVAX() external view override returns (IWAVAX wavax) { return _wavax; } - /// @notice Returns the approximate id corresponding to the inputted price. - /// Warning, the returned id may be inaccurate close to the start price of a bin - /// @param pair The address of the LBPair - /// @param price The price of y per x (multiplied by 1e36) - /// @return The id corresponding to this price + /** + * @notice Returns the approximate id corresponding to the inputted price. + * Warning, the returned id may be inaccurate close to the start price of a bin + * @param pair The address of the LBPair + * @param price The price of y per x (multiplied by 1e36) + * @return The id corresponding to this price + */ function getIdFromPrice(ILBPair pair, uint256 price) external view override returns (uint24) { return pair.getIdFromPrice(price); } - /// @notice Returns the price corresponding to the inputted id - /// @param pair The address of the LBPair - /// @param id The id - /// @return The price corresponding to this id + /** + * @notice Returns the price corresponding to the inputted id + * @param pair The address of the LBPair + * @param id The id + * @return The price corresponding to this id + */ function getPriceFromId(ILBPair pair, uint24 id) external view override returns (uint256) { return pair.getPriceFromId(id); } - /// @notice Simulate a swap in - /// @param pair The address of the LBPair - /// @param amountOut The amount of token to receive - /// @param swapForY Whether you swap X for Y (true), or Y for X (false) - /// @return amountIn The amount of token to send in order to receive amountOut token - /// @return amountOutLeft The amount of token Out that can't be returned due to a lack of liquidity - /// @return fee The amount of fees paid in token sent + /** + * @notice Simulate a swap in + * @param pair The address of the LBPair + * @param amountOut The amount of token to receive + * @param swapForY Whether you swap X for Y (true), or Y for X (false) + * @return amountIn The amount of token to send in order to receive amountOut token + * @return amountOutLeft The amount of token Out that can't be returned due to a lack of liquidity + * @return fee The amount of fees paid in token sent + */ function getSwapIn(ILBPair pair, uint128 amountOut, bool swapForY) public view @@ -135,13 +168,15 @@ contract LBRouter is ILBRouter { (amountIn, amountOutLeft, fee) = pair.getSwapIn(amountOut, swapForY); } - /// @notice Simulate a swap out - /// @param pair The address of the LBPair - /// @param amountIn The amount of token sent - /// @param swapForY Whether you swap X for Y (true), or Y for X (false) - /// @return amountInLeft The amount of token In that can't be swapped due to a lack of liquidity - /// @return amountOut The amount of token received if amountIn tokenX are sent - /// @return fee The amount of fees paid in token sent + /** + * @notice Simulate a swap out + * @param pair The address of the LBPair + * @param amountIn The amount of token sent + * @param swapForY Whether you swap X for Y (true), or Y for X (false) + * @return amountInLeft The amount of token In that can't be swapped due to a lack of liquidity + * @return amountOut The amount of token received if amountIn tokenX are sent + * @return fee The amount of fees paid in token sent + */ function getSwapOut(ILBPair pair, uint128 amountIn, bool swapForY) external view @@ -151,12 +186,14 @@ contract LBRouter is ILBRouter { (amountInLeft, amountOut, fee) = pair.getSwapOut(amountIn, swapForY); } - /// @notice Create a liquidity bin LBPair for tokenX and tokenY using the factory - /// @param tokenX The address of the first token - /// @param tokenY The address of the second token - /// @param activeId The active id of the pair - /// @param binStep The bin step in basis point, used to calculate log(1 + binStep) - /// @return pair The address of the newly created LBPair + /** + * @notice Create a liquidity bin LBPair for tokenX and tokenY using the factory + * @param tokenX The address of the first token + * @param tokenY The address of the second token + * @param activeId The active id of the pair + * @param binStep The bin step in basis point, used to calculate log(1 + binStep) + * @return pair The address of the newly created LBPair + */ function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint8 binStep) external override @@ -165,9 +202,17 @@ contract LBRouter is ILBRouter { pair = _factory.createLBPair(tokenX, tokenY, activeId, binStep); } - /// @notice Add liquidity while performing safety checks - /// @dev This function is compliant with fee on transfer tokens - /// @param liquidityParameters The liquidity parameters + /** + * @notice Add liquidity while performing safety checks + * @dev This function is compliant with fee on transfer tokens + * @param liquidityParameters The liquidity parameters + * @return amountXAdded The amount of token X added + * @return amountYAdded The amount of token Y added + * @return amountXLeft The amount of token X left (sent back to liquidityParameters.refundTo) + * @return amountYLeft The amount of token Y left (sent back to liquidityParameters.refundTo) + * @return depositIds The ids of the deposits + * @return liquidityMinted The amount of liquidity minted + */ function addLiquidity(LiquidityParameters calldata liquidityParameters) external override @@ -194,9 +239,17 @@ contract LBRouter is ILBRouter { _addLiquidity(liquidityParameters, lbPair); } - /// @notice Add liquidity with AVAX while performing safety checks - /// @dev This function is compliant with fee on transfer tokens - /// @param liquidityParameters The liquidity parameters + /** + * @notice Add liquidity with AVAX while performing safety checks + * @dev This function is compliant with fee on transfer tokens + * @param liquidityParameters The liquidity parameters + * @return amountXAdded The amount of token X added + * @return amountYAdded The amount of token Y added + * @return amountXLeft The amount of token X left (sent back to liquidityParameters.refundTo) + * @return amountYLeft The amount of token Y left (sent back to liquidityParameters.refundTo) + * @return depositIds The ids of the deposits + * @return liquidityMinted The amount of liquidity minted + */ function addLiquidityAVAX(LiquidityParameters calldata liquidityParameters) external payable @@ -237,19 +290,21 @@ contract LBRouter is ILBRouter { _addLiquidity(liquidityParameters, _LBPair); } - /// @notice Remove liquidity while performing safety checks - /// @dev This function is compliant with fee on transfer tokens - /// @param tokenX The address of token X - /// @param tokenY The address of token Y - /// @param binStep The bin step of the LBPair - /// @param amountXMin The min amount to receive of token X - /// @param amountYMin The min amount to receive of token Y - /// @param ids The list of ids to burn - /// @param amounts The list of amounts to burn of each id in `_ids` - /// @param to The address of the recipient - /// @param deadline The deadline of the tx - /// @return amountX Amount of token X returned - /// @return amountY Amount of token Y returned + /** + * @notice Remove liquidity while performing safety checks + * @dev This function is compliant with fee on transfer tokens + * @param tokenX The address of token X + * @param tokenY The address of token Y + * @param binStep The bin step of the LBPair + * @param amountXMin The min amount to receive of token X + * @param amountYMin The min amount to receive of token Y + * @param ids The list of ids to burn + * @param amounts The list of amounts to burn of each id in `_ids` + * @param to The address of the recipient + * @param deadline The deadline of the tx + * @return amountX Amount of token X returned + * @return amountY Amount of token Y returned + */ function removeLiquidity( IERC20 tokenX, IERC20 tokenY, @@ -271,20 +326,22 @@ contract LBRouter is ILBRouter { if (isWrongOrder) (amountX, amountY) = (amountY, amountX); } - /// @notice Remove AVAX liquidity while performing safety checks - /// @dev This function is **NOT** compliant with fee on transfer tokens. - /// This is wanted as it would make users pays the fee on transfer twice, - /// use the `removeLiquidity` function to remove liquidity with fee on transfer tokens. - /// @param token The address of token - /// @param binStep The bin step of the LBPair - /// @param amountTokenMin The min amount to receive of token - /// @param amountAVAXMin The min amount to receive of AVAX - /// @param ids The list of ids to burn - /// @param amounts The list of amounts to burn of each id in `_ids` - /// @param to The address of the recipient - /// @param deadline The deadline of the tx - /// @return amountToken Amount of token returned - /// @return amountAVAX Amount of AVAX returned + /** + * @notice Remove AVAX liquidity while performing safety checks + * @dev This function is **NOT** compliant with fee on transfer tokens. + * This is wanted as it would make users pays the fee on transfer twice, + * use the `removeLiquidity` function to remove liquidity with fee on transfer tokens. + * @param token The address of token + * @param binStep The bin step of the LBPair + * @param amountTokenMin The min amount to receive of token + * @param amountAVAXMin The min amount to receive of AVAX + * @param ids The list of ids to burn + * @param amounts The list of amounts to burn of each id in `_ids` + * @param to The address of the recipient + * @param deadline The deadline of the tx + * @return amountToken Amount of token returned + * @return amountAVAX Amount of AVAX returned + */ function removeLiquidityAVAX( IERC20 token, uint8 binStep, @@ -295,13 +352,12 @@ contract LBRouter is ILBRouter { address payable to, uint256 deadline ) external override ensure(deadline) returns (uint256 amountToken, uint256 amountAVAX) { - // TODO - avoid stack too deep and cache wavax - // IWAVAX wavax_ = _wavax; + IWAVAX wavax = _wavax; - ILBPair lbPair = ILBPair(_getLBPairInformation(token, IERC20(_wavax), binStep, Version.V2_1)); + ILBPair lbPair = ILBPair(_getLBPairInformation(token, IERC20(wavax), binStep, Version.V2_1)); { - bool isAVAXTokenY = IERC20(_wavax) == lbPair.getTokenY(); + bool isAVAXTokenY = IERC20(wavax) == lbPair.getTokenY(); if (!isAVAXTokenY) { (amountTokenMin, amountAVAXMin) = (amountAVAXMin, amountTokenMin); @@ -315,16 +371,19 @@ contract LBRouter is ILBRouter { token.safeTransfer(to, amountToken); - _wavax.withdraw(amountAVAX); + wavax.withdraw(amountAVAX); _safeTransferAVAX(to, amountAVAX); } - /// @notice Swaps exact tokens for tokens while performing safety checks - /// @param amountIn The amount of token to send - /// @param amountOutMin The min amount of token to receive - /// @param to The address of the recipient - /// @param deadline The deadline of the tx - /// @return amountOut Output amount of the swap + /** + * @notice Swaps exact tokens for tokens while performing safety checks + * @param amountIn The amount of token to send + * @param amountOutMin The min amount of token to receive + * @param path The path of the swap + * @param to The address of the recipient + * @param deadline The deadline of the tx + * @return amountOut Output amount of the swap + */ function swapExactTokensForTokens( uint256 amountIn, uint256 amountOutMin, @@ -341,12 +400,15 @@ contract LBRouter is ILBRouter { if (amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMin, amountOut); } - /// @notice Swaps exact tokens for AVAX while performing safety checks - /// @param amountIn The amount of token to send - /// @param amountOutMinAVAX The min amount of AVAX to receive - /// @param to The address of the recipient - /// @param deadline The deadline of the tx - /// @return amountOut Output amount of the swap + /** + * @notice Swaps exact tokens for AVAX while performing safety checks + * @param amountIn The amount of token to send + * @param amountOutMinAVAX The min amount of AVAX to receive + * @param path The path of the swap + * @param to The address of the recipient + * @param deadline The deadline of the tx + * @return amountOut Output amount of the swap + */ function swapExactTokensForAVAX( uint256 amountIn, uint256 amountOutMinAVAX, @@ -370,11 +432,14 @@ contract LBRouter is ILBRouter { _safeTransferAVAX(to, amountOut); } - /// @notice Swaps exact AVAX for tokens while performing safety checks - /// @param amountOutMin The min amount of token to receive - /// @param to The address of the recipient - /// @param deadline The deadline of the tx - /// @return amountOut Output amount of the swap + /** + * @notice Swaps exact AVAX for tokens while performing safety checks + * @param amountOutMin The min amount of token to receive + * @param path The path of the swap + * @param to The address of the recipient + * @param deadline The deadline of the tx + * @return amountOut Output amount of the swap + */ function swapExactAVAXForTokens(uint256 amountOutMin, Path memory path, address to, uint256 deadline) external payable @@ -394,7 +459,15 @@ contract LBRouter is ILBRouter { if (amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMin, amountOut); } - /// @notice Swaps tokens for exact tokens while performing safety checks + /** + * @notice Swaps tokens for exact tokens while performing safety checks + * @param amountOut The amount of token to receive + * @param amountInMax The max amount of token to send + * @param path The path of the swap + * @param to The address of the recipient + * @param deadline The deadline of the tx + * @return amountsIn Input amounts of the swap + */ function swapTokensForExactTokens( uint256 amountOut, uint256 amountInMax, @@ -417,12 +490,15 @@ contract LBRouter is ILBRouter { } } - /// @notice Swaps tokens for exact AVAX while performing safety checks - /// @param amountAVAXOut The amount of AVAX to receive - /// @param amountInMax The max amount of token to send - /// @param to The address of the recipient - /// @param deadline The deadline of the tx - /// @return amountsIn path amounts for every step of the swap + /** + * @notice Swaps tokens for exact AVAX while performing safety checks + * @param amountAVAXOut The amount of AVAX to receive + * @param amountInMax The max amount of token to send + * @param path The path of the swap + * @param to The address of the recipient + * @param deadline The deadline of the tx + * @return amountsIn path amounts for every step of the swap + */ function swapTokensForExactAVAX( uint256 amountAVAXOut, uint256 amountInMax, @@ -450,12 +526,15 @@ contract LBRouter is ILBRouter { _safeTransferAVAX(to, _amountOutReal); } - /// @notice Swaps AVAX for exact tokens while performing safety checks - /// @dev Will refund any AVAX amount sent in excess to `msg.sender` - /// @param amountOut The amount of tokens to receive - /// @param to The address of the recipient - /// @param deadline The deadline of the tx - /// @return amountsIn path amounts for every step of the swap + /** + * @notice Swaps AVAX for exact tokens while performing safety checks + * @dev Will refund any AVAX amount sent in excess to `msg.sender` + * @param amountOut The amount of tokens to receive + * @param path The path of the swap + * @param to The address of the recipient + * @param deadline The deadline of the tx + * @return amountsIn path amounts for every step of the swap + */ function swapAVAXForExactTokens(uint256 amountOut, Path memory path, address to, uint256 deadline) external payable @@ -480,12 +559,15 @@ contract LBRouter is ILBRouter { if (msg.value > amountsIn[0]) _safeTransferAVAX(msg.sender, msg.value - amountsIn[0]); } - /// @notice Swaps exact tokens for tokens while performing safety checks supporting for fee on transfer tokens - /// @param amountIn The amount of token to send - /// @param amountOutMin The min amount of token to receive - /// @param to The address of the recipient - /// @param deadline The deadline of the tx - /// @return amountOut Output amount of the swap + /** + * @notice Swaps exact tokens for tokens while performing safety checks supporting for fee on transfer tokens + * @param amountIn The amount of token to send + * @param amountOutMin The min amount of token to receive + * @param path The path of the swap + * @param to The address of the recipient + * @param deadline The deadline of the tx + * @return amountOut Output amount of the swap + */ function swapExactTokensForTokensSupportingFeeOnTransferTokens( uint256 amountIn, uint256 amountOutMin, @@ -507,12 +589,15 @@ contract LBRouter is ILBRouter { if (amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMin, amountOut); } - /// @notice Swaps exact tokens for AVAX while performing safety checks supporting for fee on transfer tokens - /// @param amountIn The amount of token to send - /// @param amountOutMinAVAX The min amount of AVAX to receive - /// @param to The address of the recipient - /// @param deadline The deadline of the tx - /// @return amountOut Output amount of the swap + /** + * @notice Swaps exact tokens for AVAX while performing safety checks supporting for fee on transfer tokens + * @param amountIn The amount of token to send + * @param amountOutMinAVAX The min amount of AVAX to receive + * @param path The path of the swap + * @param to The address of the recipient + * @param deadline The deadline of the tx + * @return amountOut Output amount of the swap + */ function swapExactTokensForAVAXSupportingFeeOnTransferTokens( uint256 amountIn, uint256 amountOutMinAVAX, @@ -539,11 +624,14 @@ contract LBRouter is ILBRouter { _safeTransferAVAX(to, amountOut); } - /// @notice Swaps exact AVAX for tokens while performing safety checks supporting for fee on transfer tokens - /// @param amountOutMin The min amount of token to receive - /// @param to The address of the recipient - /// @param deadline The deadline of the tx - /// @return amountOut Output amount of the swap + /** + * @notice Swaps exact AVAX for tokens while performing safety checks supporting for fee on transfer tokens + * @param amountOutMin The min amount of token to receive + * @param path The path of the swap + * @param to The address of the recipient + * @param deadline The deadline of the tx + * @return amountOut Output amount of the swap + */ function swapExactAVAXForTokensSupportingFeeOnTransferTokens( uint256 amountOutMin, Path memory path, @@ -566,11 +654,13 @@ contract LBRouter is ILBRouter { if (amountOutMin > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMin, amountOut); } - /// @notice Unstuck tokens that are sent to this contract by mistake - /// @dev Only callable by the factory owner - /// @param token The address of the token - /// @param to The address of the user to send back the tokens - /// @param amount The amount to send + /** + * @notice Unstuck tokens that are sent to this contract by mistake + * @dev Only callable by the factory owner + * @param token The address of the token + * @param to The address of the user to send back the tokens + * @param amount The amount to send + */ function sweep(IERC20 token, address to, uint256 amount) external override onlyFactoryOwner { if (address(token) == address(0)) { if (amount == type(uint256).max) amount = address(this).balance; @@ -581,12 +671,14 @@ contract LBRouter is ILBRouter { } } - /// @notice Unstuck LBTokens that are sent to this contract by mistake - /// @dev Only callable by the factory owner - /// @param lbToken The address of the LBToken - /// @param to The address of the user to send back the tokens - /// @param ids The list of token ids - /// @param amounts The list of amounts to send + /** + * @notice Unstuck LBTokens that are sent to this contract by mistake + * @dev Only callable by the factory owner + * @param lbToken The address of the LBToken + * @param to The address of the user to send back the tokens + * @param ids The list of token ids + * @param amounts The list of amounts to send + */ function sweepLBToken(ILBToken lbToken, address to, uint256[] calldata ids, uint256[] calldata amounts) external override @@ -595,9 +687,17 @@ contract LBRouter is ILBRouter { lbToken.batchTransferFrom(address(this), to, ids, amounts); } - /// @notice Helper function to add liquidity - /// @param liq The liquidity parameter - /// @param pair LBPair where liquidity is deposited + /** + * @notice Helper function to add liquidity + * @param liq The liquidity parameter + * @param pair LBPair where liquidity is deposited + * @return amountXAdded Amount of token X added + * @return amountYAdded Amount of token Y added + * @return amountXLeft Amount of token X left + * @return amountYLeft Amount of token Y left + * @return depositIds The list of deposit ids + * @return liquidityMinted The list of liquidity minted + */ function _addLiquidity(LiquidityParameters calldata liq, ILBPair pair) private ensure(liq.deadline) @@ -656,11 +756,14 @@ contract LBRouter is ILBRouter { } } - /// @notice Helper function to return the amounts in - /// @param pairs The list of pairs - /// @param tokenPath The swap path - /// @param amountOut The amount out - /// @return amountsIn The list of amounts in + /** + * @notice Helper function to return the amounts in + * @param versions The list of versions (V1, V2 or V2_1) + * @param pairs The list of pairs + * @param tokenPath The swap path + * @param amountOut The amount out + * @return amountsIn The list of amounts in + */ function _getAmountsIn( Version[] memory versions, address[] memory pairs, @@ -695,15 +798,17 @@ contract LBRouter is ILBRouter { } } - /// @notice Helper function to remove liquidity - /// @param pair The address of the LBPair - /// @param amountXMin The min amount to receive of token X - /// @param amountYMin The min amount to receive of token Y - /// @param ids The list of ids to burn - /// @param amounts The list of amounts to burn of each id in `_ids` - /// @param to The address of the recipient - /// @return amountX The amount of token X sent by the pair - /// @return amountY The amount of token Y sent by the pair + /** + * @notice Helper function to remove liquidity + * @param pair The address of the LBPair + * @param amountXMin The min amount to receive of token X + * @param amountYMin The min amount to receive of token Y + * @param ids The list of ids to burn + * @param amounts The list of amounts to burn of each id in `_ids` + * @param to The address of the recipient + * @return amountX The amount of token X sent by the pair + * @return amountY The amount of token Y sent by the pair + */ function _removeLiquidity( ILBPair pair, uint256 amountXMin, @@ -724,12 +829,15 @@ contract LBRouter is ILBRouter { } } - /// @notice Helper function to swap exact tokens for tokens - /// @param amountIn The amount of token sent - /// @param pairs The list of pairs - /// @param tokenPath The swap path using the binSteps following `pairBinSteps` - /// @param to The address of the recipient - /// @return amountOut The amount of token sent to `to` + /** + * @notice Helper function to swap exact tokens for tokens + * @param amountIn The amount of token sent + * @param pairs The list of pairs + * @param versions The list of versions (V1, V2 or V2_1) + * @param tokenPath The swap path using the binSteps following `pairBinSteps` + * @param to The address of the recipient + * @return amountOut The amount of token sent to `to` + */ function _swapExactTokensForTokens( uint256 amountIn, address[] memory pairs, @@ -784,12 +892,15 @@ contract LBRouter is ILBRouter { } } - /// @notice Helper function to swap tokens for exact tokens - /// @param pairs The array of pairs - /// @param tokenPath The swap path using the binSteps following `pairBinSteps` - /// @param amountsIn The list of amounts in - /// @param to The address of the recipient - /// @return amountOut The amount of token sent to `to` + /** + * @notice Helper function to swap tokens for exact tokens + * @param pairs The array of pairs + * @param versions The list of versions (V1, V2 or V2_1) + * @param tokenPath The swap path using the binSteps following `pairBinSteps` + * @param amountsIn The list of amounts in + * @param to The address of the recipient + * @return amountOut The amount of token sent to `to` + */ function _swapTokensForExactTokens( address[] memory pairs, Version[] memory versions, @@ -840,10 +951,13 @@ contract LBRouter is ILBRouter { } } - /// @notice Helper function to swap exact tokens supporting for fee on transfer tokens - /// @param pairs The list of pairs - /// @param tokenPath The swap path using the binSteps following `pairBinSteps` - /// @param to The address of the recipient + /** + * @notice Helper function to swap exact tokens supporting for fee on transfer tokens + * @param pairs The list of pairs + * @param versions The list of versions (V1, V2 or V2_1) + * @param tokenPath The swap path using the binSteps following `pairBinSteps` + * @param to The address of the recipient + */ function _swapSupportingFeeOnTransferTokens( address[] memory pairs, Version[] memory versions, @@ -889,12 +1003,15 @@ contract LBRouter is ILBRouter { } } - /// @notice Helper function to return the address of the LBPair - /// @dev Revert if the pair is not created yet - /// @param tokenX The address of the tokenX - /// @param tokenY The address of the tokenY - /// @param binStep The bin step of the LBPair - /// @return lbPair The address of the LBPair + /** + * @notice Helper function to return the address of the LBPair + * @dev Revert if the pair is not created yet + * @param tokenX The address of the tokenX + * @param tokenY The address of the tokenY + * @param binStep The bin step of the LBPair + * @param version The version of the LBPair + * @return lbPair The address of the LBPair + */ function _getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep, Version version) private view @@ -911,12 +1028,15 @@ contract LBRouter is ILBRouter { } } - /// @notice Helper function to return the address of the pair (v1 or v2, according to `binStep`) - /// @dev Revert if the pair is not created yet - /// @param binStep The bin step of the LBPair, 0 means using V1 pair, any other value will use V2 - /// @param tokenX The address of the tokenX - /// @param tokenY The address of the tokenY - /// @return pair The address of the pair of binStep `binStep` + /** + * @notice Helper function to return the address of the pair (v1 or v2, according to `binStep`) + * @dev Revert if the pair is not created yet + * @param tokenX The address of the tokenX + * @param tokenY The address of the tokenY + * @param binStep The bin step of the LBPair + * @param version The version of the LBPair + * @return pair The address of the pair of binStep `binStep` + */ function _getPair(IERC20 tokenX, IERC20 tokenY, uint256 binStep, Version version) private view @@ -930,6 +1050,13 @@ contract LBRouter is ILBRouter { } } + /** + * @notice Helper function to return a list of pairs + * @param pairBinSteps The list of bin steps + * @param versions The list of versions (V1, V2 or V2_1) + * @param tokenPath The swap path using the binSteps following `pairBinSteps` + * @return pairs The list of pairs + */ function _getPairs(uint256[] memory pairBinSteps, Version[] memory versions, IERC20[] memory tokenPath) private view @@ -949,17 +1076,21 @@ contract LBRouter is ILBRouter { } } - /// @notice Helper function to transfer AVAX - /// @param to The address of the recipient - /// @param amount The AVAX amount to send + /** + * @notice Helper function to transfer AVAX + * @param to The address of the recipient + * @param amount The AVAX amount to send + */ function _safeTransferAVAX(address to, uint256 amount) private { (bool success,) = to.call{value: amount}(""); if (!success) revert LBRouter__FailedToSendAVAX(to, amount); } - /// @notice Helper function to deposit and transfer _wavax - /// @param to The address of the recipient - /// @param amount The AVAX amount to wrap + /** + * @notice Helper function to deposit and transfer _wavax + * @param to The address of the recipient + * @param amount The AVAX amount to wrap + */ function _wavaxDepositAndTransfer(address to, uint256 amount) private { _wavax.deposit{value: amount}(); _wavax.safeTransfer(to, amount); diff --git a/src/interfaces/ILBFactory.sol b/src/interfaces/ILBFactory.sol index 8f9747af..d0efec64 100644 --- a/src/interfaces/ILBFactory.sol +++ b/src/interfaces/ILBFactory.sol @@ -7,9 +7,11 @@ import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {ILBPair} from "./ILBPair.sol"; import {IPendingOwnable} from "./IPendingOwnable.sol"; -/// @title Liquidity Book Factory Interface -/// @author Trader Joe -/// @notice Required interface of LBFactory contract +/** + * @title Liquidity Book Factory Interface + * @author Trader Joe + * @notice Required interface of LBFactory contract + */ interface ILBFactory is IPendingOwnable { error LBFactory__IdenticalAddresses(IERC20 token); error LBFactory__QuoteAssetNotWhitelisted(IERC20 quoteAsset); @@ -36,13 +38,13 @@ interface ILBFactory is IPendingOwnable { error LBFactory__ImplementationNotSet(); error LBFactory__SamePresetOpenState(); - /// @dev Structure to store the LBPair information, such as: - /// - binStep: The bin step of the LBPair - /// - LBPair: The address of the LBPair - /// - createdByOwner: Whether the pair was created by the owner of the factory - /// - ignoredForRouting: Whether the pair is ignored for routing or not. An ignored pair will not be explored during routes finding - /// - revisionIndex: The revision index of the LBPair - /// - implementation: The implementation address of the LBPair + /** + * @dev Structure to store the LBPair information, such as: + * binStep: The bin step of the LBPair + * LBPair: The address of the LBPair + * createdByOwner: Whether the pair was created by the owner of the factory + * ignoredForRouting: Whether the pair is ignored for routing or not. An ignored pair will not be explored during routes finding + */ struct LBPairInformation { uint8 binStep; ILBPair LBPair; @@ -58,19 +60,6 @@ interface ILBFactory is IPendingOwnable { event FlashLoanFeeSet(uint256 oldFlashLoanFee, uint256 newFlashLoanFee); - event FeeParametersSet( - address indexed sender, - ILBPair indexed LBPair, - uint256 binStep, - uint256 baseFactor, - uint256 filterPeriod, - uint256 decayPeriod, - uint256 reductionFactor, - uint256 variableFeeControl, - uint256 protocolShare, - uint256 maxVolatilityAccumulator - ); - event LBPairImplementationSet(address oldLBPairImplementation, address LBPairImplementation); event LBPairIgnoredStateChanged(ILBPair indexed LBPair, bool ignored); @@ -94,19 +83,17 @@ interface ILBFactory is IPendingOwnable { event OpenPresetChanged(uint8 indexed binStep, bool open); - function getMaxFee() external pure returns (uint256); + function getMaxFlashLoanFee() external pure returns (uint256); function getMinBinStep() external pure returns (uint256); function getMaxBinStep() external pure returns (uint256); - function getMaxProtocolShare() external pure returns (uint256); - function getLBPairImplementation() external view returns (address); function getNumberOfQuoteAssets() external view returns (uint256); - function getQuoteAsset(uint256 index) external view returns (IERC20); + function getQuoteAssetAtIndex(uint256 index) external view returns (IERC20); function isQuoteAsset(IERC20 token) external view returns (bool); @@ -143,7 +130,7 @@ interface ILBFactory is IPendingOwnable { view returns (LBPairInformation[] memory LBPairsBinStep); - function setLBPairImplementation(address LBPairImplementation) external; + function setLBPairImplementation(address lbPairImplementation) external; function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint8 binStep) external @@ -185,5 +172,5 @@ interface ILBFactory is IPendingOwnable { function removeQuoteAsset(IERC20 quoteAsset) external; - function forceDecay(ILBPair LBPair) external; + function forceDecay(ILBPair lbPair) external; } diff --git a/src/interfaces/ILBLegacyFactory.sol b/src/interfaces/ILBLegacyFactory.sol index 4642233c..a6e12ad5 100644 --- a/src/interfaces/ILBLegacyFactory.sol +++ b/src/interfaces/ILBLegacyFactory.sol @@ -80,7 +80,7 @@ interface ILBLegacyFactory is IPendingOwnable { function getNumberOfQuoteAssets() external view returns (uint256); - function getQuoteAsset(uint256 index) external view returns (IERC20); + function getQuoteAssetAtIndex(uint256 index) external view returns (IERC20); function isQuoteAsset(IERC20 token) external view returns (bool); diff --git a/src/interfaces/ILBRouter.sol b/src/interfaces/ILBRouter.sol index 7e259df8..9cdc21a7 100644 --- a/src/interfaces/ILBRouter.sol +++ b/src/interfaces/ILBRouter.sol @@ -12,9 +12,11 @@ import {ILBPair} from "./ILBPair.sol"; import {ILBToken} from "./ILBToken.sol"; import {IWAVAX} from "./IWAVAX.sol"; -/// @title Liquidity Book Router Interface -/// @author Trader Joe -/// @notice Required interface of LBRouter contract +/** + * @title Liquidity Book Router Interface + * @author Trader Joe + * @notice Required interface of LBRouter contract + */ interface ILBRouter { error LBRouter__SenderIsNotWAVAX(); error LBRouter__PairNotCreated(address tokenX, address tokenY, uint256 binStep); @@ -41,28 +43,35 @@ interface ILBRouter { address tokenX, address tokenY, uint256 amountX, uint256 amountY, uint256 msgValue ); + /** + * @dev This enum represents the version of the pair requested + * - V1: Joe V1 pair + * - V2: LB pair V2. Also called legacyPair + * - V2_1: LB pair V2.1 (current version) + */ enum Version { V1, V2, V2_1 } - /// @dev The liquidity parameters, such as: - /// - tokenX: The address of token X - /// - tokenY: The address of token Y - /// - binStep: The bin step of the pair - /// - revision: The revision of the pair - /// - amountX: The amount to send of token X - /// - amountY: The amount to send of token Y - /// - amountXMin: The min amount of token X added to liquidity - /// - amountYMin: The min amount of token Y added to liquidity - /// - activeIdDesired: The active id that user wants to add liquidity from - /// - idSlippage: The number of id that are allowed to slip - /// - deltaIds: The list of delta ids to add liquidity (`deltaId = activeId - desiredId`) - /// - distributionX: The distribution of tokenX with sum(distributionX) = 100e18 (100%) or 0 (0%) - /// - distributionY: The distribution of tokenY with sum(distributionY) = 100e18 (100%) or 0 (0%) - /// - to: The address of the recipient - /// - deadline: The deadline of the tx + /** + * @dev The liquidity parameters, such as: + * - tokenX: The address of token X + * - tokenY: The address of token Y + * - binStep: The bin step of the pair + * - amountX: The amount to send of token X + * - amountY: The amount to send of token Y + * - amountXMin: The min amount of token X added to liquidity + * - amountYMin: The min amount of token Y added to liquidity + * - activeIdDesired: The active id that user wants to add liquidity from + * - idSlippage: The number of id that are allowed to slip + * - deltaIds: The list of delta ids to add liquidity (`deltaId = activeId - desiredId`) + * - distributionX: The distribution of tokenX with sum(distributionX) = 100e18 (100%) or 0 (0%) + * - distributionY: The distribution of tokenY with sum(distributionY) = 100e18 (100%) or 0 (0%) + * - to: The address of the recipient + * - deadline: The deadline of the tx + */ struct LiquidityParameters { IERC20 tokenX; IERC20 tokenY; @@ -81,10 +90,12 @@ interface ILBRouter { uint256 deadline; } - /// @dev The path parameters, such as: - /// - pairBinSteps: The list of bin steps of the pairs to go through - /// - revisions: The list of revisions of the pairs to go through - /// - tokenPath: The list of tokens in the path to go through + /** + * @dev The path parameters, such as: + * - pairBinSteps: The list of bin steps of the pairs to go through + * - versions: The list of versions of the pairs to go through + * - tokenPath: The list of tokens in the path to go through + */ struct Path { uint256[] pairBinSteps; Version[] versions; diff --git a/test/LBFactory.t.sol b/test/LBFactory.t.sol index c3299c23..fab72ffb 100644 --- a/test/LBFactory.t.sol +++ b/test/LBFactory.t.sol @@ -6,24 +6,22 @@ import "./helpers/TestHelper.sol"; import "src/libraries/ImmutableClone.sol"; -/* -* Test scenarios: -* 1. Constructor -* 2. Set LBPair implementation -* 3. Create LBPair -* 4. Create revision -* 5. Ignore LBPair for routing -* 6. Set preset -* 7. Remove preset -* 8. Set fee parameters on pair -* 9. Set fee recipient -* 10. Set flash loan fee -* 11. Set factory locked state -* 12. Add quote asset to whitelist -* 13. Remove quote asset from whitelist - -Invariant ideas: -- Presets*/ +/** + * Test scenarios: + * 1. Constructor + * 2. Set LBPair implementation + * 3. Create LBPair + * 4. Create revision + * 5. Ignore LBPair for routing + * 6. Set preset + * 7. Remove preset + * 8. Set fee parameters on pair + * 9. Set fee recipient + * 10. Set flash loan fee + * 11. Set factory locked state + * 12. Add quote asset to whitelist + * 13. Remove quote asset from whitelist + */ contract LiquidityBinFactoryTest is TestHelper { event QuoteAssetRemoved(IERC20 indexed _quoteAsset); @@ -32,7 +30,6 @@ contract LiquidityBinFactoryTest is TestHelper { event LBPairCreated( IERC20 indexed tokenX, IERC20 indexed tokenY, uint256 indexed binStep, ILBPair LBPair, uint256 pid ); - event StaticFeeParametersSet( address indexed sender, uint16 baseFactor, @@ -43,7 +40,6 @@ contract LiquidityBinFactoryTest is TestHelper { uint16 protocolShare, uint24 maxVolatilityAccumulator ); - event PresetSet( uint256 indexed binStep, uint256 baseFactor, @@ -54,40 +50,32 @@ contract LiquidityBinFactoryTest is TestHelper { uint256 protocolShare, uint256 maxVolatilityAccumulator ); - event LBPairIgnoredStateChanged(ILBPair indexed LBPair, bool ignored); - event PresetRemoved(uint256 indexed binStep); - event FeeRecipientSet(address oldRecipient, address newRecipient); - event FlashLoanFeeSet(uint256 oldFlashLoanFee, uint256 newFlashLoanFee); - - event FactoryLockedStatusUpdated(bool unlocked); - event OpenPresetChanged(uint8 indexed binStep, bool open); - struct LBPairInformation { - uint256 binStep; - ILBPair LBPair; - bool createdByOwner; - bool ignoredForRouting; - } - function setUp() public override { super.setUp(); } - function test_constructor() public { - assertEq(factory.getFeeRecipient(), DEV); - assertEq(factory.getFlashLoanFee(), DEFAULT_FLASHLOAN_FEE); + function test_Constructor() public { + assertEq(factory.getFeeRecipient(), DEV, "test_Constructor::1"); + assertEq(factory.getFlashLoanFee(), DEFAULT_FLASHLOAN_FEE, "test_Constructor::2"); + + assertEq(factory.getLBPairImplementation(), address(pairImplementation), "test_Constructor::3"); + assertEq(factory.getMinBinStep(), 1, "test_Constructor::4"); + assertEq(factory.getMaxBinStep(), 200, "test_Constructor::5"); + assertEq(factory.getFeeRecipient(), DEV, "test_Constructor::6"); + assertEq(factory.getMaxFlashLoanFee(), 0.1e18, "test_Constructor::7"); vm.expectEmit(true, true, true, true); emit FlashLoanFeeSet(0, DEFAULT_FLASHLOAN_FEE); new LBFactory(DEV, DEFAULT_FLASHLOAN_FEE); // Reverts if the flash loan fee is above the max fee - uint256 maxFee = factory.getMaxFee(); + uint256 maxFee = factory.getMaxFlashLoanFee(); vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__FlashLoanFeeAboveMax.selector, maxFee + 1, maxFee)); new LBFactory(DEV, maxFee + 1); } @@ -102,7 +90,7 @@ contract LiquidityBinFactoryTest is TestHelper { assertEq(factory.getLBPairImplementation(), address(newImplementation), "test_setLBPairImplementation:1"); } - function test_reverts_SetLBPairImplementation() public { + function test_revert_SetLBPairImplementation() public { ILBPair newImplementation = new LBPair(factory); factory.setLBPairImplementation(address(newImplementation)); @@ -127,7 +115,7 @@ contract LiquidityBinFactoryTest is TestHelper { factory.setLBPairImplementation(address(newImplementationForAnotherFactory)); } - function test_createLBPair() public { + function test_CreateLBPair() public { address expectedPairAddress = ImmutableClone.predictDeterministicAddress( address(pairImplementation), abi.encodePacked(usdt, usdc, DEFAULT_BIN_STEP), @@ -139,50 +127,51 @@ contract LiquidityBinFactoryTest is TestHelper { vm.expectEmit(true, true, true, true); emit LBPairCreated(usdt, usdc, DEFAULT_BIN_STEP, ILBPair(expectedPairAddress), 0); - // TODO - Check if can get the event from the pair - // vm.expectEmit(true, true, true, true); - // emit StaticFeeParametersSet( - // address(factory), - // DEFAULT_BASE_FACTOR, - // DEFAULT_FILTER_PERIOD, - // DEFAULT_DECAY_PERIOD, - // DEFAULT_REDUCTION_FACTOR, - // DEFAULT_VARIABLE_FEE_CONTROL, - // DEFAULT_PROTOCOL_SHARE, - // DEFAULT_MAX_VOLATILITY_ACCUMULATOR - // ); - ILBPair pair = factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); - assertEq(factory.getNumberOfLBPairs(), 1, "test_createLBPair::1"); + assertEq(factory.getNumberOfLBPairs(), 1, "test_CreateLBPair::1"); LBFactory.LBPairInformation memory pairInfo = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP); - assertEq(pairInfo.binStep, DEFAULT_BIN_STEP, "test_createLBPair::2"); - assertEq(address(pairInfo.LBPair), address(pair), "test_createLBPair::2"); - assertTrue(pairInfo.createdByOwner); - assertFalse(pairInfo.ignoredForRouting); - - assertEq(factory.getAllLBPairs(usdt, usdc).length, 1, "test_createLBPair::4"); - assertEq(address(factory.getAllLBPairs(usdt, usdc)[0].LBPair), address(pair), "test_createLBPair::5"); - - assertEq(address(pair.getFactory()), address(factory), "test_createLBPair::6"); - assertEq(address(pair.getTokenX()), address(usdt), "test_createLBPair::7"); - assertEq(address(pair.getTokenY()), address(usdc), "test_createLBPair::8"); - - // FeeHelper.FeeParameters memory feeParameters = pair.feeParameters(); - // assertEq(feeParameters.volatilityAccumulator, 0, "test_createLBPair::9"); - // assertEq(feeParameters.volatilityReference, 0, "test_createLBPair::10"); - // assertEq(feeParameters.indexRef, 0, "test_createLBPair::11"); - // assertEq(feeParameters.time, 0, "test_createLBPair::12"); - // assertEq(feeParameters.maxVolatilityAccumulator, DEFAULT_MAX_VOLATILITY_ACCUMULATOR, "test_createLBPair::13"); - // assertEq(feeParameters.filterPeriod, DEFAULT_FILTER_PERIOD, "test_createLBPair::14"); - // assertEq(feeParameters.decayPeriod, DEFAULT_DECAY_PERIOD, "test_createLBPair::15"); - // assertEq(feeParameters.binStep, DEFAULT_BIN_STEP, "test_createLBPair::16"); - // assertEq(feeParameters.baseFactor, DEFAULT_BASE_FACTOR, "test_createLBPair::17"); - // assertEq(feeParameters.protocolShare, DEFAULT_PROTOCOL_SHARE, "test_createLBPair::18"); + assertEq(pairInfo.binStep, DEFAULT_BIN_STEP, "test_CreateLBPair::2"); + assertEq(address(pairInfo.LBPair), address(pair), "test_CreateLBPair::3"); + assertTrue(pairInfo.createdByOwner, "test_CreateLBPair::4"); + assertFalse(pairInfo.ignoredForRouting, "test_CreateLBPair::5"); + + assertEq(factory.getAllLBPairs(usdt, usdc).length, 1, "test_CreateLBPair::6"); + assertEq(address(factory.getAllLBPairs(usdt, usdc)[0].LBPair), address(pair), "test_CreateLBPair::7"); + + assertEq(address(pair.getFactory()), address(factory), "test_CreateLBPair::8"); + assertEq(address(pair.getTokenX()), address(usdt), "test_CreateLBPair::9"); + assertEq(address(pair.getTokenY()), address(usdc), "test_CreateLBPair::10"); + + ( + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulator + ) = pair.getStaticFeeParameters(); + + assertEq(baseFactor, DEFAULT_BASE_FACTOR, "test_createLBPair::11"); + assertEq(filterPeriod, DEFAULT_FILTER_PERIOD, "test_createLBPair::12"); + assertEq(decayPeriod, DEFAULT_DECAY_PERIOD, "test_createLBPair::13"); + assertEq(reductionFactor, DEFAULT_REDUCTION_FACTOR, "test_createLBPair::14"); + assertEq(variableFeeControl, DEFAULT_VARIABLE_FEE_CONTROL, "test_createLBPair::15"); + assertEq(protocolShare, DEFAULT_PROTOCOL_SHARE, "test_createLBPair::16"); + assertEq(maxVolatilityAccumulator, DEFAULT_MAX_VOLATILITY_ACCUMULATOR, "test_createLBPair::17"); + + (uint24 volatilityAccumulator, uint24 volatilityReference, uint24 idReference, uint40 timeOfLastUpdate) = + pair.getVariableFeeParameters(); + + assertEq(volatilityAccumulator, 0, "test_createLBPair::18"); + assertEq(volatilityReference, 0, "test_createLBPair::19"); + assertEq(idReference, ID_ONE, "test_createLBPair::20"); + assertEq(timeOfLastUpdate, 0, "test_createLBPair::21"); } - function test_createLBPairFactoryUnlocked() public { + function test_CreateLBPairFactoryUnlocked() public { // Users should not be able to create pairs by default vm.prank(ALICE); vm.expectRevert( @@ -196,16 +185,25 @@ contract LiquidityBinFactoryTest is TestHelper { vm.prank(ALICE); factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); - assertFalse(factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP).createdByOwner); + assertFalse( + factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP).createdByOwner, + "test_CreateLBPairFactoryUnlocked::1" + ); vm.prank(BOB); factory.createLBPair(weth, usdc, ID_ONE, DEFAULT_BIN_STEP); - assertFalse(factory.getLBPairInformation(weth, usdc, DEFAULT_BIN_STEP).createdByOwner); + assertFalse( + factory.getLBPairInformation(weth, usdc, DEFAULT_BIN_STEP).createdByOwner, + "test_CreateLBPairFactoryUnlocked::2" + ); factory.createLBPair(bnb, usdc, ID_ONE, DEFAULT_BIN_STEP); - assertTrue(factory.getLBPairInformation(bnb, usdc, DEFAULT_BIN_STEP).createdByOwner); + assertTrue( + factory.getLBPairInformation(bnb, usdc, DEFAULT_BIN_STEP).createdByOwner, + "test_CreateLBPairFactoryUnlocked::3" + ); // Should close pair creations again factory.setOpenPreset(DEFAULT_BIN_STEP, false); @@ -219,7 +217,7 @@ contract LiquidityBinFactoryTest is TestHelper { factory.createLBPair(wbtc, usdc, ID_ONE, DEFAULT_BIN_STEP); } - function test_reverts_createLBPair() public { + function test_revert_CreateLBPair() public { // Alice can't create a pair if the factory is locked vm.prank(ALICE); vm.expectRevert( @@ -242,7 +240,7 @@ contract LiquidityBinFactoryTest is TestHelper { vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__IdenticalAddresses.selector, usdc)); newFactory.createLBPair(usdc, usdc, ID_ONE, DEFAULT_BIN_STEP); - // Can't create a pair with an invalid bin step + // TODO Can't create a pair with an invalid bin step // vm.expectRevert(abi.encodeWithSelector(ILBFactory.BinHelper__BinStepOverflows.selector, type(uint16).max)); // newFactory.createLBPair(usdt, usdc, ID_ONE, type(uint16).max); @@ -273,7 +271,7 @@ contract LiquidityBinFactoryTest is TestHelper { newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); } - function test_setLBPairIgnored() public { + function test_SetLBPairIgnoredForRouting() public { ILBPair pair = factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); // Ignoring the USDT-USDC rev 2 pair @@ -282,8 +280,8 @@ contract LiquidityBinFactoryTest is TestHelper { factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, true); ILBFactory.LBPairInformation memory pairInfo = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP); - assertEq(address(pairInfo.LBPair), address(pair), "test_setLBPairIgnored::0"); - assertEq(pairInfo.ignoredForRouting, true, "test_setLBPairIgnored::1"); + assertEq(address(pairInfo.LBPair), address(pair), "test_SetLBPairIgnoredForRouting::1"); + assertEq(pairInfo.ignoredForRouting, true, "test_SetLBPairIgnoredForRouting::2"); // Put it back to normal vm.expectEmit(true, true, true, true); @@ -293,11 +291,11 @@ contract LiquidityBinFactoryTest is TestHelper { assertEq( factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP).ignoredForRouting, false, - "test_setLBPairIgnored::1" + "test_SetLBPairIgnoredForRouting::3" ); } - function test_reverts_setLBPairIgnored() public { + function test_revert_SetLBPairIgnoredForRouting() public { // Can't ignore for routing if not the owner vm.prank(ALICE); vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); @@ -319,7 +317,7 @@ contract LiquidityBinFactoryTest is TestHelper { factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, true); } - function todoTestFuzz_setPreset( + function testFuzz_SetPreset( uint8 binStep, uint16 baseFactor, uint16 filterPeriod, @@ -330,205 +328,71 @@ contract LiquidityBinFactoryTest is TestHelper { uint24 maxVolatilityAccumulator ) public { binStep = uint8(bound(binStep, factory.getMinBinStep(), factory.getMaxBinStep())); - filterPeriod = uint16(bound(filterPeriod, 0, type(uint16).max - 1)); - decayPeriod = uint16(bound(decayPeriod, filterPeriod + 1, type(uint16).max)); + filterPeriod = uint16(bound(filterPeriod, 0, Encoded.MASK_UINT12 - 1)); + decayPeriod = uint16(bound(decayPeriod, filterPeriod + 1, Encoded.MASK_UINT12)); reductionFactor = uint16(bound(reductionFactor, 0, Constants.BASIS_POINT_MAX)); - protocolShare = uint16(bound(protocolShare, 0, factory.getMaxProtocolShare())); variableFeeControl = uint24(bound(variableFeeControl, 0, Constants.BASIS_POINT_MAX)); + protocolShare = uint16(bound(protocolShare, 0, 2_500)); + maxVolatilityAccumulator = uint24(bound(maxVolatilityAccumulator, 0, Encoded.MASK_UINT20)); - // TODO: maxVolatilityAccumulator should be bounded but that's quite hard to calculate - uint256 totalFeesMax; - { - uint256 baseFee = (uint256(baseFactor) * binStep) * 1e10; - uint256 prod = uint256(maxVolatilityAccumulator) * binStep; - uint256 maxVariableFee = (prod * prod * variableFeeControl) / 100; - totalFeesMax = baseFee + maxVariableFee; - } - - if (totalFeesMax > factory.getMaxFee()) { - vm.expectRevert(); - factory.setPreset( - binStep, - baseFactor, - filterPeriod, - decayPeriod, - reductionFactor, - variableFeeControl, - protocolShare, - maxVolatilityAccumulator - ); - } else { - vm.expectEmit(true, true, true, true); - emit PresetSet( - binStep, - baseFactor, - filterPeriod, - decayPeriod, - reductionFactor, - variableFeeControl, - protocolShare, - maxVolatilityAccumulator - ); - - factory.setPreset( - binStep, - baseFactor, - filterPeriod, - decayPeriod, - reductionFactor, - variableFeeControl, - protocolShare, - maxVolatilityAccumulator + vm.expectEmit(true, true, true, true); + emit PresetSet( + binStep, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulator ); - // Bin step DEFAULT_BIN_STEP is already there - if (binStep != DEFAULT_BIN_STEP) { - assertEq(factory.getAllBinSteps().length, 2, "1"); - if (binStep < DEFAULT_BIN_STEP) { - assertEq(factory.getAllBinSteps()[0], binStep, "2"); - } else { - assertEq(factory.getAllBinSteps()[1], binStep, "3"); - } - } else { - assertEq(factory.getAllBinSteps().length, 1, "3"); - assertEq(factory.getAllBinSteps()[0], binStep, "4"); - } + factory.setPreset( + binStep, + baseFactor, + filterPeriod, + decayPeriod, + reductionFactor, + variableFeeControl, + protocolShare, + maxVolatilityAccumulator + ); - // Check splitted in two to avoid stack too deep errors - { - ( - uint256 baseFactorView, - uint256 filterPeriodView, - uint256 decayPeriodView, - uint256 reductionFactorView, - , - , - ) = factory.getPreset(binStep); - - assertEq(baseFactorView, baseFactor); - assertEq(filterPeriodView, filterPeriod); - assertEq(decayPeriodView, decayPeriod); - assertEq(reductionFactorView, reductionFactor); + // Bin step DEFAULT_BIN_STEP is already there + if (binStep != DEFAULT_BIN_STEP) { + assertEq(factory.getAllBinSteps().length, 2, "1"); + if (binStep < DEFAULT_BIN_STEP) { + assertEq(factory.getAllBinSteps()[0], binStep, "2"); + } else { + assertEq(factory.getAllBinSteps()[1], binStep, "3"); } + } else { + assertEq(factory.getAllBinSteps().length, 1, "3"); + assertEq(factory.getAllBinSteps()[0], binStep, "4"); + } - { - (,,,, uint256 variableFeeControlView, uint256 protocolShareView, uint256 maxVolatilityAccumulatorView) = - factory.getPreset(binStep); + // Check splitted in two to avoid stack too deep errors + { + (uint256 baseFactorView, uint256 filterPeriodView, uint256 decayPeriodView, uint256 reductionFactorView,,,) + = factory.getPreset(binStep); - assertEq(variableFeeControlView, variableFeeControl); - assertEq(protocolShareView, protocolShare); - assertEq(maxVolatilityAccumulatorView, maxVolatilityAccumulator); - } + assertEq(baseFactorView, baseFactor); + assertEq(filterPeriodView, filterPeriod); + assertEq(decayPeriodView, decayPeriod); + assertEq(reductionFactorView, reductionFactor); } - } - // TODO - check after refactoring the checks on fee parameters - function todoTestFuzz_reverts_setPreset( - uint8 binStep, - uint16 baseFactor, - uint16 filterPeriod, - uint16 decayPeriod, - uint16 reductionFactor, - uint24 variableFeeControl, - uint16 protocolShare, - uint24 maxVolatilityAccumulator - ) public { - uint256 baseFee = (uint256(baseFactor) * binStep) * 1e10; - uint256 prod = uint256(maxVolatilityAccumulator) * binStep; - uint256 maxVariableFee = (prod * prod * variableFeeControl) / 100; - - if (binStep < factory.getMinBinStep() || binStep > factory.getMaxBinStep()) { - vm.expectRevert( - abi.encodeWithSelector( - ILBFactory.LBFactory__BinStepRequirementsBreached.selector, - factory.getMinBinStep(), - binStep, - factory.getMaxBinStep() - ) - ); - factory.setPreset( - binStep, - baseFactor, - filterPeriod, - decayPeriod, - reductionFactor, - variableFeeControl, - protocolShare, - maxVolatilityAccumulator - ); - } else if (filterPeriod >= decayPeriod) { - vm.expectRevert( - abi.encodeWithSelector(ILBFactory.LBFactory__DecreasingPeriods.selector, filterPeriod, decayPeriod) - ); - factory.setPreset( - binStep, - baseFactor, - filterPeriod, - decayPeriod, - reductionFactor, - variableFeeControl, - protocolShare, - maxVolatilityAccumulator - ); - } else if (reductionFactor > Constants.BASIS_POINT_MAX) { - vm.expectRevert( - abi.encodeWithSelector( - ILBFactory.LBFactory__ReductionFactorOverflows.selector, reductionFactor, Constants.BASIS_POINT_MAX - ) - ); - factory.setPreset( - binStep, - baseFactor, - filterPeriod, - decayPeriod, - reductionFactor, - variableFeeControl, - protocolShare, - maxVolatilityAccumulator - ); - } else if (protocolShare > factory.getMaxProtocolShare()) { - vm.expectRevert( - abi.encodeWithSelector( - ILBFactory.LBFactory__ProtocolShareOverflows.selector, protocolShare, factory.getMaxProtocolShare() - ) - ); - factory.setPreset( - binStep, - baseFactor, - filterPeriod, - decayPeriod, - reductionFactor, - variableFeeControl, - protocolShare, - maxVolatilityAccumulator - ); - } else if (baseFee + maxVariableFee > factory.getMaxFee()) { - vm.expectRevert(); - factory.setPreset( - binStep, - baseFactor, - filterPeriod, - decayPeriod, - reductionFactor, - variableFeeControl, - protocolShare, - maxVolatilityAccumulator - ); - } else { - factory.setPreset( - binStep, - baseFactor, - filterPeriod, - decayPeriod, - reductionFactor, - variableFeeControl, - protocolShare, - maxVolatilityAccumulator - ); + { + (,,,, uint256 variableFeeControlView, uint256 protocolShareView, uint256 maxVolatilityAccumulatorView) = + factory.getPreset(binStep); + + assertEq(variableFeeControlView, variableFeeControl); + assertEq(protocolShareView, protocolShare); + assertEq(maxVolatilityAccumulatorView, maxVolatilityAccumulator); } } - function test_removePreset() public { + function test_RemovePreset() public { factory.setPreset( DEFAULT_BIN_STEP + 1, DEFAULT_BASE_FACTOR, @@ -551,13 +415,13 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_MAX_VOLATILITY_ACCUMULATOR ); - assertEq(factory.getAllBinSteps().length, 3); + assertEq(factory.getAllBinSteps().length, 3, "test_RemovePreset::1"); vm.expectEmit(true, true, true, true); emit PresetRemoved(DEFAULT_BIN_STEP); factory.removePreset(DEFAULT_BIN_STEP); - assertEq(factory.getAllBinSteps().length, 2); + assertEq(factory.getAllBinSteps().length, 2, "test_RemovePreset::2"); // getPreset should revert for the removed bin step vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__BinStepHasNoPreset.selector, DEFAULT_BIN_STEP)); @@ -573,74 +437,106 @@ contract LiquidityBinFactoryTest is TestHelper { factory.removePreset(DEFAULT_BIN_STEP); } - function test_setFeesParametersOnPair() public { - uint16 newBaseFactor = DEFAULT_BASE_FACTOR * 2; - uint16 newFilterPeriod = DEFAULT_FILTER_PERIOD * 2; - uint16 newDecayPeriod = DEFAULT_DECAY_PERIOD * 2; - uint16 newReductionFactor = DEFAULT_REDUCTION_FACTOR * 2; - uint24 newVariableFeeControl = DEFAULT_VARIABLE_FEE_CONTROL * 2; - uint16 newProtocolShare = DEFAULT_PROTOCOL_SHARE * 2; - uint24 newMaxVolatilityAccumulator = DEFAULT_MAX_VOLATILITY_ACCUMULATOR * 2; + function test_SetFeesParametersOnPair() public { + ILBPair pair = factory.createLBPair(wavax, usdc, ID_ONE, DEFAULT_BIN_STEP); + addLiquidity(DEV, DEV, LBPair(address(pair)), ID_ONE, 100e18, 100e18, 10, 10); - factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + // Do swaps to increase the variable fee parameters + { + deal(address(usdc), DEV, 60e18); + ILBRouter.Path memory path; + path.pairBinSteps = new uint256[](1); + path.pairBinSteps[0] = DEFAULT_BIN_STEP; + + path.versions = new ILBRouter.Version[](1); + path.versions[0] = ILBRouter.Version.V2_1; + + path.tokenPath = new IERC20[](2); + path.tokenPath[0] = usdc; + path.tokenPath[1] = wavax; + router.swapExactTokensForTokens(50e18, 0, path, address(this), block.timestamp + 1); + vm.warp(100); + router.swapExactTokensForTokens(10e18, 0, path, address(this), block.timestamp + 1); + } - // FeeHelper.FeeParameters memory oldFeeParameters = pair.feeParameters(); + ( + uint24 oldVolatilityAccumulator, + uint24 oldVolatilityReference, + uint24 oldIdReference, + uint40 oldTimeOfLastUpdate + ) = pair.getVariableFeeParameters(); vm.expectEmit(true, true, true, true); emit StaticFeeParametersSet( address(factory), - newBaseFactor, - newFilterPeriod, - newDecayPeriod, - newReductionFactor, - newVariableFeeControl, - newProtocolShare, - newMaxVolatilityAccumulator + DEFAULT_BASE_FACTOR * 2, + DEFAULT_FILTER_PERIOD * 2, + DEFAULT_DECAY_PERIOD * 2, + DEFAULT_REDUCTION_FACTOR * 2, + DEFAULT_VARIABLE_FEE_CONTROL * 2, + DEFAULT_PROTOCOL_SHARE * 2, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR * 2 ); factory.setFeesParametersOnPair( - usdt, + wavax, usdc, DEFAULT_BIN_STEP, - newBaseFactor, - newFilterPeriod, - newDecayPeriod, - newReductionFactor, - newVariableFeeControl, - newProtocolShare, - newMaxVolatilityAccumulator + DEFAULT_BASE_FACTOR * 2, + DEFAULT_FILTER_PERIOD * 2, + DEFAULT_DECAY_PERIOD * 2, + DEFAULT_REDUCTION_FACTOR * 2, + DEFAULT_VARIABLE_FEE_CONTROL * 2, + DEFAULT_PROTOCOL_SHARE * 2, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR * 2 ); - // FeeHelper.FeeParameters memory feeParameters = pair.feeParameters(); - // // Paramters should be updated - // assertEq(feeParameters.baseFactor, newBaseFactor); - // assertEq(feeParameters.filterPeriod, newFilterPeriod); - // assertEq(feeParameters.decayPeriod, newDecayPeriod); - // assertEq(feeParameters.reductionFactor, newReductionFactor); - // assertEq(feeParameters.variableFeeControl, newVariableFeeControl); - // assertEq(feeParameters.protocolShare, newProtocolShare); - // assertEq(feeParameters.maxVolatilityAccumulator, newMaxVolatilityAccumulator); - - // // Rest of the fee parameters slot should be the same - // assertEq(feeParameters.volatilityAccumulator, oldFeeParameters.volatilityAccumulator); - // assertEq(feeParameters.volatilityReference, oldFeeParameters.volatilityReference); - // assertEq(feeParameters.indexRef, oldFeeParameters.indexRef); - // assertEq(feeParameters.time, oldFeeParameters.time); + { + ( + uint16 baseFactor, + uint16 filterPeriod, + uint16 decayPeriod, + uint16 reductionFactor, + uint24 variableFeeControl, + uint16 protocolShare, + uint24 maxVolatilityAccumulator + ) = pair.getStaticFeeParameters(); + + assertEq(baseFactor, DEFAULT_BASE_FACTOR * 2, "test_SetFeesParametersOnPair::1"); + assertEq(filterPeriod, DEFAULT_FILTER_PERIOD * 2, "test_SetFeesParametersOnPair::2"); + assertEq(decayPeriod, DEFAULT_DECAY_PERIOD * 2, "test_SetFeesParametersOnPair::3"); + assertEq(reductionFactor, DEFAULT_REDUCTION_FACTOR * 2, "test_SetFeesParametersOnPair::4"); + assertEq(variableFeeControl, DEFAULT_VARIABLE_FEE_CONTROL * 2, "test_SetFeesParametersOnPair::5"); + assertEq(protocolShare, DEFAULT_PROTOCOL_SHARE * 2, "test_SetFeesParametersOnPair::6"); + assertEq( + maxVolatilityAccumulator, DEFAULT_MAX_VOLATILITY_ACCUMULATOR * 2, "test_SetFeesParametersOnPair::7" + ); + } + + { + (uint24 volatilityAccumulator, uint24 volatilityReference, uint24 idReference, uint40 timeOfLastUpdate) = + pair.getVariableFeeParameters(); + + assertEq(volatilityAccumulator, oldVolatilityAccumulator, "test_SetFeesParametersOnPair::8"); + assertEq(volatilityReference, oldVolatilityReference, "test_SetFeesParametersOnPair::9"); + assertEq(idReference, oldIdReference, "test_SetFeesParametersOnPair::10"); + assertEq(timeOfLastUpdate, oldTimeOfLastUpdate, "test_SetFeesParametersOnPair::11"); + } // Can't update if not the owner vm.prank(ALICE); vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); factory.setFeesParametersOnPair( - usdt, + wavax, usdc, DEFAULT_BIN_STEP, - newBaseFactor, - newFilterPeriod, - newDecayPeriod, - newReductionFactor, - newVariableFeeControl, - newProtocolShare, - newMaxVolatilityAccumulator + DEFAULT_BASE_FACTOR * 2, + DEFAULT_FILTER_PERIOD * 2, + DEFAULT_DECAY_PERIOD * 2, + DEFAULT_REDUCTION_FACTOR * 2, + DEFAULT_VARIABLE_FEE_CONTROL * 2, + DEFAULT_PROTOCOL_SHARE * 2, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR * 2 ); // Can't update a pair that does not exist @@ -651,22 +547,22 @@ contract LiquidityBinFactoryTest is TestHelper { weth, usdc, DEFAULT_BIN_STEP, - newBaseFactor, - newFilterPeriod, - newDecayPeriod, - newReductionFactor, - newVariableFeeControl, - newProtocolShare, - newMaxVolatilityAccumulator + DEFAULT_BASE_FACTOR * 2, + DEFAULT_FILTER_PERIOD * 2, + DEFAULT_DECAY_PERIOD * 2, + DEFAULT_REDUCTION_FACTOR * 2, + DEFAULT_VARIABLE_FEE_CONTROL * 2, + DEFAULT_PROTOCOL_SHARE * 2, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR * 2 ); } - function test_setFeeRecipient() public { + function test_SetFeeRecipient() public { vm.expectEmit(true, true, true, true); emit FeeRecipientSet(address(this), ALICE); factory.setFeeRecipient(ALICE); - assertEq(factory.getFeeRecipient(), ALICE); + assertEq(factory.getFeeRecipient(), ALICE, "test_SetFeeRecipient::1"); // Can't set if not the owner vm.prank(BOB); @@ -682,13 +578,13 @@ contract LiquidityBinFactoryTest is TestHelper { factory.setFeeRecipient(ALICE); } - function test_setFlashLoanFee() public { + function test_SetFlashLoanFee() public { uint256 newFlashLoanFee = 1_000; vm.expectEmit(true, true, true, true); emit FlashLoanFeeSet(DEFAULT_FLASHLOAN_FEE, newFlashLoanFee); factory.setFlashLoanFee(newFlashLoanFee); - assertEq(factory.getFlashLoanFee(), newFlashLoanFee); + assertEq(factory.getFlashLoanFee(), newFlashLoanFee, "test_SetFlashLoanFee::1"); // Can't set if not the owner vm.prank(ALICE); @@ -700,7 +596,7 @@ contract LiquidityBinFactoryTest is TestHelper { factory.setFlashLoanFee(newFlashLoanFee); // Can't set to a fee greater than the maximum - uint256 maxFlashLoanFee = factory.getMaxFee(); + uint256 maxFlashLoanFee = factory.getMaxFlashLoanFee(); vm.expectRevert( abi.encodeWithSelector( ILBFactory.LBFactory__FlashLoanFeeAboveMax.selector, maxFlashLoanFee + 1, maxFlashLoanFee @@ -709,14 +605,14 @@ contract LiquidityBinFactoryTest is TestHelper { factory.setFlashLoanFee(maxFlashLoanFee + 1); } - function testFuzz_openPresets(uint8 binStep) public { + function testFuzz_OpenPresets(uint8 binStep) public { uint256 minBinStep = factory.getMinBinStep(); uint256 maxBinStep = factory.getMaxBinStep(); binStep = uint8(bound(binStep, minBinStep, maxBinStep)); // Preset are not open to the public by default - assertFalse(factory.getIsPresetOpen(binStep)); + assertFalse(factory.isPresetOpen(binStep), "testFuzz_OpenPresets::1"); // Can be opened vm.expectEmit(true, true, true, true); @@ -725,9 +621,9 @@ contract LiquidityBinFactoryTest is TestHelper { for (uint256 i = minBinStep; i < maxBinStep; i++) { if (i == binStep) { - assertTrue(factory.getIsPresetOpen(uint8(i))); + assertTrue(factory.isPresetOpen(uint8(i)), "testFuzz_OpenPresets::2"); } else { - assertFalse(factory.getIsPresetOpen(uint8(i))); + assertFalse(factory.isPresetOpen(uint8(i)), "testFuzz_OpenPresets::3"); } } @@ -738,9 +634,9 @@ contract LiquidityBinFactoryTest is TestHelper { factory.setOpenPreset(nextBinStep, true); factory.setOpenPreset(previousBinStep, true); - assertTrue(factory.getIsPresetOpen(binStep)); - assertTrue(factory.getIsPresetOpen(nextBinStep)); - assertTrue(factory.getIsPresetOpen(previousBinStep)); + assertTrue(factory.isPresetOpen(binStep), "testFuzz_OpenPresets::4"); + assertTrue(factory.isPresetOpen(nextBinStep), "testFuzz_OpenPresets::5"); + assertTrue(factory.isPresetOpen(previousBinStep), "testFuzz_OpenPresets::6"); // Can't set to the same state vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__SamePresetOpenState.selector)); @@ -751,9 +647,9 @@ contract LiquidityBinFactoryTest is TestHelper { emit OpenPresetChanged(binStep, false); factory.setOpenPreset(binStep, false); - assertFalse(factory.getIsPresetOpen(binStep)); - assertTrue(factory.getIsPresetOpen(nextBinStep)); - assertTrue(factory.getIsPresetOpen(previousBinStep)); + assertFalse(factory.isPresetOpen(binStep), "testFuzz_OpenPresets::7"); + assertTrue(factory.isPresetOpen(nextBinStep), "testFuzz_OpenPresets::8"); + assertTrue(factory.isPresetOpen(previousBinStep), "testFuzz_OpenPresets::9"); // Can't open if not the owner vm.prank(ALICE); @@ -765,20 +661,22 @@ contract LiquidityBinFactoryTest is TestHelper { factory.setOpenPreset(binStep, false); } - function test_addQuoteAsset() public { + function test_AddQuoteAsset() public { uint256 numberOfQuoteAssetBefore = factory.getNumberOfQuoteAssets(); IERC20 newToken = new ERC20Mock(18); - assertEq(factory.isQuoteAsset(newToken), false); + assertEq(factory.isQuoteAsset(newToken), false, "test_AddQuoteAsset::1"); vm.expectEmit(true, true, true, true); emit QuoteAssetAdded(newToken); factory.addQuoteAsset(newToken); - assertEq(factory.isQuoteAsset(newToken), true); - assertEq(factory.getNumberOfQuoteAssets(), numberOfQuoteAssetBefore + 1); - assertEq(address(newToken), address(factory.getQuoteAsset(numberOfQuoteAssetBefore))); + assertEq(factory.isQuoteAsset(newToken), true, "test_AddQuoteAsset::2"); + assertEq(factory.getNumberOfQuoteAssets(), numberOfQuoteAssetBefore + 1, "test_AddQuoteAsset::3"); + assertEq( + address(newToken), address(factory.getQuoteAssetAtIndex(numberOfQuoteAssetBefore)), "test_AddQuoteAsset::4" + ); // Can't add if not the owner vm.prank(ALICE); @@ -790,17 +688,17 @@ contract LiquidityBinFactoryTest is TestHelper { factory.addQuoteAsset(newToken); } - function test_removeQuoteAsset() public { + function test_RemoveQuoteAsset() public { uint256 numberOfQuoteAssetBefore = factory.getNumberOfQuoteAssets(); - assertEq(factory.isQuoteAsset(usdc), true); + assertEq(factory.isQuoteAsset(usdc), true, "test_RemoveQuoteAsset::1"); vm.expectEmit(true, true, true, true); emit QuoteAssetRemoved(usdc); factory.removeQuoteAsset(usdc); - assertEq(factory.isQuoteAsset(usdc), false); - assertEq(factory.getNumberOfQuoteAssets(), numberOfQuoteAssetBefore - 1); + assertEq(factory.isQuoteAsset(usdc), false, "test_RemoveQuoteAsset::2"); + assertEq(factory.getNumberOfQuoteAssets(), numberOfQuoteAssetBefore - 1, "test_RemoveQuoteAsset::3"); // Can't remove if not the owner vm.prank(ALICE); @@ -812,7 +710,7 @@ contract LiquidityBinFactoryTest is TestHelper { factory.removeQuoteAsset(usdc); } - function test_forceDecay() public { + function test_ForceDecay() public { ILBPair pair = factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); factory.forceDecay(pair); @@ -823,7 +721,7 @@ contract LiquidityBinFactoryTest is TestHelper { factory.forceDecay(pair); } - function test_getAllLBPairs() public { + function test_GetAllLBPairs() public { /* Create pairs: - WETH/USDC with bin step = 5 - WETH/USDC with bin step = 20 @@ -858,14 +756,14 @@ contract LiquidityBinFactoryTest is TestHelper { ILBFactory.LBPairInformation[] memory LBPairsAvailable = factory.getAllLBPairs(weth, usdc); - assertEq(LBPairsAvailable.length, 2); + assertEq(LBPairsAvailable.length, 2, "test_GetAllLBPairs::1"); ILBFactory.LBPairInformation memory pair1Info = LBPairsAvailable[0]; - assertEq(address(pair1Info.LBPair), address(pair1)); - assertEq(pair1Info.binStep, 5); + assertEq(address(pair1Info.LBPair), address(pair1), "test_GetAllLBPairs::2"); + assertEq(pair1Info.binStep, 5, "test_GetAllLBPairs::3"); ILBFactory.LBPairInformation memory pair2Info = LBPairsAvailable[1]; - assertEq(address(pair2Info.LBPair), address(pair2)); - assertEq(pair2Info.binStep, 20); + assertEq(address(pair2Info.LBPair), address(pair2), "test_GetAllLBPairs::4"); + assertEq(pair2Info.binStep, 20, "test_GetAllLBPairs::5"); } } diff --git a/test/LBRouter.Liquidity.t.sol b/test/LBRouter.Liquidity.t.sol index 08e15b91..0c8f778a 100644 --- a/test/LBRouter.Liquidity.t.sol +++ b/test/LBRouter.Liquidity.t.sol @@ -6,14 +6,14 @@ import "test/helpers/TestHelper.sol"; /** * Test scenarios: - * 2. Receive - * 3. Create LBPair - * 4. Add Liquidity - * 5. Add liquidity AVAX - * 6. Remove liquidity - * 7. Remove liquidity AVAX - * 8. Sweep ERC20s - * 9. Sweep LBToken + * 1. Receive + * 2. Create LBPair + * 3. Add Liquidity + * 4. Add liquidity AVAX + * 5. Remove liquidity + * 6. Remove liquidity AVAX + * 7. Sweep ERC20s + * 8. Sweep LBToken */ contract LiquidityBinRouterTest is TestHelper { bool blockReceive; @@ -35,6 +35,14 @@ contract LiquidityBinRouterTest is TestHelper { deal(address(weth), address(this), startingBalance); } + function test_Constructor() public { + assertEq(address(router.getFactory()), address(factory), "test_Constructor::1"); + assertEq(address(router.getLegacyFactory()), address(legacyFactoryV2), "test_Constructor::2"); + assertEq(address(router.getV1Factory()), address(factoryV1), "test_Constructor::3"); + assertEq(address(router.getLegacyRouter()), address(legacyRouterV2), "test_Constructor::4"); + assertEq(address(router.getWAVAX()), address(wavax), "test_Constructor::5"); + } + function test_ReceiveAVAX() public { // Users can't send AVAX to the router vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__SenderIsNotWAVAX.selector)); @@ -45,7 +53,7 @@ contract LiquidityBinRouterTest is TestHelper { vm.prank(address(wavax)); (success,) = address(router).call{value: 1e18}(""); - assertTrue(success); + assertTrue(success, "test_ReceiveAVAX::1"); } function test_CreatePair() public { @@ -82,20 +90,20 @@ contract LiquidityBinRouterTest is TestHelper { ) = router.addLiquidity(liquidityParameters); // Check amounts - assertEq(amountXAdded, liquidityParameters.amountX, "amountXAdded"); - assertEq(amountYAdded, liquidityParameters.amountY, "amountYAdded"); - assertLt(amountXLeft, amountXAdded, "amountXLeft"); - assertLt(amountYLeft, amountYAdded, "amountYLeft"); + assertEq(amountXAdded, liquidityParameters.amountX, "testFuzz_AddLiquidityNoSlippage::1"); + assertEq(amountYAdded, liquidityParameters.amountY, "testFuzz_AddLiquidityNoSlippage::2"); + assertLt(amountXLeft, amountXAdded, "testFuzz_AddLiquidityNoSlippage::3"); + assertLt(amountYLeft, amountYAdded, "testFuzz_AddLiquidityNoSlippage::4"); assertEq(usdt.balanceOf(BOB), amountXLeft, "usdt balance"); assertEq(usdc.balanceOf(BOB), amountYLeft, "usdc balance"); // Check liquidity minted - assertEq(liquidityMinted.length, binNumber); - assertEq(depositIds.length, binNumber); + assertEq(liquidityMinted.length, binNumber, "testFuzz_AddLiquidityNoSlippage::5"); + assertEq(depositIds.length, binNumber, "testFuzz_AddLiquidityNoSlippage::6"); } - function test_reverts_AddLiquidity() public { + function test_revert_AddLiquidity() public { uint256 amountYIn = 1e18; uint24 binNumber = 7; uint24 gap = 2; @@ -158,7 +166,34 @@ contract LiquidityBinRouterTest is TestHelper { vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__IdOverflows.selector, uint256(type(uint24).max) + 1)); router.addLiquidity(liquidityParameters); - // Revert is slippage is too high + // Revert if ID slippage is caught + liquidityParameters = getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); + liquidityParameters.activeIdDesired = ID_ONE - 10; + liquidityParameters.idSlippage = 5; + + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__IdSlippageCaught.selector, + liquidityParameters.activeIdDesired, + liquidityParameters.idSlippage, + ID_ONE + ) + ); + router.addLiquidity(liquidityParameters); + + liquidityParameters.activeIdDesired = ID_ONE + 10; + + vm.expectRevert( + abi.encodeWithSelector( + ILBRouter.LBRouter__IdSlippageCaught.selector, + liquidityParameters.activeIdDesired, + liquidityParameters.idSlippage, + ID_ONE + ) + ); + router.addLiquidity(liquidityParameters); + + // Revert if slippage is too high liquidityParameters = getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); liquidityParameters.amountXMin = liquidityParameters.amountX + 1; @@ -218,14 +253,14 @@ contract LiquidityBinRouterTest is TestHelper { ) = router.addLiquidityAVAX{value: liquidityParameters.amountX}(liquidityParameters); // Check amounts - assertEq(amountXAdded, liquidityParameters.amountX, "amountXAdded"); - assertEq(amountYAdded, liquidityParameters.amountY, "amountYAdded"); - assertLt(amountXLeft, amountXAdded, "amountXLeft"); - assertLt(amountYLeft, amountYAdded, "amountYLeft"); + assertEq(amountXAdded, liquidityParameters.amountX, "test_AddLiquidityAVAX::1"); + assertEq(amountYAdded, liquidityParameters.amountY, "test_AddLiquidityAVAX::2"); + assertLt(amountXLeft, amountXAdded, "test_AddLiquidityAVAX::3"); + assertLt(amountYLeft, amountYAdded, "test_AddLiquidityAVAX::4"); // Check liquidity minted - assertEq(liquidityMinted.length, binNumber); - assertEq(depositIds.length, binNumber); + assertEq(liquidityMinted.length, binNumber, "test_AddLiquidityAVAX::5"); + assertEq(depositIds.length, binNumber, "test_AddLiquidityAVAX::6"); // Test with AVAX as token Y router.createLBPair(bnb, wavax, ID_ONE, DEFAULT_BIN_STEP); @@ -235,7 +270,7 @@ contract LiquidityBinRouterTest is TestHelper { router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); } - function test_reverts_AddLiquidityAVAX() public { + function test_revert_AddLiquidityAVAX() public { uint256 amountYIn = 1e18; uint24 binNumber = 7; uint24 gap = 2; @@ -299,14 +334,14 @@ contract LiquidityBinRouterTest is TestHelper { usdt, usdc, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp ); - assertApproxEqAbs(amountXOut, amountXAdded, 10, "amountXOut"); - assertApproxEqAbs(amountYOut, amountYAdded, 10, "amountYOut"); + assertApproxEqAbs(amountXOut, amountXAdded, 10, "test_RemoveLiquidity::1"); + assertApproxEqAbs(amountYOut, amountYAdded, 10, "test_RemoveLiquidity::2"); - assertLe(amountXOut, amountXAdded, "amountXOut"); - assertLe(amountYOut, amountYAdded, "amountYOut"); + assertLe(amountXOut, amountXAdded, "test_RemoveLiquidity::3"); + assertLe(amountYOut, amountYAdded, "test_RemoveLiquidity::4"); for (uint256 i = 0; i < depositIds.length; i++) { - assertEq(pair.balanceOf(address(this), depositIds[i]), 0, "depositId"); + assertEq(pair.balanceOf(address(this), depositIds[i]), 0, "test_RemoveLiquidity::5"); } // Try with the token inversed @@ -316,8 +351,8 @@ contract LiquidityBinRouterTest is TestHelper { usdc, usdt, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp ); - assertApproxEqAbs(amountXOut, amountXAdded, 10, "amountXOut"); - assertApproxEqAbs(amountYOut, amountYAdded, 10, "amountYOut"); + assertApproxEqAbs(amountXOut, amountXAdded, 10, "test_RemoveLiquidity::6"); + assertApproxEqAbs(amountYOut, amountYAdded, 10, "test_RemoveLiquidity::7"); // Try removing half of the liquidity (amountXAdded, amountYAdded,,, depositIds, liquidityMinted) = router.addLiquidity(liquidityParameters); @@ -330,11 +365,11 @@ contract LiquidityBinRouterTest is TestHelper { usdt, usdc, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp ); - assertApproxEqAbs(amountXOut, amountXAdded / 2, 10, "amountXOut"); - assertApproxEqAbs(amountYOut, amountYAdded / 2, 10, "amountYOut"); + assertApproxEqAbs(amountXOut, amountXAdded / 2, 10, "test_RemoveLiquidity::8"); + assertApproxEqAbs(amountYOut, amountYAdded / 2, 10, "test_RemoveLiquidity::9"); } - function test_reverts_RemoveLiquidity() public { + function test_revert_RemoveLiquidity() public { uint256 amountYIn = 1e18; uint24 binNumber = 7; uint24 gap = 2; @@ -415,14 +450,14 @@ contract LiquidityBinRouterTest is TestHelper { usdc, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, payable(address(this)), block.timestamp ); - assertApproxEqAbs(amountAVAX, amountXAdded, 10, "amountXOut"); - assertApproxEqAbs(amountToken, amountYAdded, 10, "amountYOut"); + assertApproxEqAbs(amountAVAX, amountXAdded, 10, "test_RemoveLiquidityAVAX::1"); + assertApproxEqAbs(amountToken, amountYAdded, 10, "test_RemoveLiquidityAVAX::2"); - assertEq(address(this).balance, balanceAVAXBefore + amountAVAX, "balanceAVAXAfter"); - assertEq(usdc.balanceOf(address(this)), balanceUSDCBefore + amountToken, "balanceUSDCAfter"); + assertEq(address(this).balance, balanceAVAXBefore + amountAVAX, "test_RemoveLiquidityAVAX::3"); + assertEq(usdc.balanceOf(address(this)), balanceUSDCBefore + amountToken, "test_RemoveLiquidityAVAX::4"); } - function test_reverts_RemoveLiquidityAVAX() public { + function test_revert_RemoveLiquidityAVAX() public { uint256 amountYIn = 1e18; uint24 binNumber = 7; uint24 gap = 2; @@ -461,7 +496,13 @@ contract LiquidityBinRouterTest is TestHelper { uint256 balanceBefore = usdc.balanceOf(address(this)); router.sweep(usdc, address(this), amount); - assertEq(usdc.balanceOf(address(this)), balanceBefore + amount, "balanceAfter"); + assertEq(usdc.balanceOf(address(this)), balanceBefore + amount, "test_SweepERC20::1"); + + deal(address(router), amount); + + balanceBefore = address(this).balance; + router.sweep(IERC20(address(0)), address(this), type(uint256).max); + assertEq(address(this).balance, balanceBefore + amount, "test_SweepERC20::2"); // Can't sweep if non owner usdc.mint(address(router), amount); @@ -487,7 +528,9 @@ contract LiquidityBinRouterTest is TestHelper { router.sweepLBToken(pair, DEV, depositIds, liquidityMinted); for (uint256 i = 0; i < depositIds.length; i++) { - assertEq(pair.balanceOf(DEV, depositIds[i]), balancesBefore[i] + liquidityMinted[i], "balanceAfter"); + assertEq( + pair.balanceOf(DEV, depositIds[i]), balancesBefore[i] + liquidityMinted[i], "test_SweepLBTokens::1" + ); } (,,,, depositIds, liquidityMinted) = router.addLiquidity(liquidityParameters); diff --git a/test/LBRouter.Swap.t.sol b/test/LBRouter.Swap.t.sol index 94b3c3be..5f2de562 100644 --- a/test/LBRouter.Swap.t.sol +++ b/test/LBRouter.Swap.t.sol @@ -8,14 +8,14 @@ import "test/helpers/TestHelper.sol"; * This file only test single hop swaps using V2.1 pairs * Test scenarios: * 1. swapExactTokensForTokens - * 1. swapExactTokensForAVAX - * 2. swapExactAVAXForTokens - * 3. swapTokensForExactTokens - * 4. swapTokensForExactAVAX - * 5. swapAVAXForExactTokens - * 6. swapExactTokensForTokensSupportingFeeOnTransferTokens - * 7. swapExactTokensForAVAXSupportingFeeOnTransferTokens - * 8. swapExactAVAXForTokensSupportingFeeOnTransferTokens + * 2. swapExactTokensForAVAX + * 3. swapExactAVAXForTokens + * 4. swapTokensForExactTokens + * 5. swapTokensForExactAVAX + * 6. swapAVAXForExactTokens + * 7. swapExactTokensForTokensSupportingFeeOnTransferTokens + * 8. swapExactTokensForAVAXSupportingFeeOnTransferTokens + * 9. swapExactAVAXForTokensSupportingFeeOnTransferTokens */ contract LiquidityBinRouterSwapTest is TestHelper { function setUp() public override { @@ -44,6 +44,14 @@ contract LiquidityBinRouterSwapTest is TestHelper { router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); } + function test_GetIdFromPrice() public view { + ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP).LBPair; + + router.getIdFromPrice(pair, 924521306405372907020063908180274956666); + + router.getPriceFromId(pair, 1_000 + ID_ONE); + } + function test_SwapExactTokensForTokens() public { uint128 amountIn = 20e18; @@ -57,8 +65,8 @@ contract LiquidityBinRouterSwapTest is TestHelper { (uint256 amountOut) = router.swapExactTokensForTokens(amountIn, amountOutExpected, path, address(this), block.timestamp + 1); - assertEq(amountOut, amountOutExpected, "amountOut"); - assertEq(usdc.balanceOf(address(this)), balanceBefore + amountOut, "balance"); + assertEq(amountOut, amountOutExpected, "test_SwapExactTokensForTokens::1"); + assertEq(usdc.balanceOf(address(this)), balanceBefore + amountOut, "test_SwapExactTokensForTokens::2"); // Reverts if amountOut is less than amountOutMin (, amountOutExpected,) = router.getSwapOut(pair, amountIn, true); @@ -79,6 +87,15 @@ contract LiquidityBinRouterSwapTest is TestHelper { path.tokenPath = new IERC20[](0); vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); router.swapExactTokensForTokens(amountIn, amountOutExpected, path, address(this), block.timestamp + 1); + + // Revert if the pair doesn't exist + path.tokenPath = new IERC20[](2); + path.tokenPath[0] = usdt; + path.tokenPath[1] = taxToken; + vm.expectRevert( + abi.encodeWithSelector(ILBRouter.LBRouter__PairNotCreated.selector, usdt, taxToken, DEFAULT_BIN_STEP) + ); + router.swapExactTokensForTokens(amountIn, amountOutExpected, path, address(this), block.timestamp + 1); } function test_SwapExactTokensForAVAX() public { @@ -95,8 +112,8 @@ contract LiquidityBinRouterSwapTest is TestHelper { amountIn, amountOutExpected, path, payable(address(this)), block.timestamp + 1 ); - assertEq(amountOut, amountOutExpected, "amountOut"); - assertEq(address(this).balance, balanceBefore + amountOut, "balance"); + assertEq(amountOut, amountOutExpected, "test_SwapExactTokensForAVAX::1"); + assertEq(address(this).balance, balanceBefore + amountOut, "test_SwapExactTokensForAVAX::2"); // Reverts if amountOut is less than amountOutMin (, amountOutExpected,) = router.getSwapOut(pair, amountIn, false); @@ -141,8 +158,8 @@ contract LiquidityBinRouterSwapTest is TestHelper { (uint256 amountOut) = router.swapExactAVAXForTokens{value: amountIn}(amountOutExpected, path, address(this), block.timestamp + 1); - assertEq(amountOut, amountOutExpected, "amountOut"); - assertEq(usdc.balanceOf(address(this)), balanceBefore + amountOut, "balance"); + assertEq(amountOut, amountOutExpected, "test_SwapExactAVAXForTokens::1"); + assertEq(usdc.balanceOf(address(this)), balanceBefore + amountOut, "test_SwapExactAVAXForTokens::2"); (, amountOutExpected,) = router.getSwapOut(pair, amountIn, true); @@ -184,8 +201,8 @@ contract LiquidityBinRouterSwapTest is TestHelper { (uint256[] memory amountsIn) = router.swapTokensForExactTokens(amountOut, amountInExpected, path, address(this), block.timestamp + 1); - assertEq(amountsIn[0], amountInExpected, "amountOut"); - assertEq(usdt.balanceOf(address(this)), balanceBefore - amountsIn[0], "balance"); + assertEq(amountsIn[0], amountInExpected, "test_SwapTokensForExactTokens::1"); + assertEq(usdt.balanceOf(address(this)), balanceBefore - amountsIn[0], "test_SwapTokensForExactTokens::2"); (amountInExpected,,) = router.getSwapIn(pair, amountOut, true); @@ -197,8 +214,6 @@ contract LiquidityBinRouterSwapTest is TestHelper { ); router.swapTokensForExactTokens(amountOut, amountInExpected - 1, path, address(this), block.timestamp + 1); - // TODO - try to hit LBRouter__InsufficientAmountOut - // Revert is dealine passed vm.expectRevert( abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) @@ -225,8 +240,8 @@ contract LiquidityBinRouterSwapTest is TestHelper { amountOut, amountInExpected, path, payable(address(this)), block.timestamp + 1 ); - assertEq(amountsIn[0], amountInExpected, "amountOut"); - assertEq(usdc.balanceOf(address(this)), balanceBefore - amountsIn[0], "balance"); + assertEq(amountsIn[0], amountInExpected, "test_SwapTokensForExactAVAX::1"); + assertEq(usdc.balanceOf(address(this)), balanceBefore - amountsIn[0], "test_SwapTokensForExactAVAX::2"); (amountInExpected,,) = router.getSwapIn(pair, amountOut, false); @@ -238,8 +253,6 @@ contract LiquidityBinRouterSwapTest is TestHelper { ); router.swapTokensForExactTokens(amountOut, amountInExpected - 1, path, address(this), block.timestamp + 1); - // TODO - try to hit LBRouter__InsufficientAmountOut - // Revert if token out isn't WAVAX path.tokenPath[1] = usdt; vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); @@ -272,8 +285,8 @@ contract LiquidityBinRouterSwapTest is TestHelper { amountOut, path, address(this), block.timestamp + 1 ); - assertEq(amountsIn[0], amountInExpected, "amountOut"); - assertEq(address(this).balance, balanceBefore - amountsIn[0], "balance"); + assertEq(amountsIn[0], amountInExpected, "test_SwapAVAXForExactTokens::1"); + assertEq(address(this).balance, balanceBefore - amountsIn[0], "test_SwapAVAXForExactTokens::2"); (amountInExpected,,) = router.getSwapIn(pair, amountOut, true); @@ -285,8 +298,6 @@ contract LiquidityBinRouterSwapTest is TestHelper { ); router.swapAVAXForExactTokens{value: amountInExpected / 2}(amountOut, path, address(this), block.timestamp + 1); - // TODO - try to hit LBRouter__InsufficientAmountOut - // Revert if token in isn't WAVAX path.tokenPath[0] = usdt; vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); @@ -304,57 +315,6 @@ contract LiquidityBinRouterSwapTest is TestHelper { router.swapAVAXForExactTokens{value: amountInExpected}(amountOut, path, address(this), block.timestamp + 1); } - function test_swapExactTokensForAVAXSupportingFeeOnTransferTokens() public { - uint128 amountIn = 20e18; - - ILBPair pair = factory.getLBPairInformation(taxToken, wavax, DEFAULT_BIN_STEP).LBPair; - (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, true); - - ILBRouter.Path memory path = _buildPath(taxToken, wavax); - - // Reverts if amountOut is less than amountOutMin - vm.expectRevert( - abi.encodeWithSelector( - ILBRouter.LBRouter__InsufficientAmountOut.selector, amountOutExpected, amountOutExpected / 2 - ) - ); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn, amountOutExpected, path, payable(address(this)), block.timestamp + 1 - ); - - uint256 balanceBefore = address(this).balance; - - // Token tax is 50% - (uint256 amountOut) = router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn, amountOutExpected / 2, path, payable(address(this)), block.timestamp + 1 - ); - - assertEq(amountOut, amountOutExpected / 2, "amountOut"); - assertEq(address(this).balance, balanceBefore + amountOut, "balance"); - - // Revert if token out isn't WAVAX - path.tokenPath[1] = usdt; - vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn, amountOutExpected / 2, path, payable(address(this)), block.timestamp + 1 - ); - - // Revert is dealine passed - vm.expectRevert( - abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) - ); - router.swapExactTokensForAVAX( - amountIn, amountOutExpected / 2, path, payable(address(this)), block.timestamp - 1 - ); - - // Revert if the path arrays are not valid - path.tokenPath = new IERC20[](0); - vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); - router.swapExactTokensForAVAX( - amountIn, amountOutExpected / 2, path, payable(address(this)), block.timestamp + 1 - ); - } - function test_SwapExactTokensForAVAXSupportingFeeOnTransferTokens() public { uint128 amountIn = 20e18; @@ -379,8 +339,12 @@ contract LiquidityBinRouterSwapTest is TestHelper { amountIn, amountOutExpected / 2, path, payable(address(this)), block.timestamp + 1 ); - assertEq(amountOut, amountOutExpected / 2, "amountOut"); - assertEq(address(this).balance, balanceBefore + amountOut, "balance"); + assertEq(amountOut, amountOutExpected / 2, "test_SwapExactTokensForAVAXSupportingFeeOnTransferTokens::1"); + assertEq( + address(this).balance, + balanceBefore + amountOut, + "test_SwapExactTokensForAVAXSupportingFeeOnTransferTokens::2" + ); // Revert if token out isn't WAVAX path.tokenPath[1] = usdt; @@ -429,8 +393,12 @@ contract LiquidityBinRouterSwapTest is TestHelper { amountOutExpected / 2, path, address(this), block.timestamp + 1 ); - assertEq(amountOut, amountOutExpected / 2 + 1, "amountOut"); - assertEq(taxToken.balanceOf(address(this)), balanceBefore + amountOut, "balance"); + assertEq(amountOut, amountOutExpected / 2 + 1, "test_SwapExactAVAXForTokensSupportingFeeOnTransferTokens::1"); + assertEq( + taxToken.balanceOf(address(this)), + balanceBefore + amountOut, + "test_SwapExactAVAXForTokensSupportingFeeOnTransferTokens::2" + ); // Revert if token in isn't WAVAX path.tokenPath[0] = usdt; diff --git a/test/integration/LBQuoter.t.sol b/test/integration/LBQuoter.t.sol index 29e3d22e..22d3a389 100644 --- a/test/integration/LBQuoter.t.sol +++ b/test/integration/LBQuoter.t.sol @@ -4,17 +4,17 @@ pragma solidity 0.8.10; import "../helpers/TestHelper.sol"; -/* -* Market deployed: -* - USDT/USDC, V1 with low liquidity, V2 with high liquidity -* - WAVAX/USDC, V1 with high liquidity, V2 with low liquidity -* - WETH/USDC, V1 with low liquidity, V2.1 with high liquidity -* - BNB/USDC, V2 with high liquidity, V2.1 with low liquidity -* -* Every market with low liquidity has a slighly higher price. -* It should be picked with small amounts but not with large amounts. -* All tokens are considered 18 decimals for simplification purposes. -**/ +/** + * Market deployed: + * - USDT/USDC, V1 with low liquidity, V2 with high liquidity + * - WAVAX/USDC, V1 with high liquidity, V2 with low liquidity + * - WETH/USDC, V1 with low liquidity, V2.1 with high liquidity + * - BNB/USDC, V2 with high liquidity, V2.1 with low liquidity + * + * Every market with low liquidity has a slighly higher price. + * It should be picked with small amounts but not with large amounts. + * All tokens are considered 18 decimals for simplification purposes. + */ contract LiquidityBinQuoterTest is TestHelper { uint256 private defaultBaseFee = DEFAULT_BIN_STEP * uint256(DEFAULT_BASE_FACTOR) * 1e10; @@ -98,10 +98,11 @@ contract LiquidityBinQuoterTest is TestHelper { } function test_Constructor() public { - assertEq(address(quoter.getRouterV2()), address(router)); - assertEq(address(quoter.getFactoryV1()), AvalancheAddresses.JOE_V1_FACTORY); - assertEq(address(quoter.getLegacyFactoryV2()), AvalancheAddresses.JOE_V2_FACTORY); - assertEq(address(quoter.getFactoryV2()), address(factory)); + assertEq(address(quoter.getRouterV2()), address(router), "test_Constructor::1"); + assertEq(address(quoter.getFactoryV1()), AvalancheAddresses.JOE_V1_FACTORY, "test_Constructor::2"); + assertEq(address(quoter.getLegacyFactoryV2()), AvalancheAddresses.JOE_V2_FACTORY, "test_Constructor::3"); + assertEq(address(quoter.getFactoryV2()), address(factory), "test_Constructor::4"); + assertEq(address(quoter.getLegacyRouterV2()), address(legacyRouterV2), "test_Constructor::5"); } function test_InvalidLength() public { @@ -244,7 +245,6 @@ contract LiquidityBinQuoterTest is TestHelper { function test_Scenario4() public { // BNB/USDC, V2 with high liquidity, V2.1 with low liquidity - address[] memory route = new address[](2); route[0] = address(bnb); route[1] = address(usdc); diff --git a/test/integration/LBRouter.t.sol b/test/integration/LBRouter.t.sol index d1c858f4..b8abfe2f 100644 --- a/test/integration/LBRouter.t.sol +++ b/test/integration/LBRouter.t.sol @@ -58,7 +58,7 @@ contract LiquidityBinRouterForkTest is TestHelper { router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); } - function test_swapExactTokensForTokens() public { + function test_SwapExactTokensForTokens() public { uint256 amountIn = 1e18; ILBRouter.Path memory path = _buildPath(usdt, weth); @@ -67,10 +67,18 @@ contract LiquidityBinRouterForkTest is TestHelper { uint256 amountOut = router.swapExactTokensForTokens(amountIn, 0, path, address(this), block.timestamp + 1); - assertEq(amountOut, quote.amounts[3]); + assertEq(amountOut, quote.amounts[3], "test_SwapExactTokensForTokens::1"); + + // Reverse path + path = _buildPath(weth, usdt); + quote = quoter.findBestPathFromAmountIn(_convertToAddresses(path.tokenPath), uint128(amountIn)); + + amountOut = router.swapExactTokensForTokens(amountIn, 0, path, address(this), block.timestamp + 1); + + assertEq(amountOut, quote.amounts[3], "test_SwapExactTokensForTokens::2"); } - function test_swapTokensForExactTokens() public { + function test_SwapTokensForExactTokens() public { uint256 amountOut = 1e18; ILBRouter.Path memory path = _buildPath(weth, usdt); @@ -80,10 +88,19 @@ contract LiquidityBinRouterForkTest is TestHelper { uint256[] memory amountsIn = router.swapTokensForExactTokens(amountOut, quote.amounts[0], path, address(this), block.timestamp + 1); - assertEq(amountsIn[0], quote.amounts[0]); + assertEq(amountsIn[0], quote.amounts[0], "test_SwapTokensForExactTokens::1"); + + // Reverse path + path = _buildPath(usdt, weth); + quote = quoter.findBestPathFromAmountOut(_convertToAddresses(path.tokenPath), uint128(amountOut)); + + amountsIn = + router.swapTokensForExactTokens(amountOut, quote.amounts[0], path, address(this), block.timestamp + 1); + + assertEq(amountsIn[0], quote.amounts[0], "test_SwapTokensForExactTokens::2"); } - function test_swapExactTokensForTokensSupportingFeeOnTransferTokens() public { + function test_SwapExactTokensForTokensSupportingFeeOnTransferTokens() public { uint256 amountIn = 1e18; ILBRouter.Path memory path = _buildPath(taxToken, usdt); @@ -94,7 +111,21 @@ contract LiquidityBinRouterForkTest is TestHelper { amountIn, 0, path, address(this), block.timestamp + 1 ); - assertApproxEqRel(amountOut, quote.amounts[3] / 2, 1e12); + assertApproxEqRel( + amountOut, quote.amounts[3] / 2, 1e12, "test_SwapExactTokensForTokensSupportingFeeOnTransferTokens::1" + ); + + // Reverse path + path = _buildPath(usdt, taxToken); + quote = quoter.findBestPathFromAmountIn(_convertToAddresses(path.tokenPath), uint128(amountIn)); + + amountOut = router.swapExactTokensForTokensSupportingFeeOnTransferTokens( + amountIn, 0, path, address(this), block.timestamp + 1 + ); + + assertApproxEqRel( + amountOut, quote.amounts[3] / 2, 1e12, "test_SwapExactTokensForTokensSupportingFeeOnTransferTokens::2" + ); } function _buildPath(IERC20 tokenIn, IERC20 tokenOut) private view returns (ILBRouter.Path memory path) { From d642b6e46d8634de04053c902012d8016ddc329d Mon Sep 17 00:00:00 2001 From: Louis <107303182+0x0Louis@users.noreply.github.com> Date: Mon, 6 Feb 2023 16:15:04 +0100 Subject: [PATCH 18/47] make decode function clearer (#84) * make decode function clearer * missing functions --- src/LBRouter.sol | 12 +++--- src/libraries/BinHelper.sol | 10 ++--- src/libraries/math/PackedUint128Math.sol | 6 +-- test/libraries/BinHelper.t.sol | 43 ++++++++++----------- test/libraries/math/PackedUint128Math.t.sol | 8 ++-- test/mocks/FlashBorrower.sol | 4 +- 6 files changed, 40 insertions(+), 43 deletions(-) diff --git a/src/LBRouter.sol b/src/LBRouter.sol index 36c99974..9218329c 100644 --- a/src/LBRouter.sol +++ b/src/LBRouter.sol @@ -744,15 +744,15 @@ contract LBRouter is ILBRouter { bytes32 amountsLeft; (amountsReceived, amountsLeft, liquidityMinted) = pair.mint(liq.to, liquidityConfigs, liq.refundTo); - amountXAdded = amountsReceived.decodeFirst(); - amountYAdded = amountsReceived.decodeSecond(); + amountXAdded = amountsReceived.decodeX(); + amountYAdded = amountsReceived.decodeY(); if (amountXAdded < liq.amountXMin || amountYAdded < liq.amountYMin) { revert LBRouter__AmountSlippageCaught(liq.amountXMin, amountXAdded, liq.amountYMin, amountYAdded); } - amountXLeft = amountsLeft.decodeFirst(); - amountYLeft = amountsLeft.decodeSecond(); + amountXLeft = amountsLeft.decodeX(); + amountYLeft = amountsLeft.decodeY(); } } @@ -820,8 +820,8 @@ contract LBRouter is ILBRouter { (bytes32[] memory amountsBurned) = pair.burn(msg.sender, to, ids, amounts); for (uint256 i; i < amountsBurned.length; ++i) { - amountX += amountsBurned[i].decodeFirst(); - amountY += amountsBurned[i].decodeSecond(); + amountX += amountsBurned[i].decodeX(); + amountY += amountsBurned[i].decodeY(); } if (amountX < amountXMin || amountY < amountYMin) { diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index 87608a4c..aa3b4013 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -192,7 +192,7 @@ library BinHelper { * @return Whether the bin is empty (true) or not (false) */ function isEmpty(bytes32 binReserves, bool isX) internal pure returns (bool) { - return isX ? binReserves.decodeFirst() == 0 : binReserves.decodeSecond() == 0; + return isX ? binReserves.decodeX() == 0 : binReserves.decodeY() == 0; } /** @@ -277,7 +277,7 @@ library BinHelper { * [128 - 256[: empty */ function receivedX(bytes32 reserves, IERC20 tokenX) internal view returns (bytes32) { - uint128 reserveX = reserves.decodeFirst(); + uint128 reserveX = reserves.decodeX(); return (_balanceOf(tokenX) - reserveX).encodeFirst(); } @@ -290,7 +290,7 @@ library BinHelper { * [128 - 256[: amountY */ function receivedY(bytes32 reserves, IERC20 tokenY) internal view returns (bytes32) { - uint128 reserveY = reserves.decodeSecond(); + uint128 reserveY = reserves.decodeY(); return (_balanceOf(tokenY) - reserveY).encodeSecond(); } @@ -319,7 +319,7 @@ library BinHelper { * @param recipient The recipient */ function transferX(bytes32 amounts, IERC20 tokenX, address recipient) internal { - uint128 amountX = amounts.decodeFirst(); + uint128 amountX = amounts.decodeX(); if (amountX > 0) tokenX.safeTransfer(recipient, amountX); } @@ -333,7 +333,7 @@ library BinHelper { * @param recipient The recipient */ function transferY(bytes32 amounts, IERC20 tokenY, address recipient) internal { - uint128 amountY = amounts.decodeSecond(); + uint128 amountY = amounts.decodeY(); if (amountY > 0) tokenY.safeTransfer(recipient, amountY); } diff --git a/src/libraries/math/PackedUint128Math.sol b/src/libraries/math/PackedUint128Math.sol index a3935c7a..e68624e1 100644 --- a/src/libraries/math/PackedUint128Math.sol +++ b/src/libraries/math/PackedUint128Math.sol @@ -101,7 +101,7 @@ library PackedUint128Math { * [128 - 256[: any * @return x1 The first uint128 */ - function decodeFirst(bytes32 z) internal pure returns (uint128 x1) { + function decodeX(bytes32 z) internal pure returns (uint128 x1) { assembly { x1 := and(z, MASK_128) } @@ -114,7 +114,7 @@ library PackedUint128Math { * [128 - 256[: x2 * @return x2 The second uint128 */ - function decodeSecond(bytes32 z) internal pure returns (uint128 x2) { + function decodeY(bytes32 z) internal pure returns (uint128 x2) { assembly { x2 := shr(OFFSET, z) } @@ -133,7 +133,7 @@ library PackedUint128Math { * @return x The decoded uint128 */ function decode(bytes32 z, bool first) internal pure returns (uint128 x) { - return first ? decodeFirst(z) : decodeSecond(z); + return first ? decodeX(z) : decodeY(z); } /** diff --git a/test/libraries/BinHelper.t.sol b/test/libraries/BinHelper.t.sol index 9dccb878..cebffe7b 100644 --- a/test/libraries/BinHelper.t.sol +++ b/test/libraries/BinHelper.t.sol @@ -123,12 +123,12 @@ contract BinHelperTest is TestHelper { binReserves = binReserves.add(effectiveAmountsIn); totalSupply += shares; - uint256 userReceivedX = shares.mulDivRoundDown(binReserves.decodeFirst(), totalSupply); - uint256 userReceivedY = shares.mulDivRoundDown(binReserves.decodeSecond(), totalSupply); + uint256 userReceivedX = shares.mulDivRoundDown(binReserves.decodeX(), totalSupply); + uint256 userReceivedY = shares.mulDivRoundDown(binReserves.decodeY(), totalSupply); uint256 receivedInY = userReceivedX.mulShiftRoundDown(price, Constants.SCALE_OFFSET) + userReceivedY; - uint256 sentInY = price.mulShiftRoundDown(effectiveAmountsIn.decodeFirst(), Constants.SCALE_OFFSET) - + effectiveAmountsIn.decodeSecond(); + uint256 sentInY = + price.mulShiftRoundDown(effectiveAmountsIn.decodeX(), Constants.SCALE_OFFSET) + effectiveAmountsIn.decodeY(); assertApproxEqAbs(receivedInY, sentInY, ((price - 1) >> 128) + 2, "test_TryExploitShares::1"); } @@ -253,13 +253,10 @@ contract BinHelperTest is TestHelper { uint256 amountInWithoutFees = amountsInToBin.sub(totalFees).decode(swapForY); (uint256 amountOutWithNoFees, uint256 amountOut) = swapForY - ? ( - price.mulShiftRoundDown(amountsInToBin.decodeFirst(), Constants.SCALE_OFFSET), - amountsOutOfBin.decodeSecond() - ) + ? (price.mulShiftRoundDown(amountsInToBin.decodeX(), Constants.SCALE_OFFSET), amountsOutOfBin.decodeY()) : ( - uint256(amountsInToBin.decodeSecond()).shiftDivRoundDown(Constants.SCALE_OFFSET, price), - amountsOutOfBin.decodeFirst() + uint256(amountsInToBin.decodeY()).shiftDivRoundDown(Constants.SCALE_OFFSET, price), + amountsOutOfBin.decodeX() ); assertGe(amountOutWithNoFees, amountOut, "test_GetAmounts::2"); @@ -312,8 +309,8 @@ contract BinHelperTest is TestHelper { uint256 amountInForSwap = amountsInToBin.decode(swapForY); (uint256 amountOutWithNoFees, uint256 amountOut) = swapForY - ? (price.mulShiftRoundDown(amountInForSwap, Constants.SCALE_OFFSET), amountsOutOfBin.decodeSecond()) - : (uint256(amountInForSwap).shiftDivRoundDown(Constants.SCALE_OFFSET, price), amountsOutOfBin.decodeFirst()); + ? (price.mulShiftRoundDown(amountInForSwap, Constants.SCALE_OFFSET), amountsOutOfBin.decodeY()) + : (uint256(amountInForSwap).shiftDivRoundDown(Constants.SCALE_OFFSET, price), amountsOutOfBin.decodeX()); assertGe(amountOutWithNoFees, amountOut, "test_GetAmounts::2"); } @@ -354,12 +351,12 @@ contract BinHelperTest is TestHelper { assertEq(receivedY, sentY, "test_Received::2"); received = reserves.receivedX(IERC20(address(usdc))); - receivedX = received.decodeFirst(); + receivedX = received.decodeX(); assertEq(receivedX, sentX, "test_Received::3"); received = reserves.receivedY(IERC20(address(wavax))); - receivedY = received.decodeSecond(); + receivedY = received.decodeY(); assertEq(receivedY, sentY, "test_Received::4"); } @@ -377,22 +374,22 @@ contract BinHelperTest is TestHelper { firstHalf.transfer(IERC20(address(usdc)), IERC20(address(wavax)), recipient); - assertEq(usdc.balanceOf(recipient), firstHalf.decodeFirst(), "test_Transfer::1"); - assertEq(wavax.balanceOf(recipient), firstHalf.decodeSecond(), "test_Transfer::2"); - assertEq(usdc.balanceOf(address(this)), secondHalf.decodeFirst(), "test_Transfer::3"); - assertEq(wavax.balanceOf(address(this)), secondHalf.decodeSecond(), "test_Transfer::4"); + assertEq(usdc.balanceOf(recipient), firstHalf.decodeX(), "test_Transfer::1"); + assertEq(wavax.balanceOf(recipient), firstHalf.decodeY(), "test_Transfer::2"); + assertEq(usdc.balanceOf(address(this)), secondHalf.decodeX(), "test_Transfer::3"); + assertEq(wavax.balanceOf(address(this)), secondHalf.decodeY(), "test_Transfer::4"); secondHalf.transferX(IERC20(address(usdc)), recipient); - assertEq(usdc.balanceOf(recipient), amounts.decodeFirst(), "test_Transfer::5"); - assertEq(wavax.balanceOf(recipient), firstHalf.decodeSecond(), "test_Transfer::6"); + assertEq(usdc.balanceOf(recipient), amounts.decodeX(), "test_Transfer::5"); + assertEq(wavax.balanceOf(recipient), firstHalf.decodeY(), "test_Transfer::6"); assertEq(usdc.balanceOf(address(this)), 0, "test_Transfer::7"); - assertEq(wavax.balanceOf(address(this)), secondHalf.decodeSecond(), "test_Transfer::8"); + assertEq(wavax.balanceOf(address(this)), secondHalf.decodeY(), "test_Transfer::8"); secondHalf.transferY(IERC20(address(wavax)), recipient); - assertEq(usdc.balanceOf(recipient), amounts.decodeFirst(), "test_Transfer::9"); - assertEq(wavax.balanceOf(recipient), amounts.decodeSecond(), "test_Transfer::10"); + assertEq(usdc.balanceOf(recipient), amounts.decodeX(), "test_Transfer::9"); + assertEq(wavax.balanceOf(recipient), amounts.decodeY(), "test_Transfer::10"); assertEq(usdc.balanceOf(address(this)), 0, "test_Transfer::11"); assertEq(wavax.balanceOf(address(this)), 0, "test_Transfer::12"); } diff --git a/test/libraries/math/PackedUint128Math.t.sol b/test/libraries/math/PackedUint128Math.t.sol index 94891c72..61a0baa0 100644 --- a/test/libraries/math/PackedUint128Math.t.sol +++ b/test/libraries/math/PackedUint128Math.t.sol @@ -33,12 +33,12 @@ contract PackedUint128MathTest is Test { assertEq(x2, uint128(uint256(x) >> 128), "testFuzz_Decode::2"); } - function testFuzz_DecodeFirst(bytes32 x) external { - assertEq(uint128(uint256(x)), x.decodeFirst(), "testFuzz_DecodeFirst::1"); + function testFuzz_decodeX(bytes32 x) external { + assertEq(uint128(uint256(x)), x.decodeX(), "testFuzz_decodeX::1"); } - function testFuzz_DecodeSecond(bytes32 x) external { - assertEq(uint128(uint256(x) >> 128), x.decodeSecond(), "testFuzz_DecodeSecond::1"); + function testFuzz_decodeY(bytes32 x) external { + assertEq(uint128(uint256(x) >> 128), x.decodeY(), "testFuzz_decodeY::1"); } function testFuzz_DecodeBool(bytes32 x, bool first) external { diff --git a/test/mocks/FlashBorrower.sol b/test/mocks/FlashBorrower.sol index 407ecdcf..9cefecdf 100644 --- a/test/mocks/FlashBorrower.sol +++ b/test/mocks/FlashBorrower.sol @@ -46,11 +46,11 @@ contract FlashBorrower is ILBFlashLoanCallback { } if (paybackX == type(uint128).max) { - paybackX = amounts.decodeFirst() + totalFees.decodeFirst(); + paybackX = amounts.decodeX() + totalFees.decodeX(); } if (paybackY == type(uint128).max) { - paybackY = amounts.decodeSecond() + totalFees.decodeSecond(); + paybackY = amounts.decodeY() + totalFees.decodeY(); } if (paybackX > 0) { From 8cfaffa7ffbaddf27b122c83deecbb7fed366bce Mon Sep 17 00:00:00 2001 From: Mathieu <85969303+Mathieu-Be@users.noreply.github.com> Date: Tue, 7 Feb 2023 12:30:27 +0100 Subject: [PATCH 19/47] add missing methods on legacy router interface (#85) --- src/interfaces/ILBLegacyRouter.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/interfaces/ILBLegacyRouter.sol b/src/interfaces/ILBLegacyRouter.sol index 26e1e59d..a871f2c6 100644 --- a/src/interfaces/ILBLegacyRouter.sol +++ b/src/interfaces/ILBLegacyRouter.sol @@ -31,6 +31,10 @@ interface ILBLegacyRouter { uint256 deadline; } + function getIdFromPrice(ILBLegacyPair LBPair, uint256 price) external view returns (uint24); + + function getPriceFromId(ILBLegacyPair LBPair, uint24 id) external view returns (uint256); + function getSwapIn(ILBLegacyPair lbPair, uint256 amountOut, bool swapForY) external view From 7c794105ad6790efa31b5b35523dbb8248fbaacd Mon Sep 17 00:00:00 2001 From: Patate Date: Wed, 8 Feb 2023 10:16:06 +0100 Subject: [PATCH 20/47] add missing methods in the legacy pair interface --- src/interfaces/ILBLegacyPair.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/interfaces/ILBLegacyPair.sol b/src/interfaces/ILBLegacyPair.sol index 64859c01..3293ed9e 100644 --- a/src/interfaces/ILBLegacyPair.sol +++ b/src/interfaces/ILBLegacyPair.sol @@ -15,4 +15,8 @@ interface ILBLegacyPair { function getReservesAndId() external view returns (uint256 reserveX, uint256 reserveY, uint256 activeId); function swap(bool sentTokenY, address to) external returns (uint256 amountXOut, uint256 amountYOut); + + function findFirstNonEmptyBinId(uint24 id_, bool sentTokenY) external view returns (uint24 id); + + function getBin(uint24 id) external view returns (uint256 reserveX, uint256 reserveY); } From d1eee665ca36459ecc3f38405dcfd3af7baacb72 Mon Sep 17 00:00:00 2001 From: Louis <107303182+0x0Louis@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:17:08 +0100 Subject: [PATCH 21/47] Clean up and refactor (#86) * remove old tests * refactor avax to native --- src/LBFactory.sol | 4 +- src/LBRouter.sol | 170 +++---- src/interfaces/ILBLegacyRouter.sol | 2 +- src/interfaces/ILBRouter.sol | 34 +- src/interfaces/{IWAVAX.sol => IWNATIVE.sol} | 6 +- test/Faucet.t.sol | 42 +- test/LBFactory.t.sol | 8 +- test/LBPairFees.t.sol | 322 ++++++------- test/LBPairFlashloan.t.sol | 34 +- test/LBPairInitialState.t.sol | 81 ++-- test/LBPairLiquidity.t.sol | 116 ++--- test/LBPairOracle.t.sol | 119 ++--- test/LBPairSwap.t.sol | 64 +-- test/LBRouter.Liquidity.t.sol | 102 ++--- test/LBRouter.Swap.t.sol | 161 +++---- test/helpers/TestHelper.sol | 28 +- test/integration/Addresses.sol | 2 +- test/integration/LBQuoter.t.sol | 16 +- test/integration/LBRouter.t.sol | 26 +- test/libraries/BinHelper.t.sol | 24 +- test/mocks/Faucet.sol | 32 +- test/mocks/{WAVAX.sol => WNATIVE.sol} | 4 +- test_old/LBFactory.MultiPools.t.sol | 174 ------- test_old/LBPair.Fees.t.sol | 404 ----------------- test_old/LBPair.FlashLoans.t.sol | 63 --- test_old/LBPair.Liquidity.t.sol | 252 ----------- test_old/LBPair.Oracle.t.sol | 247 ---------- test_old/LBPair.Swaps.t.sol | 310 ------------- test_old/LBPair.t.sol | 150 ------- test_old/LBQuoter.t.sol | 227 ---------- test_old/LBRouter.FeesOnLiquidityAdd.t.sol | 238 ---------- test_old/LBRouter.Fork.Swaps.t.sol | 268 ----------- test_old/LBRouter.Liquidity.t.sol | 429 ------------------ test_old/LBRouter.Swaps.t.sol | 474 -------------------- test_old/LBRouter.t.sol | 386 ---------------- test_old/LBToken.t.sol | 241 ---------- test_old/LBTokenInternal.t.sol | 88 ---- 37 files changed, 709 insertions(+), 4639 deletions(-) rename src/interfaces/{IWAVAX.sol => IWNATIVE.sol} (64%) rename test/mocks/{WAVAX.sol => WNATIVE.sol} (83%) delete mode 100644 test_old/LBFactory.MultiPools.t.sol delete mode 100644 test_old/LBPair.Fees.t.sol delete mode 100644 test_old/LBPair.FlashLoans.t.sol delete mode 100644 test_old/LBPair.Liquidity.t.sol delete mode 100644 test_old/LBPair.Oracle.t.sol delete mode 100644 test_old/LBPair.Swaps.t.sol delete mode 100644 test_old/LBPair.t.sol delete mode 100644 test_old/LBQuoter.t.sol delete mode 100644 test_old/LBRouter.FeesOnLiquidityAdd.t.sol delete mode 100644 test_old/LBRouter.Fork.Swaps.t.sol delete mode 100644 test_old/LBRouter.Liquidity.t.sol delete mode 100644 test_old/LBRouter.Swaps.t.sol delete mode 100644 test_old/LBRouter.t.sol delete mode 100644 test_old/LBToken.t.sol delete mode 100644 test_old/LBTokenInternal.t.sol diff --git a/src/LBFactory.sol b/src/LBFactory.sol index f0c01715..70c80d45 100644 --- a/src/LBFactory.sol +++ b/src/LBFactory.sol @@ -597,7 +597,7 @@ contract LBFactory is PendingOwnable, ILBFactory { /** * @notice Function to add an asset to the whitelist of quote assets - * @param quoteAsset The quote asset (e.g: AVAX, USDC...) + * @param quoteAsset The quote asset (e.g: NATIVE, USDC...) */ function addQuoteAsset(IERC20 quoteAsset) external override onlyOwner { if (!_quoteAssetWhitelist.add(address(quoteAsset))) { @@ -609,7 +609,7 @@ contract LBFactory is PendingOwnable, ILBFactory { /** * @notice Function to remove an asset from the whitelist of quote assets - * @param quoteAsset The quote asset (e.g: AVAX, USDC...) + * @param quoteAsset The quote asset (e.g: NATIVE, USDC...) */ function removeQuoteAsset(IERC20 quoteAsset) external override onlyOwner { if (!_quoteAssetWhitelist.remove(address(quoteAsset))) revert LBFactory__QuoteAssetNotWhitelisted(quoteAsset); diff --git a/src/LBRouter.sol b/src/LBRouter.sol index 9218329c..0780bcfe 100644 --- a/src/LBRouter.sol +++ b/src/LBRouter.sol @@ -23,7 +23,7 @@ import {ILBLegacyRouter} from "./interfaces/ILBLegacyRouter.sol"; import {IJoeFactory} from "./interfaces/IJoeFactory.sol"; import {ILBLegacyFactory} from "./interfaces/ILBLegacyFactory.sol"; import {ILBFactory} from "./interfaces/ILBFactory.sol"; -import {IWAVAX} from "./interfaces/IWAVAX.sol"; +import {IWNATIVE} from "./interfaces/IWNATIVE.sol"; /** * @title Liquidity Book Router @@ -32,7 +32,7 @@ import {IWAVAX} from "./interfaces/IWAVAX.sol"; */ contract LBRouter is ILBRouter { using TokenHelper for IERC20; - using TokenHelper for IWAVAX; + using TokenHelper for IWNATIVE; using JoeLibrary for uint256; using PackedUint128Math for bytes32; @@ -40,7 +40,7 @@ contract LBRouter is ILBRouter { IJoeFactory private immutable _factoryV1; ILBLegacyFactory private immutable _legacyFactory; ILBLegacyRouter private immutable _legacyRouter; - IWAVAX private immutable _wavax; + IWNATIVE private immutable _wnative; modifier onlyFactoryOwner() { if (msg.sender != _factory.owner()) revert LBRouter__NotFactoryOwner(); @@ -66,27 +66,27 @@ contract LBRouter is ILBRouter { * @param factoryV1 Address of Joe V1 factory * @param legacyFactory Address of Joe V2 factory * @param legacyRouter Address of Joe V2 router - * @param wavax Address of WAVAX + * @param wnative Address of WNATIVE */ constructor( ILBFactory factory, IJoeFactory factoryV1, ILBLegacyFactory legacyFactory, ILBLegacyRouter legacyRouter, - IWAVAX wavax + IWNATIVE wnative ) { _factory = factory; _factoryV1 = factoryV1; _legacyFactory = legacyFactory; _legacyRouter = legacyRouter; - _wavax = wavax; + _wnative = wnative; } /** - * @dev Receive function that only accept AVAX from the WAVAX contract + * @dev Receive function that only accept NATIVE from the WNATIVE contract */ receive() external payable { - if (msg.sender != address(_wavax)) revert LBRouter__SenderIsNotWAVAX(); + if (msg.sender != address(_wnative)) revert LBRouter__SenderIsNotWNATIVE(); } /** @@ -122,11 +122,11 @@ contract LBRouter is ILBRouter { } /** - * View function to get the WAVAX address - * @return wavax The address of WAVAX + * View function to get the WNATIVE address + * @return wnative The address of WNATIVE */ - function getWAVAX() external view override returns (IWAVAX wavax) { - return _wavax; + function getWNATIVE() external view override returns (IWNATIVE wnative) { + return _wnative; } /** @@ -240,7 +240,7 @@ contract LBRouter is ILBRouter { } /** - * @notice Add liquidity with AVAX while performing safety checks + * @notice Add liquidity with NATIVE while performing safety checks * @dev This function is compliant with fee on transfer tokens * @param liquidityParameters The liquidity parameters * @return amountXAdded The amount of token X added @@ -250,7 +250,7 @@ contract LBRouter is ILBRouter { * @return depositIds The ids of the deposits * @return liquidityMinted The amount of liquidity minted */ - function addLiquidityAVAX(LiquidityParameters calldata liquidityParameters) + function addLiquidityNATIVE(LiquidityParameters calldata liquidityParameters) external payable override @@ -270,14 +270,14 @@ contract LBRouter is ILBRouter { ); if (liquidityParameters.tokenX != _LBPair.getTokenX()) revert LBRouter__WrongTokenOrder(); - if (liquidityParameters.tokenX == _wavax && liquidityParameters.amountX == msg.value) { - _wavaxDepositAndTransfer(address(_LBPair), msg.value); + if (liquidityParameters.tokenX == _wnative && liquidityParameters.amountX == msg.value) { + _wnativeDepositAndTransfer(address(_LBPair), msg.value); liquidityParameters.tokenY.safeTransferFrom(msg.sender, address(_LBPair), liquidityParameters.amountY); - } else if (liquidityParameters.tokenY == _wavax && liquidityParameters.amountY == msg.value) { + } else if (liquidityParameters.tokenY == _wnative && liquidityParameters.amountY == msg.value) { liquidityParameters.tokenX.safeTransferFrom(msg.sender, address(_LBPair), liquidityParameters.amountX); - _wavaxDepositAndTransfer(address(_LBPair), msg.value); + _wnativeDepositAndTransfer(address(_LBPair), msg.value); } else { - revert LBRouter__WrongAvaxLiquidityParameters( + revert LBRouter__WrongNativeLiquidityParameters( address(liquidityParameters.tokenX), address(liquidityParameters.tokenY), liquidityParameters.amountX, @@ -327,52 +327,52 @@ contract LBRouter is ILBRouter { } /** - * @notice Remove AVAX liquidity while performing safety checks + * @notice Remove NATIVE liquidity while performing safety checks * @dev This function is **NOT** compliant with fee on transfer tokens. * This is wanted as it would make users pays the fee on transfer twice, * use the `removeLiquidity` function to remove liquidity with fee on transfer tokens. * @param token The address of token * @param binStep The bin step of the LBPair * @param amountTokenMin The min amount to receive of token - * @param amountAVAXMin The min amount to receive of AVAX + * @param amountNATIVEMin The min amount to receive of NATIVE * @param ids The list of ids to burn * @param amounts The list of amounts to burn of each id in `_ids` * @param to The address of the recipient * @param deadline The deadline of the tx * @return amountToken Amount of token returned - * @return amountAVAX Amount of AVAX returned + * @return amountNATIVE Amount of NATIVE returned */ - function removeLiquidityAVAX( + function removeLiquidityNATIVE( IERC20 token, uint8 binStep, uint256 amountTokenMin, - uint256 amountAVAXMin, + uint256 amountNATIVEMin, uint256[] memory ids, uint256[] memory amounts, address payable to, uint256 deadline - ) external override ensure(deadline) returns (uint256 amountToken, uint256 amountAVAX) { - IWAVAX wavax = _wavax; + ) external override ensure(deadline) returns (uint256 amountToken, uint256 amountNATIVE) { + IWNATIVE wnative = _wnative; - ILBPair lbPair = ILBPair(_getLBPairInformation(token, IERC20(wavax), binStep, Version.V2_1)); + ILBPair lbPair = ILBPair(_getLBPairInformation(token, IERC20(wnative), binStep, Version.V2_1)); { - bool isAVAXTokenY = IERC20(wavax) == lbPair.getTokenY(); + bool isNATIVETokenY = IERC20(wnative) == lbPair.getTokenY(); - if (!isAVAXTokenY) { - (amountTokenMin, amountAVAXMin) = (amountAVAXMin, amountTokenMin); + if (!isNATIVETokenY) { + (amountTokenMin, amountNATIVEMin) = (amountNATIVEMin, amountTokenMin); } (uint256 amountX, uint256 amountY) = - _removeLiquidity(lbPair, amountTokenMin, amountAVAXMin, ids, amounts, address(this)); + _removeLiquidity(lbPair, amountTokenMin, amountNATIVEMin, ids, amounts, address(this)); - (amountToken, amountAVAX) = isAVAXTokenY ? (amountX, amountY) : (amountY, amountX); + (amountToken, amountNATIVE) = isNATIVETokenY ? (amountX, amountY) : (amountY, amountX); } token.safeTransfer(to, amountToken); - wavax.withdraw(amountAVAX); - _safeTransferAVAX(to, amountAVAX); + wnative.withdraw(amountNATIVE); + _safeTransferNATIVE(to, amountNATIVE); } /** @@ -401,22 +401,22 @@ contract LBRouter is ILBRouter { } /** - * @notice Swaps exact tokens for AVAX while performing safety checks + * @notice Swaps exact tokens for NATIVE while performing safety checks * @param amountIn The amount of token to send - * @param amountOutMinAVAX The min amount of AVAX to receive + * @param amountOutMinNATIVE The min amount of NATIVE to receive * @param path The path of the swap * @param to The address of the recipient * @param deadline The deadline of the tx * @return amountOut Output amount of the swap */ - function swapExactTokensForAVAX( + function swapExactTokensForNATIVE( uint256 amountIn, - uint256 amountOutMinAVAX, + uint256 amountOutMinNATIVE, Path memory path, address payable to, uint256 deadline ) external override ensure(deadline) verifyPathValidity(path) returns (uint256 amountOut) { - if (path.tokenPath[path.pairBinSteps.length] != IERC20(_wavax)) { + if (path.tokenPath[path.pairBinSteps.length] != IERC20(_wnative)) { revert LBRouter__InvalidTokenPath(address(path.tokenPath[path.pairBinSteps.length])); } @@ -426,21 +426,21 @@ contract LBRouter is ILBRouter { amountOut = _swapExactTokensForTokens(amountIn, pairs, path.versions, path.tokenPath, address(this)); - if (amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMinAVAX, amountOut); + if (amountOutMinNATIVE > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMinNATIVE, amountOut); - _wavax.withdraw(amountOut); - _safeTransferAVAX(to, amountOut); + _wnative.withdraw(amountOut); + _safeTransferNATIVE(to, amountOut); } /** - * @notice Swaps exact AVAX for tokens while performing safety checks + * @notice Swaps exact NATIVE for tokens while performing safety checks * @param amountOutMin The min amount of token to receive * @param path The path of the swap * @param to The address of the recipient * @param deadline The deadline of the tx * @return amountOut Output amount of the swap */ - function swapExactAVAXForTokens(uint256 amountOutMin, Path memory path, address to, uint256 deadline) + function swapExactNATIVEForTokens(uint256 amountOutMin, Path memory path, address to, uint256 deadline) external payable override @@ -448,11 +448,11 @@ contract LBRouter is ILBRouter { verifyPathValidity(path) returns (uint256 amountOut) { - if (path.tokenPath[0] != IERC20(_wavax)) revert LBRouter__InvalidTokenPath(address(path.tokenPath[0])); + if (path.tokenPath[0] != IERC20(_wnative)) revert LBRouter__InvalidTokenPath(address(path.tokenPath[0])); address[] memory pairs = _getPairs(path.pairBinSteps, path.versions, path.tokenPath); - _wavaxDepositAndTransfer(pairs[0], msg.value); + _wnativeDepositAndTransfer(pairs[0], msg.value); amountOut = _swapExactTokensForTokens(msg.value, pairs, path.versions, path.tokenPath, to); @@ -491,27 +491,27 @@ contract LBRouter is ILBRouter { } /** - * @notice Swaps tokens for exact AVAX while performing safety checks - * @param amountAVAXOut The amount of AVAX to receive + * @notice Swaps tokens for exact NATIVE while performing safety checks + * @param amountNATIVEOut The amount of NATIVE to receive * @param amountInMax The max amount of token to send * @param path The path of the swap * @param to The address of the recipient * @param deadline The deadline of the tx * @return amountsIn path amounts for every step of the swap */ - function swapTokensForExactAVAX( - uint256 amountAVAXOut, + function swapTokensForExactNATIVE( + uint256 amountNATIVEOut, uint256 amountInMax, Path memory path, address payable to, uint256 deadline ) external override ensure(deadline) verifyPathValidity(path) returns (uint256[] memory amountsIn) { - if (path.tokenPath[path.pairBinSteps.length] != IERC20(_wavax)) { + if (path.tokenPath[path.pairBinSteps.length] != IERC20(_wnative)) { revert LBRouter__InvalidTokenPath(address(path.tokenPath[path.pairBinSteps.length])); } address[] memory pairs = _getPairs(path.pairBinSteps, path.versions, path.tokenPath); - amountsIn = _getAmountsIn(path.versions, pairs, path.tokenPath, amountAVAXOut); + amountsIn = _getAmountsIn(path.versions, pairs, path.tokenPath, amountNATIVEOut); if (amountsIn[0] > amountInMax) revert LBRouter__MaxAmountInExceeded(amountInMax, amountsIn[0]); @@ -520,22 +520,22 @@ contract LBRouter is ILBRouter { uint256 _amountOutReal = _swapTokensForExactTokens(pairs, path.versions, path.tokenPath, amountsIn, address(this)); - if (_amountOutReal < amountAVAXOut) revert LBRouter__InsufficientAmountOut(amountAVAXOut, _amountOutReal); + if (_amountOutReal < amountNATIVEOut) revert LBRouter__InsufficientAmountOut(amountNATIVEOut, _amountOutReal); - _wavax.withdraw(_amountOutReal); - _safeTransferAVAX(to, _amountOutReal); + _wnative.withdraw(_amountOutReal); + _safeTransferNATIVE(to, _amountOutReal); } /** - * @notice Swaps AVAX for exact tokens while performing safety checks - * @dev Will refund any AVAX amount sent in excess to `msg.sender` + * @notice Swaps NATIVE for exact tokens while performing safety checks + * @dev Will refund any NATIVE amount sent in excess to `msg.sender` * @param amountOut The amount of tokens to receive * @param path The path of the swap * @param to The address of the recipient * @param deadline The deadline of the tx * @return amountsIn path amounts for every step of the swap */ - function swapAVAXForExactTokens(uint256 amountOut, Path memory path, address to, uint256 deadline) + function swapNATIVEForExactTokens(uint256 amountOut, Path memory path, address to, uint256 deadline) external payable override @@ -543,20 +543,20 @@ contract LBRouter is ILBRouter { verifyPathValidity(path) returns (uint256[] memory amountsIn) { - if (path.tokenPath[0] != IERC20(_wavax)) revert LBRouter__InvalidTokenPath(address(path.tokenPath[0])); + if (path.tokenPath[0] != IERC20(_wnative)) revert LBRouter__InvalidTokenPath(address(path.tokenPath[0])); address[] memory pairs = _getPairs(path.pairBinSteps, path.versions, path.tokenPath); amountsIn = _getAmountsIn(path.versions, pairs, path.tokenPath, amountOut); if (amountsIn[0] > msg.value) revert LBRouter__MaxAmountInExceeded(msg.value, amountsIn[0]); - _wavaxDepositAndTransfer(pairs[0], amountsIn[0]); + _wnativeDepositAndTransfer(pairs[0], amountsIn[0]); uint256 amountOutReal = _swapTokensForExactTokens(pairs, path.versions, path.tokenPath, amountsIn, to); if (amountOutReal < amountOut) revert LBRouter__InsufficientAmountOut(amountOut, amountOutReal); - if (msg.value > amountsIn[0]) _safeTransferAVAX(msg.sender, msg.value - amountsIn[0]); + if (msg.value > amountsIn[0]) _safeTransferNATIVE(msg.sender, msg.value - amountsIn[0]); } /** @@ -590,55 +590,55 @@ contract LBRouter is ILBRouter { } /** - * @notice Swaps exact tokens for AVAX while performing safety checks supporting for fee on transfer tokens + * @notice Swaps exact tokens for NATIVE while performing safety checks supporting for fee on transfer tokens * @param amountIn The amount of token to send - * @param amountOutMinAVAX The min amount of AVAX to receive + * @param amountOutMinNATIVE The min amount of NATIVE to receive * @param path The path of the swap * @param to The address of the recipient * @param deadline The deadline of the tx * @return amountOut Output amount of the swap */ - function swapExactTokensForAVAXSupportingFeeOnTransferTokens( + function swapExactTokensForNATIVESupportingFeeOnTransferTokens( uint256 amountIn, - uint256 amountOutMinAVAX, + uint256 amountOutMinNATIVE, Path memory path, address payable to, uint256 deadline ) external override ensure(deadline) verifyPathValidity(path) returns (uint256 amountOut) { - if (path.tokenPath[path.pairBinSteps.length] != IERC20(_wavax)) { + if (path.tokenPath[path.pairBinSteps.length] != IERC20(_wnative)) { revert LBRouter__InvalidTokenPath(address(path.tokenPath[path.pairBinSteps.length])); } address[] memory pairs = _getPairs(path.pairBinSteps, path.versions, path.tokenPath); - uint256 balanceBefore = _wavax.balanceOf(address(this)); + uint256 balanceBefore = _wnative.balanceOf(address(this)); path.tokenPath[0].safeTransferFrom(msg.sender, pairs[0], amountIn); _swapSupportingFeeOnTransferTokens(pairs, path.versions, path.tokenPath, address(this)); - amountOut = _wavax.balanceOf(address(this)) - balanceBefore; - if (amountOutMinAVAX > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMinAVAX, amountOut); + amountOut = _wnative.balanceOf(address(this)) - balanceBefore; + if (amountOutMinNATIVE > amountOut) revert LBRouter__InsufficientAmountOut(amountOutMinNATIVE, amountOut); - _wavax.withdraw(amountOut); - _safeTransferAVAX(to, amountOut); + _wnative.withdraw(amountOut); + _safeTransferNATIVE(to, amountOut); } /** - * @notice Swaps exact AVAX for tokens while performing safety checks supporting for fee on transfer tokens + * @notice Swaps exact NATIVE for tokens while performing safety checks supporting for fee on transfer tokens * @param amountOutMin The min amount of token to receive * @param path The path of the swap * @param to The address of the recipient * @param deadline The deadline of the tx * @return amountOut Output amount of the swap */ - function swapExactAVAXForTokensSupportingFeeOnTransferTokens( + function swapExactNATIVEForTokensSupportingFeeOnTransferTokens( uint256 amountOutMin, Path memory path, address to, uint256 deadline ) external payable override ensure(deadline) verifyPathValidity(path) returns (uint256 amountOut) { - if (path.tokenPath[0] != IERC20(_wavax)) revert LBRouter__InvalidTokenPath(address(path.tokenPath[0])); + if (path.tokenPath[0] != IERC20(_wnative)) revert LBRouter__InvalidTokenPath(address(path.tokenPath[0])); address[] memory pairs = _getPairs(path.pairBinSteps, path.versions, path.tokenPath); @@ -646,7 +646,7 @@ contract LBRouter is ILBRouter { uint256 balanceBefore = targetToken.balanceOf(to); - _wavaxDepositAndTransfer(pairs[0], msg.value); + _wnativeDepositAndTransfer(pairs[0], msg.value); _swapSupportingFeeOnTransferTokens(pairs, path.versions, path.tokenPath, to); @@ -664,7 +664,7 @@ contract LBRouter is ILBRouter { function sweep(IERC20 token, address to, uint256 amount) external override onlyFactoryOwner { if (address(token) == address(0)) { if (amount == type(uint256).max) amount = address(this).balance; - _safeTransferAVAX(to, amount); + _safeTransferNATIVE(to, amount); } else { if (amount == type(uint256).max) amount = token.balanceOf(address(this)); token.safeTransfer(to, amount); @@ -1077,22 +1077,22 @@ contract LBRouter is ILBRouter { } /** - * @notice Helper function to transfer AVAX + * @notice Helper function to transfer NATIVE * @param to The address of the recipient - * @param amount The AVAX amount to send + * @param amount The NATIVE amount to send */ - function _safeTransferAVAX(address to, uint256 amount) private { + function _safeTransferNATIVE(address to, uint256 amount) private { (bool success,) = to.call{value: amount}(""); - if (!success) revert LBRouter__FailedToSendAVAX(to, amount); + if (!success) revert LBRouter__FailedToSendNATIVE(to, amount); } /** - * @notice Helper function to deposit and transfer _wavax + * @notice Helper function to deposit and transfer _wnative * @param to The address of the recipient - * @param amount The AVAX amount to wrap + * @param amount The NATIVE amount to wrap */ - function _wavaxDepositAndTransfer(address to, uint256 amount) private { - _wavax.deposit{value: amount}(); - _wavax.safeTransfer(to, amount); + function _wnativeDepositAndTransfer(address to, uint256 amount) private { + _wnative.deposit{value: amount}(); + _wnative.safeTransfer(to, amount); } } diff --git a/src/interfaces/ILBLegacyRouter.sol b/src/interfaces/ILBLegacyRouter.sol index a871f2c6..4df2bef2 100644 --- a/src/interfaces/ILBLegacyRouter.sol +++ b/src/interfaces/ILBLegacyRouter.sol @@ -8,7 +8,7 @@ import {ILBFactory} from "./ILBFactory.sol"; import {IJoeFactory} from "./IJoeFactory.sol"; import {ILBLegacyPair} from "./ILBLegacyPair.sol"; import {ILBToken} from "./ILBToken.sol"; -import {IWAVAX} from "./IWAVAX.sol"; +import {IWNATIVE} from "./IWNATIVE.sol"; /// @title Liquidity Book Router Interface /// @author Trader Joe diff --git a/src/interfaces/ILBRouter.sol b/src/interfaces/ILBRouter.sol index 9cdc21a7..63463a26 100644 --- a/src/interfaces/ILBRouter.sol +++ b/src/interfaces/ILBRouter.sol @@ -10,7 +10,7 @@ import {ILBLegacyFactory} from "./ILBLegacyFactory.sol"; import {ILBLegacyRouter} from "./ILBLegacyRouter.sol"; import {ILBPair} from "./ILBPair.sol"; import {ILBToken} from "./ILBToken.sol"; -import {IWAVAX} from "./IWAVAX.sol"; +import {IWNATIVE} from "./IWNATIVE.sol"; /** * @title Liquidity Book Router Interface @@ -18,7 +18,7 @@ import {IWAVAX} from "./IWAVAX.sol"; * @notice Required interface of LBRouter contract */ interface ILBRouter { - error LBRouter__SenderIsNotWAVAX(); + error LBRouter__SenderIsNotWNATIVE(); error LBRouter__PairNotCreated(address tokenX, address tokenY, uint256 binStep); error LBRouter__WrongAmounts(uint256 amount, uint256 reserve); error LBRouter__SwapOverflows(uint256 id); @@ -32,14 +32,14 @@ interface ILBRouter { error LBRouter__IdSlippageCaught(uint256 activeIdDesired, uint256 idSlippage, uint256 activeId); error LBRouter__AmountSlippageCaught(uint256 amountXMin, uint256 amountX, uint256 amountYMin, uint256 amountY); error LBRouter__IdDesiredOverflows(uint256 idDesired, uint256 idSlippage); - error LBRouter__FailedToSendAVAX(address recipient, uint256 amount); + error LBRouter__FailedToSendNATIVE(address recipient, uint256 amount); error LBRouter__DeadlineExceeded(uint256 deadline, uint256 currentTimestamp); error LBRouter__AmountSlippageBPTooBig(uint256 amountSlippage); error LBRouter__InsufficientAmountOut(uint256 amountOutMin, uint256 amountOut); error LBRouter__MaxAmountInExceeded(uint256 amountInMax, uint256 amountIn); error LBRouter__InvalidTokenPath(address wrongToken); error LBRouter__InvalidVersion(uint256 version); - error LBRouter__WrongAvaxLiquidityParameters( + error LBRouter__WrongNativeLiquidityParameters( address tokenX, address tokenY, uint256 amountX, uint256 amountY, uint256 msgValue ); @@ -110,7 +110,7 @@ interface ILBRouter { function getLegacyRouter() external view returns (ILBLegacyRouter); - function getWAVAX() external view returns (IWAVAX); + function getWNATIVE() external view returns (IWNATIVE); function getIdFromPrice(ILBPair LBPair, uint256 price) external view returns (uint24); @@ -141,7 +141,7 @@ interface ILBRouter { uint256[] memory liquidityMinted ); - function addLiquidityAVAX(LiquidityParameters calldata liquidityParameters) + function addLiquidityNATIVE(LiquidityParameters calldata liquidityParameters) external payable returns ( @@ -165,16 +165,16 @@ interface ILBRouter { uint256 deadline ) external returns (uint256 amountX, uint256 amountY); - function removeLiquidityAVAX( + function removeLiquidityNATIVE( IERC20 token, uint8 binStep, uint256 amountTokenMin, - uint256 amountAVAXMin, + uint256 amountNATIVEMin, uint256[] memory ids, uint256[] memory amounts, address payable to, uint256 deadline - ) external returns (uint256 amountToken, uint256 amountAVAX); + ) external returns (uint256 amountToken, uint256 amountNATIVE); function swapExactTokensForTokens( uint256 amountIn, @@ -184,15 +184,15 @@ interface ILBRouter { uint256 deadline ) external returns (uint256 amountOut); - function swapExactTokensForAVAX( + function swapExactTokensForNATIVE( uint256 amountIn, - uint256 amountOutMinAVAX, + uint256 amountOutMinNATIVE, Path memory path, address payable to, uint256 deadline ) external returns (uint256 amountOut); - function swapExactAVAXForTokens(uint256 amountOutMin, Path memory path, address to, uint256 deadline) + function swapExactNATIVEForTokens(uint256 amountOutMin, Path memory path, address to, uint256 deadline) external payable returns (uint256 amountOut); @@ -205,7 +205,7 @@ interface ILBRouter { uint256 deadline ) external returns (uint256[] memory amountsIn); - function swapTokensForExactAVAX( + function swapTokensForExactNATIVE( uint256 amountOut, uint256 amountInMax, Path memory path, @@ -213,7 +213,7 @@ interface ILBRouter { uint256 deadline ) external returns (uint256[] memory amountsIn); - function swapAVAXForExactTokens(uint256 amountOut, Path memory path, address to, uint256 deadline) + function swapNATIVEForExactTokens(uint256 amountOut, Path memory path, address to, uint256 deadline) external payable returns (uint256[] memory amountsIn); @@ -226,15 +226,15 @@ interface ILBRouter { uint256 deadline ) external returns (uint256 amountOut); - function swapExactTokensForAVAXSupportingFeeOnTransferTokens( + function swapExactTokensForNATIVESupportingFeeOnTransferTokens( uint256 amountIn, - uint256 amountOutMinAVAX, + uint256 amountOutMinNATIVE, Path memory path, address payable to, uint256 deadline ) external returns (uint256 amountOut); - function swapExactAVAXForTokensSupportingFeeOnTransferTokens( + function swapExactNATIVEForTokensSupportingFeeOnTransferTokens( uint256 amountOutMin, Path memory path, address to, diff --git a/src/interfaces/IWAVAX.sol b/src/interfaces/IWNATIVE.sol similarity index 64% rename from src/interfaces/IWAVAX.sol rename to src/interfaces/IWNATIVE.sol index e40ba884..ec26b5e2 100644 --- a/src/interfaces/IWAVAX.sol +++ b/src/interfaces/IWNATIVE.sol @@ -5,10 +5,10 @@ pragma solidity 0.8.10; import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; /** - * @title WAVAX Interface - * @notice Required interface of Wrapped AVAX contract + * @title WNATIVE Interface + * @notice Required interface of Wrapped NATIVE contract */ -interface IWAVAX is IERC20 { +interface IWNATIVE is IERC20 { function deposit() external payable; function withdraw(uint256) external; diff --git a/test/Faucet.t.sol b/test/Faucet.t.sol index 6c75b37b..a5f4a5cc 100644 --- a/test/Faucet.t.sol +++ b/test/Faucet.t.sol @@ -19,18 +19,18 @@ contract FaucetTest is Test { ERC20Mock token6; ERC20Mock token12; - IERC20 AVAX = IERC20(address(0)); + IERC20 NATIVE = IERC20(address(0)); uint96 constant TOKEN6_PER_REQUEST = 1_000e6; uint96 constant TOKEN12_PER_REQUEST = 1_000e12; - uint96 constant AVAX_PER_REQUEST = 1e18; + uint96 constant NATIVE_PER_REQUEST = 1e18; uint256 constant REQUEST_COOLDOWN = 24 hours; function setUp() public { token6 = new ERC20Mock( 6); token12 = new ERC20Mock( 12); - faucet = new Faucet{value: 10 * AVAX_PER_REQUEST}(AVAX_PER_REQUEST, REQUEST_COOLDOWN); + faucet = new Faucet{value: 10 * NATIVE_PER_REQUEST}(NATIVE_PER_REQUEST, REQUEST_COOLDOWN); token6.mint(address(faucet), 10 * TOKEN6_PER_REQUEST); token12.mint(address(faucet), 10 * TOKEN12_PER_REQUEST); @@ -93,13 +93,13 @@ contract FaucetTest is Test { vm.stopPrank(); vm.expectRevert("Not a faucet token"); - faucet.removeFaucetToken(AVAX); + faucet.removeFaucetToken(NATIVE); faucet.removeFaucetToken(IERC20(token6)); IERC20 faucetToken; (faucetToken,) = faucet.faucetTokens(0); - assertEq(address(faucetToken), address(AVAX)); + assertEq(address(faucetToken), address(NATIVE)); (faucetToken,) = faucet.faucetTokens(1); assertEq(address(faucetToken), address(token12)); @@ -117,7 +117,7 @@ contract FaucetTest is Test { assertEq(token6.balanceOf(ALICE), TOKEN6_PER_REQUEST); assertEq(token12.balanceOf(ALICE), TOKEN12_PER_REQUEST); - assertEq(ALICE.balance, AVAX_PER_REQUEST); + assertEq(ALICE.balance, NATIVE_PER_REQUEST); vm.startPrank(BOB, BOB); faucet.request(); @@ -125,7 +125,7 @@ contract FaucetTest is Test { assertEq(token6.balanceOf(BOB), TOKEN6_PER_REQUEST); assertEq(token12.balanceOf(BOB), TOKEN12_PER_REQUEST); - assertEq(BOB.balance, AVAX_PER_REQUEST); + assertEq(BOB.balance, NATIVE_PER_REQUEST); } function testRequestFaucetTokensByOperator() external { @@ -148,11 +148,11 @@ contract FaucetTest is Test { assertEq(token6.balanceOf(ALICE), TOKEN6_PER_REQUEST); assertEq(token12.balanceOf(ALICE), TOKEN12_PER_REQUEST); - assertEq(ALICE.balance, AVAX_PER_REQUEST); + assertEq(ALICE.balance, NATIVE_PER_REQUEST); assertEq(token6.balanceOf(BOB), TOKEN6_PER_REQUEST); assertEq(token12.balanceOf(BOB), TOKEN12_PER_REQUEST); - assertEq(BOB.balance, AVAX_PER_REQUEST); + assertEq(BOB.balance, NATIVE_PER_REQUEST); vm.startPrank(BOB, BOB); vm.expectRevert("Too many requests"); @@ -163,16 +163,16 @@ contract FaucetTest is Test { function testSetRequestAmount() external { uint96 newRequestToken6Amount = 100e6; uint96 newRequestToken12Amount = 100e12; - uint96 newRequestAvaxAmount = 2e18; + uint96 newRequestNativeAmount = 2e18; vm.startPrank(ALICE, ALICE); vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); - faucet.setAmountPerRequest(AVAX, newRequestToken6Amount); + faucet.setAmountPerRequest(NATIVE, newRequestToken6Amount); vm.stopPrank(); faucet.setAmountPerRequest(IERC20(token6), newRequestToken6Amount); faucet.setAmountPerRequest(IERC20(token12), newRequestToken12Amount); - faucet.setAmountPerRequest(AVAX, newRequestAvaxAmount); + faucet.setAmountPerRequest(NATIVE, newRequestNativeAmount); vm.startPrank(ALICE, ALICE); faucet.request(); @@ -180,22 +180,22 @@ contract FaucetTest is Test { assertEq(token6.balanceOf(ALICE), newRequestToken6Amount); assertEq(token12.balanceOf(ALICE), newRequestToken12Amount); - assertEq(ALICE.balance, newRequestAvaxAmount); + assertEq(ALICE.balance, newRequestNativeAmount); } - function testWithdrawAvax() external { + function testWithdrawNative() external { assertEq(ALICE.balance, 0); - faucet.withdrawToken(AVAX, ALICE, 1e18); + faucet.withdrawToken(NATIVE, ALICE, 1e18); assertEq(ALICE.balance, 1e18); vm.startPrank(ALICE, ALICE); vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); - faucet.withdrawToken(AVAX, ALICE, 1e18); + faucet.withdrawToken(NATIVE, ALICE, 1e18); vm.stopPrank(); - // Leave 0.99...9 AVAX in the contract - faucet.withdrawToken(AVAX, ALICE, address(faucet).balance - (AVAX_PER_REQUEST - 1)); - assertEq(address(faucet).balance, AVAX_PER_REQUEST - 1); + // Leave 0.99...9 NATIVE in the contract + faucet.withdrawToken(NATIVE, ALICE, address(faucet).balance - (NATIVE_PER_REQUEST - 1)); + assertEq(address(faucet).balance, NATIVE_PER_REQUEST - 1); vm.startPrank(BOB, BOB); faucet.request(); @@ -227,7 +227,7 @@ contract FaucetTest is Test { assertEq(token6.balanceOf(BOB), 0); assertEq(token12.balanceOf(BOB), TOKEN12_PER_REQUEST); - assertEq(BOB.balance, AVAX_PER_REQUEST); + assertEq(BOB.balance, NATIVE_PER_REQUEST); } function testSetRequestCooldown() external { @@ -268,6 +268,6 @@ contract FaucetTest is Test { assertEq(token6.balanceOf(ALICE), 2 * TOKEN6_PER_REQUEST); assertEq(token12.balanceOf(ALICE), 2 * TOKEN12_PER_REQUEST); - assertEq(ALICE.balance, 2 * AVAX_PER_REQUEST); + assertEq(ALICE.balance, 2 * NATIVE_PER_REQUEST); } } diff --git a/test/LBFactory.t.sol b/test/LBFactory.t.sol index fab72ffb..a59c5840 100644 --- a/test/LBFactory.t.sol +++ b/test/LBFactory.t.sol @@ -438,7 +438,7 @@ contract LiquidityBinFactoryTest is TestHelper { } function test_SetFeesParametersOnPair() public { - ILBPair pair = factory.createLBPair(wavax, usdc, ID_ONE, DEFAULT_BIN_STEP); + ILBPair pair = factory.createLBPair(wnative, usdc, ID_ONE, DEFAULT_BIN_STEP); addLiquidity(DEV, DEV, LBPair(address(pair)), ID_ONE, 100e18, 100e18, 10, 10); // Do swaps to increase the variable fee parameters @@ -453,7 +453,7 @@ contract LiquidityBinFactoryTest is TestHelper { path.tokenPath = new IERC20[](2); path.tokenPath[0] = usdc; - path.tokenPath[1] = wavax; + path.tokenPath[1] = wnative; router.swapExactTokensForTokens(50e18, 0, path, address(this), block.timestamp + 1); vm.warp(100); router.swapExactTokensForTokens(10e18, 0, path, address(this), block.timestamp + 1); @@ -479,7 +479,7 @@ contract LiquidityBinFactoryTest is TestHelper { ); factory.setFeesParametersOnPair( - wavax, + wnative, usdc, DEFAULT_BIN_STEP, DEFAULT_BASE_FACTOR * 2, @@ -527,7 +527,7 @@ contract LiquidityBinFactoryTest is TestHelper { vm.prank(ALICE); vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); factory.setFeesParametersOnPair( - wavax, + wnative, usdc, DEFAULT_BIN_STEP, DEFAULT_BASE_FACTOR * 2, diff --git a/test/LBPairFees.t.sol b/test/LBPairFees.t.sol index c9c47c44..beda3fea 100644 --- a/test/LBPairFees.t.sol +++ b/test/LBPairFees.t.sol @@ -14,32 +14,32 @@ contract LBPairFeesTest is TestHelper { function setUp() public override { super.setUp(); - pairWavax = createLBPair(wavax, usdc); + pairWnative = createLBPair(wnative, usdc); - addLiquidity(DEV, DEV, pairWavax, ID_ONE, amountX, amountY, 10, 10); - require(wavax.balanceOf(DEV) == 0 && usdc.balanceOf(DEV) == 0, "setUp::1"); + addLiquidity(DEV, DEV, pairWnative, ID_ONE, amountX, amountY, 10, 10); + require(wnative.balanceOf(DEV) == 0 && usdc.balanceOf(DEV) == 0, "setUp::1"); } function testFuzz_SwapInX(uint128 amountOut) external { vm.assume(amountOut > 0 && amountOut <= 1e18); - (uint128 amountIn, uint128 amountOutLeft,) = pairWavax.getSwapIn(amountOut, true); + (uint128 amountIn, uint128 amountOutLeft,) = pairWnative.getSwapIn(amountOut, true); assertEq(amountOutLeft, 0, "testFuzz_SwapInFeesAmounts::1"); - deal(address(wavax), ALICE, amountIn); + deal(address(wnative), ALICE, amountIn); vm.prank(ALICE); - wavax.transfer(address(pairWavax), amountIn); - pairWavax.swap(true, ALICE); + wnative.transfer(address(pairWnative), amountIn); + pairWnative.swap(true, ALICE); - assertEq(wavax.balanceOf(ALICE), 0, "testFuzz_SwapInFeesAmounts::2"); + assertEq(wnative.balanceOf(ALICE), 0, "testFuzz_SwapInFeesAmounts::2"); assertEq(usdc.balanceOf(ALICE), amountOut, "testFuzz_SwapInFeesAmounts::3"); - removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + removeLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 10, 10); - (uint128 protocolFeeX,) = pairWavax.getProtocolFees(); + (uint128 protocolFeeX,) = pairWnative.getProtocolFees(); - uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceX = wnative.balanceOf(DEV); uint256 balanceY = usdc.balanceOf(DEV); assertEq(balanceX, amountX + amountIn - protocolFeeX, "testFuzz_SwapInFeesAmounts::4"); @@ -49,23 +49,23 @@ contract LBPairFeesTest is TestHelper { function testFuzz_SwapInY(uint128 amountOut) external { vm.assume(amountOut > 0 && amountOut <= 1e18); - (uint128 amountIn, uint128 amountOutLeft,) = pairWavax.getSwapIn(amountOut, false); + (uint128 amountIn, uint128 amountOutLeft,) = pairWnative.getSwapIn(amountOut, false); assertEq(amountOutLeft, 0, "testFuzz_SwapInFeesAmounts::1"); deal(address(usdc), ALICE, amountIn); vm.prank(ALICE); - usdc.transfer(address(pairWavax), amountIn); - pairWavax.swap(false, ALICE); + usdc.transfer(address(pairWnative), amountIn); + pairWnative.swap(false, ALICE); assertEq(usdc.balanceOf(ALICE), 0, "testFuzz_SwapInFeesAmounts::2"); - assertEq(wavax.balanceOf(ALICE), amountOut, "testFuzz_SwapInFeesAmounts::3"); + assertEq(wnative.balanceOf(ALICE), amountOut, "testFuzz_SwapInFeesAmounts::3"); - removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + removeLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 10, 10); - (, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + (, uint128 protocolFeeY) = pairWnative.getProtocolFees(); - uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceX = wnative.balanceOf(DEV); uint256 balanceY = usdc.balanceOf(DEV); assertEq(balanceX, amountX - amountOut, "testFuzz_SwapInFeesAmounts::4"); @@ -73,23 +73,23 @@ contract LBPairFeesTest is TestHelper { } function testFuzz_SwapOutX(uint128 amountIn) external { - (uint128 amountInLeft, uint128 amountOut,) = pairWavax.getSwapOut(amountIn, true); + (uint128 amountInLeft, uint128 amountOut,) = pairWnative.getSwapOut(amountIn, true); vm.assume(amountOut > 0 && amountInLeft == 0); - deal(address(wavax), ALICE, amountIn); + deal(address(wnative), ALICE, amountIn); vm.prank(ALICE); - wavax.transfer(address(pairWavax), amountIn); - pairWavax.swap(true, ALICE); + wnative.transfer(address(pairWnative), amountIn); + pairWnative.swap(true, ALICE); - assertEq(wavax.balanceOf(ALICE), 0, "testFuzz_SwapInFeesAmounts::2"); + assertEq(wnative.balanceOf(ALICE), 0, "testFuzz_SwapInFeesAmounts::2"); assertEq(usdc.balanceOf(ALICE), amountOut, "testFuzz_SwapInFeesAmounts::3"); - removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + removeLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 10, 10); - (uint128 protocolFeeX,) = pairWavax.getProtocolFees(); + (uint128 protocolFeeX,) = pairWnative.getProtocolFees(); - uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceX = wnative.balanceOf(DEV); uint256 balanceY = usdc.balanceOf(DEV); assertEq(balanceX, amountX + amountIn - protocolFeeX, "testFuzz_SwapInFeesAmounts::4"); @@ -97,23 +97,23 @@ contract LBPairFeesTest is TestHelper { } function testFuzz_SwapOutY(uint128 amountIn) external { - (uint128 amountInLeft, uint128 amountOut,) = pairWavax.getSwapOut(amountIn, false); + (uint128 amountInLeft, uint128 amountOut,) = pairWnative.getSwapOut(amountIn, false); vm.assume(amountOut > 0 && amountInLeft == 0); deal(address(usdc), ALICE, amountIn); vm.prank(ALICE); - usdc.transfer(address(pairWavax), amountIn); - pairWavax.swap(false, ALICE); + usdc.transfer(address(pairWnative), amountIn); + pairWnative.swap(false, ALICE); assertEq(usdc.balanceOf(ALICE), 0, "testFuzz_SwapInFeesAmounts::2"); - assertEq(wavax.balanceOf(ALICE), amountOut, "testFuzz_SwapInFeesAmounts::3"); + assertEq(wnative.balanceOf(ALICE), amountOut, "testFuzz_SwapInFeesAmounts::3"); - removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + removeLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 10, 10); - (, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + (, uint128 protocolFeeY) = pairWnative.getProtocolFees(); - uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceX = wnative.balanceOf(DEV); uint256 balanceY = usdc.balanceOf(DEV); assertEq(balanceX, amountX - amountOut, "testFuzz_SwapInFeesAmounts::4"); @@ -123,36 +123,36 @@ contract LBPairFeesTest is TestHelper { function testFuzz_SwapInXAndY(uint128 amountXOut, uint128 amountYOut) external { vm.assume(amountXOut > 0 && amountXOut <= 1e18 && amountYOut > 0 && amountYOut <= 1e18); - (uint128 amountXIn, uint128 amountYOutLeft,) = pairWavax.getSwapIn(amountYOut, true); + (uint128 amountXIn, uint128 amountYOutLeft,) = pairWnative.getSwapIn(amountYOut, true); assertEq(amountYOutLeft, 0, "testFuzz_SwapInFeesAmounts::1"); - deal(address(wavax), BOB, 1e36); + deal(address(wnative), BOB, 1e36); deal(address(usdc), BOB, 1e36); vm.prank(BOB); - wavax.transfer(address(pairWavax), amountXIn); - pairWavax.swap(true, ALICE); + wnative.transfer(address(pairWnative), amountXIn); + pairWnative.swap(true, ALICE); assertEq(usdc.balanceOf(ALICE), amountYOut, "testFuzz_SwapInFeesAmounts::2"); - (uint128 amountYIn, uint128 amountXOutLeft,) = pairWavax.getSwapIn(amountXOut, false); + (uint128 amountYIn, uint128 amountXOutLeft,) = pairWnative.getSwapIn(amountXOut, false); assertEq(amountXOutLeft, 0, "testFuzz_SwapInFeesAmounts::3"); vm.prank(BOB); - usdc.transfer(address(pairWavax), amountYIn); - pairWavax.swap(false, ALICE); + usdc.transfer(address(pairWnative), amountYIn); + pairWnative.swap(false, ALICE); - uint256 realAmountXOut = wavax.balanceOf(ALICE); + uint256 realAmountXOut = wnative.balanceOf(ALICE); assertGe(realAmountXOut, amountXOut, "testFuzz_SwapInFeesAmounts::4"); - removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + removeLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 10, 10); - (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWnative.getProtocolFees(); - assertEq(wavax.balanceOf(address(pairWavax)), protocolFeeX, "testFuzz_SwapInFeesAmounts::5"); - assertEq(usdc.balanceOf(address(pairWavax)), protocolFeeY, "testFuzz_SwapInFeesAmounts::6"); + assertEq(wnative.balanceOf(address(pairWnative)), protocolFeeX, "testFuzz_SwapInFeesAmounts::5"); + assertEq(usdc.balanceOf(address(pairWnative)), protocolFeeY, "testFuzz_SwapInFeesAmounts::6"); - uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceX = wnative.balanceOf(DEV); uint256 balanceY = usdc.balanceOf(DEV); assertEq(balanceX, amountX + amountXIn - realAmountXOut - protocolFeeX, "testFuzz_SwapInFeesAmounts::7"); @@ -162,36 +162,36 @@ contract LBPairFeesTest is TestHelper { function testFuzz_SwapInYandX(uint128 amountYOut, uint128 amountXOut) external { vm.assume(amountXOut > 0 && amountXOut <= 1e18 && amountYOut > 0 && amountYOut <= 1e18); - (uint128 amountYIn, uint128 amountXOutLeft,) = pairWavax.getSwapIn(amountXOut, false); + (uint128 amountYIn, uint128 amountXOutLeft,) = pairWnative.getSwapIn(amountXOut, false); assertEq(amountXOutLeft, 0, "testFuzz_SwapInFeesAmounts::1"); - deal(address(wavax), BOB, 1e36); + deal(address(wnative), BOB, 1e36); deal(address(usdc), BOB, 1e36); vm.prank(BOB); - usdc.transfer(address(pairWavax), amountYIn); - pairWavax.swap(false, ALICE); + usdc.transfer(address(pairWnative), amountYIn); + pairWnative.swap(false, ALICE); - assertEq(wavax.balanceOf(ALICE), amountXOut, "testFuzz_SwapInFeesAmounts::2"); + assertEq(wnative.balanceOf(ALICE), amountXOut, "testFuzz_SwapInFeesAmounts::2"); - (uint128 amountXIn, uint128 amountYOutLeft,) = pairWavax.getSwapIn(amountYOut, true); + (uint128 amountXIn, uint128 amountYOutLeft,) = pairWnative.getSwapIn(amountYOut, true); assertEq(amountYOutLeft, 0, "testFuzz_SwapInFeesAmounts::3"); vm.prank(BOB); - wavax.transfer(address(pairWavax), amountXIn); - pairWavax.swap(true, ALICE); + wnative.transfer(address(pairWnative), amountXIn); + pairWnative.swap(true, ALICE); uint256 realAmountYOut = usdc.balanceOf(ALICE); assertGe(realAmountYOut, amountYOut, "testFuzz_SwapInFeesAmounts::4"); - removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + removeLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 10, 10); - (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWnative.getProtocolFees(); - assertEq(wavax.balanceOf(address(pairWavax)), protocolFeeX, "testFuzz_SwapInFeesAmounts::5"); - assertEq(usdc.balanceOf(address(pairWavax)), protocolFeeY, "testFuzz_SwapInFeesAmounts::6"); + assertEq(wnative.balanceOf(address(pairWnative)), protocolFeeX, "testFuzz_SwapInFeesAmounts::5"); + assertEq(usdc.balanceOf(address(pairWnative)), protocolFeeY, "testFuzz_SwapInFeesAmounts::6"); - uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceX = wnative.balanceOf(DEV); uint256 balanceY = usdc.balanceOf(DEV); assertEq(balanceX, amountX + amountXIn - amountXOut - protocolFeeX, "testFuzz_SwapInFeesAmounts::7"); @@ -199,36 +199,36 @@ contract LBPairFeesTest is TestHelper { } function testFuzz_SwapOutXAndY(uint128 amountXIn, uint128 amountYIn) external { - (uint128 amountXInLeft, uint128 amountYOut,) = pairWavax.getSwapOut(amountXIn, true); + (uint128 amountXInLeft, uint128 amountYOut,) = pairWnative.getSwapOut(amountXIn, true); vm.assume(amountXInLeft == 0 && amountYOut > 0); - deal(address(wavax), BOB, 1e36); + deal(address(wnative), BOB, 1e36); deal(address(usdc), BOB, 1e36); vm.prank(BOB); - wavax.transfer(address(pairWavax), amountXIn); - pairWavax.swap(true, ALICE); + wnative.transfer(address(pairWnative), amountXIn); + pairWnative.swap(true, ALICE); assertEq(usdc.balanceOf(ALICE), amountYOut, "testFuzz_SwapInFeesAmounts::1"); - (uint128 amountYInLeft, uint128 amountXOut,) = pairWavax.getSwapOut(amountYIn, false); + (uint128 amountYInLeft, uint128 amountXOut,) = pairWnative.getSwapOut(amountYIn, false); vm.assume(amountYInLeft == 0 && amountXOut > 0); vm.prank(BOB); - usdc.transfer(address(pairWavax), amountYIn); - pairWavax.swap(false, ALICE); + usdc.transfer(address(pairWnative), amountYIn); + pairWnative.swap(false, ALICE); - uint256 realAmountXOut = wavax.balanceOf(ALICE); + uint256 realAmountXOut = wnative.balanceOf(ALICE); assertGe(realAmountXOut, amountXOut, "testFuzz_SwapInFeesAmounts::2"); - removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + removeLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 10, 10); - (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWnative.getProtocolFees(); - assertEq(wavax.balanceOf(address(pairWavax)), protocolFeeX, "testFuzz_SwapInFeesAmounts::3"); - assertEq(usdc.balanceOf(address(pairWavax)), protocolFeeY, "testFuzz_SwapInFeesAmounts::4"); + assertEq(wnative.balanceOf(address(pairWnative)), protocolFeeX, "testFuzz_SwapInFeesAmounts::3"); + assertEq(usdc.balanceOf(address(pairWnative)), protocolFeeY, "testFuzz_SwapInFeesAmounts::4"); - uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceX = wnative.balanceOf(DEV); uint256 balanceY = usdc.balanceOf(DEV); assertEq(balanceX, amountX + amountXIn - realAmountXOut - protocolFeeX, "testFuzz_SwapInFeesAmounts::5"); @@ -236,36 +236,36 @@ contract LBPairFeesTest is TestHelper { } function testFuzz_SwapOutYAndX(uint128 amountXIn, uint128 amountYIn) external { - (uint128 amountYInLeft, uint128 amountXOut,) = pairWavax.getSwapOut(amountYIn, false); + (uint128 amountYInLeft, uint128 amountXOut,) = pairWnative.getSwapOut(amountYIn, false); vm.assume(amountYInLeft == 0 && amountXOut > 0); - deal(address(wavax), BOB, 1e36); + deal(address(wnative), BOB, 1e36); deal(address(usdc), BOB, 1e36); vm.prank(BOB); - usdc.transfer(address(pairWavax), amountYIn); - pairWavax.swap(false, ALICE); + usdc.transfer(address(pairWnative), amountYIn); + pairWnative.swap(false, ALICE); - assertEq(wavax.balanceOf(ALICE), amountXOut, "testFuzz_SwapInFeesAmounts::1"); + assertEq(wnative.balanceOf(ALICE), amountXOut, "testFuzz_SwapInFeesAmounts::1"); - (uint128 amountXInLeft, uint128 amountYOut,) = pairWavax.getSwapOut(amountXIn, true); + (uint128 amountXInLeft, uint128 amountYOut,) = pairWnative.getSwapOut(amountXIn, true); vm.assume(amountXInLeft == 0 && amountYOut > 0); vm.prank(BOB); - wavax.transfer(address(pairWavax), amountXIn); - pairWavax.swap(true, ALICE); + wnative.transfer(address(pairWnative), amountXIn); + pairWnative.swap(true, ALICE); uint256 realAmountYOut = usdc.balanceOf(ALICE); assertGe(realAmountYOut, amountYOut, "testFuzz_SwapInFeesAmounts::2"); - removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + removeLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 10, 10); - (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWnative.getProtocolFees(); - assertEq(wavax.balanceOf(address(pairWavax)), protocolFeeX, "testFuzz_SwapInFeesAmounts::3"); - assertEq(usdc.balanceOf(address(pairWavax)), protocolFeeY, "testFuzz_SwapInFeesAmounts::4"); + assertEq(wnative.balanceOf(address(pairWnative)), protocolFeeX, "testFuzz_SwapInFeesAmounts::3"); + assertEq(usdc.balanceOf(address(pairWnative)), protocolFeeY, "testFuzz_SwapInFeesAmounts::4"); - uint256 balanceX = wavax.balanceOf(DEV); + uint256 balanceX = wnative.balanceOf(DEV); uint256 balanceY = usdc.balanceOf(DEV); assertEq(balanceX, amountX + amountXIn - amountXOut - protocolFeeX, "testFuzz_SwapInFeesAmounts::5"); @@ -273,32 +273,32 @@ contract LBPairFeesTest is TestHelper { } function test_FeesX2LP() external { - addLiquidity(ALICE, ALICE, pairWavax, ID_ONE, amountX, amountY, 10, 10); + addLiquidity(ALICE, ALICE, pairWnative, ID_ONE, amountX, amountY, 10, 10); uint128 amountXIn = 1e18; - (, uint128 amountYOut,) = pairWavax.getSwapOut(amountXIn, true); + (, uint128 amountYOut,) = pairWnative.getSwapOut(amountXIn, true); - deal(address(wavax), BOB, amountXIn); + deal(address(wnative), BOB, amountXIn); vm.prank(BOB); - wavax.transfer(address(pairWavax), amountXIn); - pairWavax.swap(true, BOB); + wnative.transfer(address(pairWnative), amountXIn); + pairWnative.swap(true, BOB); - removeLiquidity(ALICE, ALICE, pairWavax, ID_ONE, 1e18, 10, 10); + removeLiquidity(ALICE, ALICE, pairWnative, ID_ONE, 1e18, 10, 10); - (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWnative.getProtocolFees(); assertApproxEqAbs( - wavax.balanceOf(address(ALICE)), amountX + (amountXIn - protocolFeeX) / 2, 2, "test_FeesX2LP::1" + wnative.balanceOf(address(ALICE)), amountX + (amountXIn - protocolFeeX) / 2, 2, "test_FeesX2LP::1" ); assertApproxEqAbs( usdc.balanceOf(address(ALICE)), amountY - (amountYOut + protocolFeeY) / 2, 2, "test_FeesX2LP::2" ); - removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + removeLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 10, 10); assertApproxEqAbs( - wavax.balanceOf(address(DEV)), amountX + (amountXIn - protocolFeeX) / 2, 2, "test_FeesX2LP::3" + wnative.balanceOf(address(DEV)), amountX + (amountXIn - protocolFeeX) / 2, 2, "test_FeesX2LP::3" ); assertApproxEqAbs( usdc.balanceOf(address(DEV)), amountY - (amountYOut + protocolFeeY) / 2, 2, "test_FeesX2LP::4" @@ -306,73 +306,73 @@ contract LBPairFeesTest is TestHelper { } function test_FeesY2LP() external { - addLiquidity(ALICE, ALICE, pairWavax, ID_ONE, amountX, amountY, 10, 10); + addLiquidity(ALICE, ALICE, pairWnative, ID_ONE, amountX, amountY, 10, 10); uint128 amountYIn = 1e18; - (, uint128 amountXOut,) = pairWavax.getSwapOut(amountYIn, false); + (, uint128 amountXOut,) = pairWnative.getSwapOut(amountYIn, false); deal(address(usdc), BOB, amountYIn); vm.prank(BOB); - usdc.transfer(address(pairWavax), amountYIn); - pairWavax.swap(false, BOB); + usdc.transfer(address(pairWnative), amountYIn); + pairWnative.swap(false, BOB); - removeLiquidity(ALICE, ALICE, pairWavax, ID_ONE, 1e18, 10, 10); + removeLiquidity(ALICE, ALICE, pairWnative, ID_ONE, 1e18, 10, 10); - (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWnative.getProtocolFees(); assertApproxEqAbs( - wavax.balanceOf(address(ALICE)), amountX - (amountXOut + protocolFeeX) / 2, 2, "test_FeesY2LP::1" + wnative.balanceOf(address(ALICE)), amountX - (amountXOut + protocolFeeX) / 2, 2, "test_FeesY2LP::1" ); assertApproxEqAbs( usdc.balanceOf(address(ALICE)), amountY + (amountYIn - protocolFeeY) / 2, 2, "test_FeesY2LP::2" ); - removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); + removeLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 10, 10); assertApproxEqAbs( - wavax.balanceOf(address(DEV)), amountX - (amountXOut + protocolFeeX) / 2, 2, "test_FeesY2LP::3" + wnative.balanceOf(address(DEV)), amountX - (amountXOut + protocolFeeX) / 2, 2, "test_FeesY2LP::3" ); assertApproxEqAbs(usdc.balanceOf(address(DEV)), amountY + (amountYIn - protocolFeeY) / 2, 2, "test_FeesY2LP::4"); } function test_Fees2LPFlashloan() external { - removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 10, 10); - addLiquidity(DEV, DEV, pairWavax, ID_ONE, amountX, amountY, 1, 1); + removeLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 10, 10); + addLiquidity(DEV, DEV, pairWnative, ID_ONE, amountX, amountY, 1, 1); - FlashBorrower borrower = new FlashBorrower(pairWavax); + FlashBorrower borrower = new FlashBorrower(pairWnative); - deal(address(wavax), address(borrower), 2e18); + deal(address(wnative), address(borrower), 2e18); deal(address(usdc), address(borrower), 2e18); (uint128 feeX1, uint128 feeY1) = (1e18 + 1, 1e18); vm.prank(address(borrower)); - pairWavax.flashLoan(borrower, bytes32(uint256(1)), abi.encode(feeX1, feeY1, Constants.CALLBACK_SUCCESS, 0)); + pairWnative.flashLoan(borrower, bytes32(uint256(1)), abi.encode(feeX1, feeY1, Constants.CALLBACK_SUCCESS, 0)); - (uint128 protocolFeeX1, uint128 protocolFeeY1) = pairWavax.getProtocolFees(); + (uint128 protocolFeeX1, uint128 protocolFeeY1) = pairWnative.getProtocolFees(); (feeX1, feeY1) = (feeX1 - protocolFeeX1, feeY1 - protocolFeeY1); - addLiquidity(ALICE, ALICE, pairWavax, ID_ONE, amountX, amountY, 1, 1); + addLiquidity(ALICE, ALICE, pairWnative, ID_ONE, amountX, amountY, 1, 1); (uint128 feeX2, uint128 feeY2) = (1e18 + 1, 1e18); vm.prank(address(borrower)); - pairWavax.flashLoan(borrower, bytes32(uint256(1)), abi.encode(feeX2, feeY2, Constants.CALLBACK_SUCCESS, 0)); + pairWnative.flashLoan(borrower, bytes32(uint256(1)), abi.encode(feeX2, feeY2, Constants.CALLBACK_SUCCESS, 0)); { - (uint128 protocolFeeX2, uint128 protocolFeeY2) = pairWavax.getProtocolFees(); + (uint128 protocolFeeX2, uint128 protocolFeeY2) = pairWnative.getProtocolFees(); (feeX2, feeY2) = (feeX2 - (protocolFeeX2 - protocolFeeX1), feeY2 - (protocolFeeY2 - protocolFeeY1)); } (uint256 shareAlice, uint256 shareDev) = - (pairWavax.balanceOf(address(ALICE), ID_ONE), pairWavax.balanceOf(address(DEV), ID_ONE)); + (pairWnative.balanceOf(address(ALICE), ID_ONE), pairWnative.balanceOf(address(DEV), ID_ONE)); - removeLiquidity(ALICE, ALICE, pairWavax, ID_ONE, 1e18, 1, 1); - removeLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 1, 1); + removeLiquidity(ALICE, ALICE, pairWnative, ID_ONE, 1e18, 1, 1); + removeLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 1, 1); assertApproxEqAbs( - wavax.balanceOf(address(ALICE)), + wnative.balanceOf(address(ALICE)), amountX + feeX2 * shareAlice / (shareAlice + shareDev), 1, "test_Fees2LPFlashloan::1" @@ -385,7 +385,7 @@ contract LBPairFeesTest is TestHelper { ); assertApproxEqAbs( - wavax.balanceOf(address(DEV)), + wnative.balanceOf(address(DEV)), amountX + feeX1 + feeX2 * shareDev / (shareAlice + shareDev), 1, "test_Fees2LPFlashloan::3" @@ -399,111 +399,111 @@ contract LBPairFeesTest is TestHelper { } function test_CollectProtocolFeesXTokens() external { - FlashBorrower borrower = new FlashBorrower(pairWavax); + FlashBorrower borrower = new FlashBorrower(pairWnative); - deal(address(wavax), address(borrower), 1e36); + deal(address(wnative), address(borrower), 1e36); deal(address(usdc), address(borrower), 1e36); vm.prank(address(borrower)); - pairWavax.flashLoan(borrower, uint128(1).encode(0), abi.encode(1e18 + 1, 0, Constants.CALLBACK_SUCCESS, 0)); + pairWnative.flashLoan(borrower, uint128(1).encode(0), abi.encode(1e18 + 1, 0, Constants.CALLBACK_SUCCESS, 0)); - (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWnative.getProtocolFees(); address feeRecipient = factory.getFeeRecipient(); vm.prank(feeRecipient); - pairWavax.collectProtocolFees(); + pairWnative.collectProtocolFees(); - assertEq(wavax.balanceOf(feeRecipient), protocolFeeX - 1, "test_CollectProtocolFees::1"); + assertEq(wnative.balanceOf(feeRecipient), protocolFeeX - 1, "test_CollectProtocolFees::1"); assertEq(usdc.balanceOf(feeRecipient), 0, "test_CollectProtocolFees::2"); - (protocolFeeX, protocolFeeY) = pairWavax.getProtocolFees(); + (protocolFeeX, protocolFeeY) = pairWnative.getProtocolFees(); assertEq(protocolFeeX, 1, "test_CollectProtocolFees::3"); assertEq(protocolFeeY, 0, "test_CollectProtocolFees::4"); } function test_CollectProtocolFeesYTokens() external { - FlashBorrower borrower = new FlashBorrower(pairWavax); + FlashBorrower borrower = new FlashBorrower(pairWnative); - deal(address(wavax), address(borrower), 1e36); + deal(address(wnative), address(borrower), 1e36); deal(address(usdc), address(borrower), 1e36); vm.prank(address(borrower)); - pairWavax.flashLoan(borrower, uint128(0).encode(1), abi.encode(0, 1e18 + 1, Constants.CALLBACK_SUCCESS, 0)); + pairWnative.flashLoan(borrower, uint128(0).encode(1), abi.encode(0, 1e18 + 1, Constants.CALLBACK_SUCCESS, 0)); - (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWnative.getProtocolFees(); address feeRecipient = factory.getFeeRecipient(); vm.prank(feeRecipient); - pairWavax.collectProtocolFees(); + pairWnative.collectProtocolFees(); - assertEq(wavax.balanceOf(feeRecipient), 0, "test_CollectProtocolFees::1"); + assertEq(wnative.balanceOf(feeRecipient), 0, "test_CollectProtocolFees::1"); assertEq(usdc.balanceOf(feeRecipient), protocolFeeY - 1, "test_CollectProtocolFees::2"); - (protocolFeeX, protocolFeeY) = pairWavax.getProtocolFees(); + (protocolFeeX, protocolFeeY) = pairWnative.getProtocolFees(); assertEq(protocolFeeX, 0, "test_CollectProtocolFees::3"); assertEq(protocolFeeY, 1, "test_CollectProtocolFees::4"); } function test_CollectProtocolFeesBothTokens() external { - FlashBorrower borrower = new FlashBorrower(pairWavax); + FlashBorrower borrower = new FlashBorrower(pairWnative); - deal(address(wavax), address(borrower), 1e36); + deal(address(wnative), address(borrower), 1e36); deal(address(usdc), address(borrower), 1e36); vm.prank(address(borrower)); - pairWavax.flashLoan( + pairWnative.flashLoan( borrower, uint128(1).encode(1), abi.encode(1e18 + 1, 1e18 + 1, Constants.CALLBACK_SUCCESS, 0) ); - (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWnative.getProtocolFees(); address feeRecipient = factory.getFeeRecipient(); vm.prank(feeRecipient); - pairWavax.collectProtocolFees(); + pairWnative.collectProtocolFees(); - assertEq(wavax.balanceOf(feeRecipient), protocolFeeX - 1, "test_CollectProtocolFees::1"); + assertEq(wnative.balanceOf(feeRecipient), protocolFeeX - 1, "test_CollectProtocolFees::1"); assertEq(usdc.balanceOf(feeRecipient), protocolFeeY - 1, "test_CollectProtocolFees::2"); - (protocolFeeX, protocolFeeY) = pairWavax.getProtocolFees(); + (protocolFeeX, protocolFeeY) = pairWnative.getProtocolFees(); assertEq(protocolFeeX, 1, "test_CollectProtocolFees::3"); assertEq(protocolFeeY, 1, "test_CollectProtocolFees::4"); } function test_CollectProtocolFeesAfterSwap() external { - deal(address(wavax), address(BOB), 1e18); + deal(address(wnative), address(BOB), 1e18); vm.prank(BOB); - wavax.transfer(address(pairWavax), 1e18); - pairWavax.swap(true, BOB); + wnative.transfer(address(pairWnative), 1e18); + pairWnative.swap(true, BOB); - (uint128 protocolFeeX, uint128 protocolFeeY) = pairWavax.getProtocolFees(); + (uint128 protocolFeeX, uint128 protocolFeeY) = pairWnative.getProtocolFees(); uint128 previousProtocolFeeX = protocolFeeX; assertGt(protocolFeeX, 0, "test_CollectProtocolFees::1"); assertEq(protocolFeeY, 0, "test_CollectProtocolFees::2"); - (uint128 reserveX, uint128 reserveY) = pairWavax.getReserves(); + (uint128 reserveX, uint128 reserveY) = pairWnative.getReserves(); address feeRecipient = factory.getFeeRecipient(); vm.prank(feeRecipient); - pairWavax.collectProtocolFees(); + pairWnative.collectProtocolFees(); - (uint128 reserveXAfter, uint128 reserveYAfter) = pairWavax.getReserves(); + (uint128 reserveXAfter, uint128 reserveYAfter) = pairWnative.getReserves(); assertEq(reserveXAfter, reserveX, "test_CollectProtocolFees::3"); assertEq(reserveYAfter, reserveY, "test_CollectProtocolFees::4"); - assertEq(wavax.balanceOf(feeRecipient), protocolFeeX - 1, "test_CollectProtocolFees::5"); + assertEq(wnative.balanceOf(feeRecipient), protocolFeeX - 1, "test_CollectProtocolFees::5"); assertEq(usdc.balanceOf(feeRecipient), 0, "test_CollectProtocolFees::6"); - (protocolFeeX, protocolFeeY) = pairWavax.getProtocolFees(); + (protocolFeeX, protocolFeeY) = pairWnative.getProtocolFees(); assertEq(protocolFeeX, 1, "test_CollectProtocolFees::7"); assertEq(protocolFeeY, 0, "test_CollectProtocolFees::8"); @@ -511,37 +511,37 @@ contract LBPairFeesTest is TestHelper { deal(address(usdc), address(BOB), 1e18); vm.prank(BOB); - usdc.transfer(address(pairWavax), 1e18); - pairWavax.swap(false, BOB); + usdc.transfer(address(pairWnative), 1e18); + pairWnative.swap(false, BOB); - (protocolFeeX, protocolFeeY) = pairWavax.getProtocolFees(); + (protocolFeeX, protocolFeeY) = pairWnative.getProtocolFees(); uint128 previousProtocolFeeY = protocolFeeY; assertEq(protocolFeeX, 1, "test_CollectProtocolFees::9"); assertGt(protocolFeeY, 0, "test_CollectProtocolFees::10"); - (reserveX, reserveY) = pairWavax.getReserves(); + (reserveX, reserveY) = pairWnative.getReserves(); vm.prank(feeRecipient); - pairWavax.collectProtocolFees(); + pairWnative.collectProtocolFees(); - (reserveXAfter, reserveYAfter) = pairWavax.getReserves(); + (reserveXAfter, reserveYAfter) = pairWnative.getReserves(); assertEq(reserveXAfter, reserveX, "test_CollectProtocolFees::11"); assertEq(reserveYAfter, reserveY, "test_CollectProtocolFees::12"); - assertEq(wavax.balanceOf(feeRecipient), previousProtocolFeeX - 1, "test_CollectProtocolFees::13"); + assertEq(wnative.balanceOf(feeRecipient), previousProtocolFeeX - 1, "test_CollectProtocolFees::13"); assertEq(usdc.balanceOf(feeRecipient), protocolFeeY - 1, "test_CollectProtocolFees::14"); - (protocolFeeX, protocolFeeY) = pairWavax.getProtocolFees(); + (protocolFeeX, protocolFeeY) = pairWnative.getProtocolFees(); assertEq(protocolFeeX, 1, "test_CollectProtocolFees::15"); assertEq(protocolFeeY, 1, "test_CollectProtocolFees::16"); vm.prank(feeRecipient); - pairWavax.collectProtocolFees(); + pairWnative.collectProtocolFees(); - assertEq(wavax.balanceOf(feeRecipient), previousProtocolFeeX - 1, "test_CollectProtocolFees::19"); + assertEq(wnative.balanceOf(feeRecipient), previousProtocolFeeX - 1, "test_CollectProtocolFees::19"); assertEq(usdc.balanceOf(feeRecipient), previousProtocolFeeY - 1, "test_CollectProtocolFees::20"); } @@ -558,12 +558,12 @@ contract LBPairFeesTest is TestHelper { vm.assume(baseFee + varFee > 1e17); - bytes memory data = abi.encodePacked(wavax, usdc, binStep); + bytes memory data = abi.encodePacked(wnative, usdc, binStep); - pairWavax = LBPair(ImmutableClone.cloneDeterministic(address(pairImplementation), data, keccak256(data))); + pairWnative = LBPair(ImmutableClone.cloneDeterministic(address(pairImplementation), data, keccak256(data))); vm.expectRevert(ILBPair.LBPair__MaxTotalFeeExceeded.selector); vm.prank(address(factory)); - pairWavax.setStaticFeeParameters(baseFactor, 1, 1, 1, variableFeeControl, 1, maxVolatilityAccumulator); + pairWnative.setStaticFeeParameters(baseFactor, 1, 1, 1, variableFeeControl, 1, maxVolatilityAccumulator); } } diff --git a/test/LBPairFlashloan.t.sol b/test/LBPairFlashloan.t.sol index 89011dd9..2f646f93 100644 --- a/test/LBPairFlashloan.t.sol +++ b/test/LBPairFlashloan.t.sol @@ -16,14 +16,14 @@ contract LBPairFlashloanTest is TestHelper { function setUp() public override { super.setUp(); - pairWavax = createLBPair(wavax, usdc); + pairWnative = createLBPair(wnative, usdc); - addLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 1e18, 50, 50); + addLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 1e18, 50, 50); - borrower = new FlashBorrower(pairWavax); + borrower = new FlashBorrower(pairWnative); // Make sure the borrower can pay back the flash loan - deal(address(wavax), address(borrower), 1e18); + deal(address(wnative), address(borrower), 1e18); deal(address(usdc), address(borrower), 1e18); } @@ -33,21 +33,21 @@ contract LBPairFlashloanTest is TestHelper { bytes32 amountsBorrowed = amountX.encode(amountY); bytes memory data = abi.encode(type(uint128).max, type(uint128).max, Constants.CALLBACK_SUCCESS, 0); - uint256 balanceX = wavax.balanceOf(address(pairWavax)); - uint256 balanceY = usdc.balanceOf(address(pairWavax)); + uint256 balanceX = wnative.balanceOf(address(pairWnative)); + uint256 balanceY = usdc.balanceOf(address(pairWnative)); uint256 flashLoanFee = factory.getFlashLoanFee(); uint256 feeX = (amountX * flashLoanFee + 1e18 - 1) / 1e18; uint256 feeY = (amountY * flashLoanFee + 1e18 - 1) / 1e18; - pairWavax.flashLoan(borrower, amountsBorrowed, data); + pairWnative.flashLoan(borrower, amountsBorrowed, data); - assertEq(wavax.balanceOf(address(pairWavax)), balanceX + feeX, "TestFuzz_Flashloan::1"); - assertEq(usdc.balanceOf(address(pairWavax)), balanceY + feeY, "TestFuzz_Flashloan::2"); + assertEq(wnative.balanceOf(address(pairWnative)), balanceX + feeX, "TestFuzz_Flashloan::1"); + assertEq(usdc.balanceOf(address(pairWnative)), balanceY + feeY, "TestFuzz_Flashloan::2"); - (uint256 reserveX, uint256 reserveY) = pairWavax.getReserves(); - (uint256 protocolFeeX, uint256 protocolFeeY) = pairWavax.getProtocolFees(); + (uint256 reserveX, uint256 reserveY) = pairWnative.getReserves(); + (uint256 protocolFeeX, uint256 protocolFeeY) = pairWnative.getProtocolFees(); assertEq(reserveX + protocolFeeX, balanceX + feeX, "TestFuzz_Flashloan::3"); assertEq(reserveY + protocolFeeY, balanceY + feeY, "TestFuzz_Flashloan::4"); @@ -65,17 +65,17 @@ contract LBPairFlashloanTest is TestHelper { bytes memory data = abi.encode(amountX + feeX - 1, amountY + feeY, Constants.CALLBACK_SUCCESS, 0); vm.expectRevert(ILBPair.LBPair__FlashLoanInsufficientAmount.selector); - pairWavax.flashLoan(borrower, amountsBorrowed, data); + pairWnative.flashLoan(borrower, amountsBorrowed, data); data = abi.encode(amountX + feeX, amountY + feeY - 1, Constants.CALLBACK_SUCCESS, 0); vm.expectRevert(ILBPair.LBPair__FlashLoanInsufficientAmount.selector); - pairWavax.flashLoan(borrower, amountsBorrowed, data); + pairWnative.flashLoan(borrower, amountsBorrowed, data); data = abi.encode(amountX + feeX - 1, amountY + feeY - 1, Constants.CALLBACK_SUCCESS, 0); vm.expectRevert(ILBPair.LBPair__FlashLoanInsufficientAmount.selector); - pairWavax.flashLoan(borrower, amountsBorrowed, data); + pairWnative.flashLoan(borrower, amountsBorrowed, data); } function testFuzz_revert_FlashLoanCallbackFailed(bytes32 callback) external { @@ -85,7 +85,7 @@ contract LBPairFlashloanTest is TestHelper { bytes memory data = abi.encode(0, 0, callback, 0); vm.expectRevert(ILBPair.LBPair__FlashLoanCallbackFailed.selector); - pairWavax.flashLoan(borrower, amountsBorrowed, data); + pairWnative.flashLoan(borrower, amountsBorrowed, data); } function testFuzz_revert_FlashLoanReentrant(bytes32 callback) external { @@ -95,11 +95,11 @@ contract LBPairFlashloanTest is TestHelper { bytes memory data = abi.encode(0, 0, callback, 1); vm.expectRevert(ReentrancyGuard.ReentrancyGuard__ReentrantCall.selector); - pairWavax.flashLoan(borrower, amountsBorrowed, data); + pairWnative.flashLoan(borrower, amountsBorrowed, data); } function test_revert_FlashLoan0Amounts() external { vm.expectRevert(ILBPair.LBPair__ZeroBorrowAmount.selector); - pairWavax.flashLoan(borrower, 0, ""); + pairWnative.flashLoan(borrower, 0, ""); } } diff --git a/test/LBPairInitialState.t.sol b/test/LBPairInitialState.t.sol index b8e8707f..3fc59f47 100644 --- a/test/LBPairInitialState.t.sol +++ b/test/LBPairInitialState.t.sol @@ -8,53 +8,53 @@ contract LBPairInitialStateTest is TestHelper { function setUp() public override { super.setUp(); - pairWavax = createLBPair(wavax, usdc); + pairWnative = createLBPair(wnative, usdc); } function test_GetFactory() external { - assertEq(address(pairWavax.getFactory()), address(factory), "test_GetFactory::1"); + assertEq(address(pairWnative.getFactory()), address(factory), "test_GetFactory::1"); } function test_GetTokenX() external { - assertEq(address(pairWavax.getTokenX()), address(wavax), "test_GetTokenX::1"); + assertEq(address(pairWnative.getTokenX()), address(wnative), "test_GetTokenX::1"); } function test_GetTokenY() external { - assertEq(address(pairWavax.getTokenY()), address(usdc), "test_GetTokenY::1"); + assertEq(address(pairWnative.getTokenY()), address(usdc), "test_GetTokenY::1"); } function test_GetBinStep() external { - assertEq(pairWavax.getBinStep(), DEFAULT_BIN_STEP, "test_GetBinStep::1"); + assertEq(pairWnative.getBinStep(), DEFAULT_BIN_STEP, "test_GetBinStep::1"); } function test_GetReserves() external { - (uint128 reserveX, uint128 reserveY) = pairWavax.getReserves(); + (uint128 reserveX, uint128 reserveY) = pairWnative.getReserves(); assertEq(reserveX, 0, "test_GetReserves::1"); assertEq(reserveY, 0, "test_GetReserves::2"); } function test_GetActiveId() external { - assertEq(pairWavax.getActiveId(), ID_ONE, "test_GetActiveId::1"); + assertEq(pairWnative.getActiveId(), ID_ONE, "test_GetActiveId::1"); } function testFuzz_GetBin(uint24 id) external { - (uint128 reserveX, uint128 reserveY) = pairWavax.getBin(id); + (uint128 reserveX, uint128 reserveY) = pairWnative.getBin(id); assertEq(reserveX, 0, "test_GetBin::1"); assertEq(reserveY, 0, "test_GetBin::2"); } function test_GetNextNonEmptyBin() external { - assertEq(pairWavax.getNextNonEmptyBin(false, 0), 0, "test_GetNextNonEmptyBin::1"); - assertEq(pairWavax.getNextNonEmptyBin(true, 0), type(uint24).max, "test_GetNextNonEmptyBin::2"); + assertEq(pairWnative.getNextNonEmptyBin(false, 0), 0, "test_GetNextNonEmptyBin::1"); + assertEq(pairWnative.getNextNonEmptyBin(true, 0), type(uint24).max, "test_GetNextNonEmptyBin::2"); - assertEq(pairWavax.getNextNonEmptyBin(false, type(uint24).max), 0, "test_GetNextNonEmptyBin::3"); - assertEq(pairWavax.getNextNonEmptyBin(true, type(uint24).max), type(uint24).max, "test_GetNextNonEmptyBin::4"); + assertEq(pairWnative.getNextNonEmptyBin(false, type(uint24).max), 0, "test_GetNextNonEmptyBin::3"); + assertEq(pairWnative.getNextNonEmptyBin(true, type(uint24).max), type(uint24).max, "test_GetNextNonEmptyBin::4"); } function test_GetProtocolFees() external { - (uint128 protocolFeesX, uint128 protocolFeesY) = pairWavax.getProtocolFees(); + (uint128 protocolFeesX, uint128 protocolFeesY) = pairWnative.getProtocolFees(); assertEq(protocolFeesX, 0, "test_GetProtocolFees::1"); assertEq(protocolFeesY, 0, "test_GetProtocolFees::2"); @@ -69,7 +69,7 @@ contract LBPairInitialStateTest is TestHelper { uint24 variableFeeControl, uint16 protocolShare, uint24 maxVolatilityAccumulator - ) = pairWavax.getStaticFeeParameters(); + ) = pairWnative.getStaticFeeParameters(); assertEq(baseFactor, DEFAULT_BASE_FACTOR, "test_GetParameters::1"); assertEq(filterPeriod, DEFAULT_FILTER_PERIOD, "test_GetParameters::2"); @@ -82,7 +82,7 @@ contract LBPairInitialStateTest is TestHelper { function test_GetVariableFeeParameters() external { (uint24 volatilityAccumulator, uint24 volatilityReference, uint24 idReference, uint40 timeOfLastUpdate) = - pairWavax.getVariableFeeParameters(); + pairWnative.getVariableFeeParameters(); assertEq(volatilityAccumulator, 0, "test_GetParameters::1"); assertEq(volatilityReference, 0, "test_GetParameters::2"); @@ -92,7 +92,7 @@ contract LBPairInitialStateTest is TestHelper { function test_GetOracleParameters() external { (uint8 sampleLifetime, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp) = - pairWavax.getOracleParameters(); + pairWnative.getOracleParameters(); assertEq(sampleLifetime, OracleHelper._MAX_SAMPLE_LIFETIME, "test_GetParameters::1"); assertEq(size, 0, "test_GetParameters::2"); @@ -102,7 +102,8 @@ contract LBPairInitialStateTest is TestHelper { } function test_GetOracleSampleAt() external { - (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = pairWavax.getOracleSampleAt(1); + (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = + pairWnative.getOracleSampleAt(1); assertEq(cumulativeId, 0, "test_GetParameters::1"); assertEq(cumulativeVolatility, 0, "test_GetParameters::2"); @@ -113,49 +114,49 @@ contract LBPairInitialStateTest is TestHelper { uint256 delta = uint256(DEFAULT_BIN_STEP) * 5e13; assertApproxEqRel( - pairWavax.getPriceFromId(1_000 + 2 ** 23), + pairWnative.getPriceFromId(1_000 + 2 ** 23), 924521306405372907020063908180274956666, delta, "test_GetPriceFromId::1" ); assertApproxEqRel( - pairWavax.getPriceFromId(2 ** 23 - 1_000), + pairWnative.getPriceFromId(2 ** 23 - 1_000), 125245452360126660303600960578690115355, delta, "test_GetPriceFromId::2" ); assertApproxEqRel( - pairWavax.getPriceFromId(2 ** 23 + 10_000), + pairWnative.getPriceFromId(2 ** 23 + 10_000), 7457860201113570250644758522304565438757805, delta, "test_GetPriceFromId::3" ); assertApproxEqRel( - pairWavax.getPriceFromId(2 ** 23 - 10_000), + pairWnative.getPriceFromId(2 ** 23 - 10_000), 15526181252368702469753297095319515, delta, "test_GetPriceFromId::4" ); // avoid overflow of assertApproxEqRel with a too high price assertLe( - pairWavax.getPriceFromId(2 ** 23 + 80_000), + pairWnative.getPriceFromId(2 ** 23 + 80_000), 18133092123953330812316154041959812232388892985347108730495479426840526848, "test_GetPriceFromId::5" ); assertGe( - pairWavax.getPriceFromId(2 ** 23 + 80_000), + pairWnative.getPriceFromId(2 ** 23 + 80_000), 18096880266539986845478224721407196147811144510344442837666495029900738560, "test_GetPriceFromId::6" ); - assertApproxEqRel(pairWavax.getPriceFromId(2 ** 23 - 80_000), 6392, 1e8, "test_GetPriceFromId::7"); + assertApproxEqRel(pairWnative.getPriceFromId(2 ** 23 - 80_000), 6392, 1e8, "test_GetPriceFromId::7"); assertApproxEqRel( - pairWavax.getPriceFromId(2 ** 23 + 12_345), + pairWnative.getPriceFromId(2 ** 23 + 12_345), 77718771515321296819382407317364352468140333, delta, "test_GetPriceFromId::8" ); assertApproxEqRel( - pairWavax.getPriceFromId(2 ** 23 - 12_345), + pairWnative.getPriceFromId(2 ** 23 - 12_345), 1489885737765286392982993705955521, delta, "test_GetPriceFromId::9" @@ -164,46 +165,52 @@ contract LBPairInitialStateTest is TestHelper { function test_GetIdFromPrice() external { assertApproxEqAbs( - pairWavax.getIdFromPrice(924521306405372907020063908180274956666), + pairWnative.getIdFromPrice(924521306405372907020063908180274956666), 1_000 + 2 ** 23, 1, "test_GetPriceFromId::1" ); assertApproxEqAbs( - pairWavax.getIdFromPrice(125245452360126660303600960578690115355), + pairWnative.getIdFromPrice(125245452360126660303600960578690115355), 2 ** 23 - 1_000, 1, "test_GetPriceFromId::2" ); assertApproxEqAbs( - pairWavax.getIdFromPrice(7457860201113570250644758522304565438757805), + pairWnative.getIdFromPrice(7457860201113570250644758522304565438757805), 2 ** 23 + 10_000, 1, "test_GetPriceFromId::3" ); assertApproxEqAbs( - pairWavax.getIdFromPrice(15526181252368702469753297095319515), 2 ** 23 - 10_000, 1, "test_GetPriceFromId::4" + pairWnative.getIdFromPrice(15526181252368702469753297095319515), + 2 ** 23 - 10_000, + 1, + "test_GetPriceFromId::4" ); assertApproxEqAbs( - pairWavax.getIdFromPrice(18114977146806524168130684952726477124021312024291123319263609183005067158), + pairWnative.getIdFromPrice(18114977146806524168130684952726477124021312024291123319263609183005067158), 2 ** 23 + 80_000, 1, "test_GetPriceFromId::5" ); - assertApproxEqAbs(pairWavax.getIdFromPrice(6392), 2 ** 23 - 80_000, 1, "test_GetPriceFromId::6"); + assertApproxEqAbs(pairWnative.getIdFromPrice(6392), 2 ** 23 - 80_000, 1, "test_GetPriceFromId::6"); assertApproxEqAbs( - pairWavax.getIdFromPrice(77718771515321296819382407317364352468140333), + pairWnative.getIdFromPrice(77718771515321296819382407317364352468140333), 2 ** 23 + 12_345, 1, "test_GetPriceFromId::7" ); assertApproxEqAbs( - pairWavax.getIdFromPrice(1489885737765286392982993705955521), 2 ** 23 - 12_345, 1, "test_GetPriceFromId::8" + pairWnative.getIdFromPrice(1489885737765286392982993705955521), + 2 ** 23 - 12_345, + 1, + "test_GetPriceFromId::8" ); } function testFuzz_GetSwapOut(uint128 amountOut, bool swapForY) external { - (uint128 amountIn, uint128 amountOutLeft, uint128 fee) = pairWavax.getSwapIn(amountOut, swapForY); + (uint128 amountIn, uint128 amountOutLeft, uint128 fee) = pairWnative.getSwapIn(amountOut, swapForY); assertEq(amountIn, 0, "testFuzz_GetSwapInOut::1"); assertEq(amountOutLeft, amountOut, "testFuzz_GetSwapInOut::2"); @@ -211,7 +218,7 @@ contract LBPairInitialStateTest is TestHelper { } function testFuzz_GetSwapIn(uint128 amountIn, bool swapForY) external { - (uint128 amountInLeft, uint128 amountOut, uint128 fee) = pairWavax.getSwapOut(amountIn, swapForY); + (uint128 amountInLeft, uint128 amountOut, uint128 fee) = pairWnative.getSwapOut(amountIn, swapForY); assertEq(amountInLeft, amountIn, "testFuzz_GetSwapInOut::1"); assertEq(amountOut, 0, "testFuzz_GetSwapInOut::2"); @@ -221,6 +228,6 @@ contract LBPairInitialStateTest is TestHelper { function test_revert_SetStaticFeeParameters() external { vm.expectRevert(ILBPair.LBPair__InvalidStaticFeeParameters.selector); vm.prank(address(factory)); - pairWavax.setStaticFeeParameters(0, 0, 0, 0, 0, 0, 0); + pairWnative.setStaticFeeParameters(0, 0, 0, 0, 0, 0, 0); } } diff --git a/test/LBPairLiquidity.t.sol b/test/LBPairLiquidity.t.sol index ecde8a74..f8c15d52 100644 --- a/test/LBPairLiquidity.t.sol +++ b/test/LBPairLiquidity.t.sol @@ -9,12 +9,12 @@ contract LBPairLiquidityTest is TestHelper { uint256 constant PRECISION = 1e18; - uint24 immutable activeId = ID_ONE - 24647; // id where 1 AVAX = 20 USDC + uint24 immutable activeId = ID_ONE - 24647; // id where 1 NATIVE = 20 USDC function setUp() public override { super.setUp(); - pairWavax = createLBPairFromStartId(wavax, usdc, activeId); + pairWnative = createLBPairFromStartId(wnative, usdc, activeId); } function test_SimpleMint() external { @@ -23,16 +23,18 @@ contract LBPairLiquidityTest is TestHelper { uint8 nbBinX = 6; uint8 nbBinY = 6; - addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, nbBinX, nbBinY); + addLiquidity(ALICE, BOB, pairWnative, activeId, amountX, amountY, nbBinX, nbBinY); - assertEq(wavax.balanceOf(ALICE), amountX - amountX * (PRECISION / nbBinX) / 1e18 * nbBinX, "test_SimpleMint::1"); + assertEq( + wnative.balanceOf(ALICE), amountX - amountX * (PRECISION / nbBinX) / 1e18 * nbBinX, "test_SimpleMint::1" + ); assertEq(usdc.balanceOf(ALICE), amountY - amountY * (PRECISION / nbBinY) / 1e18 * nbBinY, "test_SimpleMint::2"); uint256 total = getTotalBins(nbBinX, nbBinY); for (uint256 i; i < total; ++i) { uint24 id = getId(activeId, i, nbBinY); - (uint128 binReserveX, uint128 binReserveY) = pairWavax.getBin(id); + (uint128 binReserveX, uint128 binReserveY) = pairWnative.getBin(id); if (id < activeId) { assertEq(binReserveX, 0, "test_SimpleMint::3"); @@ -45,8 +47,8 @@ contract LBPairLiquidityTest is TestHelper { assertEq(binReserveY, 0, "test_SimpleMint::8"); } - assertGt(pairWavax.balanceOf(BOB, id), 0, "test_SimpleMint::9"); - assertEq(pairWavax.balanceOf(ALICE, id), 0, "test_SimpleMint::10"); + assertGt(pairWnative.balanceOf(BOB, id), 0, "test_SimpleMint::9"); + assertEq(pairWnative.balanceOf(ALICE, id), 0, "test_SimpleMint::10"); } } @@ -56,7 +58,7 @@ contract LBPairLiquidityTest is TestHelper { uint8 nbBinX = 6; uint8 nbBinY = 6; - addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, nbBinX, nbBinY); + addLiquidity(ALICE, BOB, pairWnative, activeId, amountX, amountY, nbBinX, nbBinY); uint256 total = getTotalBins(nbBinX, nbBinY); uint256[] memory balances = new uint256[](total); @@ -64,15 +66,15 @@ contract LBPairLiquidityTest is TestHelper { for (uint256 i; i < total; ++i) { uint24 id = getId(activeId, i, nbBinY); - balances[i] = pairWavax.balanceOf(BOB, id); + balances[i] = pairWnative.balanceOf(BOB, id); } - addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, nbBinX, nbBinY); + addLiquidity(ALICE, BOB, pairWnative, activeId, amountX, amountY, nbBinX, nbBinY); for (uint256 i; i < total; ++i) { uint24 id = getId(activeId, i, nbBinY); - (uint128 binReserveX, uint128 binReserveY) = pairWavax.getBin(id); + (uint128 binReserveX, uint128 binReserveY) = pairWnative.getBin(id); if (id < activeId) { assertEq(binReserveX, 0, "test_SimpleMint::1"); @@ -85,7 +87,7 @@ contract LBPairLiquidityTest is TestHelper { assertEq(binReserveY, 0, "test_SimpleMint::6"); } - assertEq(pairWavax.balanceOf(BOB, id), 2 * balances[i], "test_DoubleMint::7"); + assertEq(pairWnative.balanceOf(BOB, id), 2 * balances[i], "test_DoubleMint::7"); } } @@ -95,7 +97,7 @@ contract LBPairLiquidityTest is TestHelper { uint8 nbBinX = 6; uint8 nbBinY = 6; - addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, nbBinX, nbBinY); + addLiquidity(ALICE, BOB, pairWnative, activeId, amountX, amountY, nbBinX, nbBinY); uint256 total = getTotalBins(nbBinX, nbBinY); uint256[] memory balances = new uint256[](total); @@ -103,19 +105,21 @@ contract LBPairLiquidityTest is TestHelper { for (uint256 i; i < total; ++i) { uint24 id = getId(activeId, i, nbBinY); - balances[i] = pairWavax.balanceOf(BOB, id); + balances[i] = pairWnative.balanceOf(BOB, id); } - addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, nbBinX, 0); - addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, 0, nbBinY); + addLiquidity(ALICE, BOB, pairWnative, activeId, amountX, amountY, nbBinX, 0); + addLiquidity(ALICE, BOB, pairWnative, activeId, amountX, amountY, 0, nbBinY); for (uint256 i; i < total; ++i) { uint24 id = getId(activeId, i, nbBinY); if (id == activeId) { - assertApproxEqRel(pairWavax.balanceOf(BOB, id), 2 * balances[i], 1e15, "test_MintWithDifferentBins::1"); // composition fee + assertApproxEqRel( + pairWnative.balanceOf(BOB, id), 2 * balances[i], 1e15, "test_MintWithDifferentBins::1" + ); // composition fee } else { - assertEq(pairWavax.balanceOf(BOB, id), 2 * balances[i], "test_MintWithDifferentBins::2"); + assertEq(pairWnative.balanceOf(BOB, id), 2 * balances[i], "test_MintWithDifferentBins::2"); } } } @@ -126,7 +130,7 @@ contract LBPairLiquidityTest is TestHelper { uint8 nbBinX = 6; uint8 nbBinY = 6; - addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, nbBinX, nbBinY); + addLiquidity(ALICE, BOB, pairWnative, activeId, amountX, amountY, nbBinX, nbBinY); uint256 total = getTotalBins(nbBinX, nbBinY); @@ -137,17 +141,17 @@ contract LBPairLiquidityTest is TestHelper { uint24 id = getId(activeId, i, nbBinY); ids[i] = id; - balances[i] = pairWavax.balanceOf(BOB, id); + balances[i] = pairWnative.balanceOf(BOB, id); } - (uint128 reserveX, uint128 reserveY) = pairWavax.getReserves(); + (uint128 reserveX, uint128 reserveY) = pairWnative.getReserves(); vm.prank(BOB); - pairWavax.burn(BOB, BOB, ids, balances); + pairWnative.burn(BOB, BOB, ids, balances); - assertEq(wavax.balanceOf(BOB), reserveX, "test_SimpleBurn::1"); + assertEq(wnative.balanceOf(BOB), reserveX, "test_SimpleBurn::1"); assertEq(usdc.balanceOf(BOB), reserveY, "test_SimpleBurn::2"); - (reserveX, reserveY) = pairWavax.getReserves(); + (reserveX, reserveY) = pairWnative.getReserves(); assertEq(reserveX, 0, "test_BurnPartial::3"); assertEq(reserveY, 0, "test_BurnPartial::4"); @@ -159,7 +163,7 @@ contract LBPairLiquidityTest is TestHelper { uint8 nbBinX = 6; uint8 nbBinY = 6; - addLiquidity(ALICE, BOB, pairWavax, activeId, amountX, amountY, nbBinX, nbBinY); + addLiquidity(ALICE, BOB, pairWnative, activeId, amountX, amountY, nbBinX, nbBinY); uint256 total = getTotalBins(nbBinX, nbBinY); @@ -171,27 +175,27 @@ contract LBPairLiquidityTest is TestHelper { uint24 id = getId(activeId, i, nbBinY); ids[i] = id; - uint256 balance = pairWavax.balanceOf(BOB, id); + uint256 balance = pairWnative.balanceOf(BOB, id); halfbalances[i] = balance / 2; balances[i] = balance - balance / 2; } - (uint128 reserveX, uint128 reserveY) = pairWavax.getReserves(); + (uint128 reserveX, uint128 reserveY) = pairWnative.getReserves(); vm.prank(BOB); - pairWavax.burn(BOB, BOB, ids, halfbalances); + pairWnative.burn(BOB, BOB, ids, halfbalances); - assertApproxEqRel(wavax.balanceOf(BOB), reserveX / 2, 1e10, "test_BurnPartial::1"); + assertApproxEqRel(wnative.balanceOf(BOB), reserveX / 2, 1e10, "test_BurnPartial::1"); assertApproxEqRel(usdc.balanceOf(BOB), reserveY / 2, 1e10, "test_BurnPartial::2"); vm.prank(BOB); - pairWavax.burn(BOB, BOB, ids, balances); + pairWnative.burn(BOB, BOB, ids, balances); - assertEq(wavax.balanceOf(BOB), reserveX, "test_BurnPartial::3"); + assertEq(wnative.balanceOf(BOB), reserveX, "test_BurnPartial::3"); assertEq(usdc.balanceOf(BOB), reserveY, "test_BurnPartial::4"); - (reserveX, reserveY) = pairWavax.getReserves(); + (reserveX, reserveY) = pairWnative.getReserves(); assertEq(reserveX, 0, "test_BurnPartial::5"); assertEq(reserveY, 0, "test_BurnPartial::6"); @@ -201,24 +205,24 @@ contract LBPairLiquidityTest is TestHelper { uint8 nbBinX = 6; uint8 nbBinY = 6; - addLiquidity(ALICE, BOB, pairWavax, activeId, 100 * 10 ** 18, 2_000 * 10 ** 6, nbBinX, nbBinY); + addLiquidity(ALICE, BOB, pairWnative, activeId, 100 * 10 ** 18, 2_000 * 10 ** 6, nbBinX, nbBinY); uint24 lowId = activeId - nbBinY + 1; uint24 upperId = activeId + nbBinX - 1; - uint24 id = pairWavax.getNextNonEmptyBin(false, 0); + uint24 id = pairWnative.getNextNonEmptyBin(false, 0); assertEq(id, lowId, "test_GetNextNonEmptyBin::1"); uint256 total = getTotalBins(nbBinX, nbBinY); for (uint256 i; i < total - 1; ++i) { - id = pairWavax.getNextNonEmptyBin(false, id); + id = pairWnative.getNextNonEmptyBin(false, id); assertEq(id, lowId + i + 1, "test_GetNextNonEmptyBin::2"); } - id = pairWavax.getNextNonEmptyBin(true, type(uint24).max); + id = pairWnative.getNextNonEmptyBin(true, type(uint24).max); assertEq(id, upperId, "test_GetNextNonEmptyBin::3"); @@ -226,16 +230,16 @@ contract LBPairLiquidityTest is TestHelper { uint256[] memory balances = new uint256[](1); ids[0] = activeId; - balances[0] = pairWavax.balanceOf(BOB, activeId); + balances[0] = pairWnative.balanceOf(BOB, activeId); vm.prank(BOB); - pairWavax.burn(BOB, BOB, ids, balances); + pairWnative.burn(BOB, BOB, ids, balances); - id = pairWavax.getNextNonEmptyBin(false, activeId - 1); + id = pairWnative.getNextNonEmptyBin(false, activeId - 1); assertEq(id, activeId + 1, "test_GetNextNonEmptyBin::4"); - id = pairWavax.getNextNonEmptyBin(true, activeId + 1); + id = pairWnative.getNextNonEmptyBin(true, activeId + 1); assertEq(id, activeId - 1, "test_GetNextNonEmptyBin::5"); } @@ -243,30 +247,30 @@ contract LBPairLiquidityTest is TestHelper { function test_revert_MintEmptyConfig() external { bytes32[] memory data = new bytes32[](0); vm.expectRevert(ILBPair.LBPair__EmptyMarketConfigs.selector); - pairWavax.mint(BOB, data, BOB); + pairWnative.mint(BOB, data, BOB); } function test_revert_MintZeroShares() external { bytes32[] memory data = new bytes32[](1); data[0] = LiquidityConfigurations.encodeParams(1e18, 1e18, activeId); vm.expectRevert(abi.encodeWithSelector(ILBPair.LBPair__ZeroShares.selector, activeId)); - pairWavax.mint(BOB, data, BOB); + pairWnative.mint(BOB, data, BOB); } function test_revert_MintMoreThanAmountSent() external { - deal(address(wavax), address(pairWavax), 1e18); - deal(address(usdc), address(pairWavax), 1e18); + deal(address(wnative), address(pairWnative), 1e18); + deal(address(usdc), address(pairWnative), 1e18); bytes32[] memory data = new bytes32[](2); data[0] = LiquidityConfigurations.encodeParams(0, 0.5e18, activeId - 1); data[1] = LiquidityConfigurations.encodeParams(0, 0.5e18 + 1, activeId); vm.expectRevert(PackedUint128Math.PackedUint128Math__SubUnderflow.selector); - pairWavax.mint(BOB, data, BOB); + pairWnative.mint(BOB, data, BOB); data[1] = LiquidityConfigurations.encodeParams(0.5e18, 0, activeId); data[0] = LiquidityConfigurations.encodeParams(0.5e18 + 1, 0, activeId + 1); vm.expectRevert(PackedUint128Math.PackedUint128Math__SubUnderflow.selector); - pairWavax.mint(BOB, data, BOB); + pairWnative.mint(BOB, data, BOB); } function test_revert_BurnEmptyArraysOrDifferent() external { @@ -274,41 +278,41 @@ contract LBPairLiquidityTest is TestHelper { uint256[] memory balances = new uint256[](1); vm.expectRevert(ILBPair.LBPair__InvalidInput.selector); - pairWavax.burn(DEV, DEV, ids, balances); + pairWnative.burn(DEV, DEV, ids, balances); ids = new uint256[](1); balances = new uint256[](0); vm.expectRevert(ILBPair.LBPair__InvalidInput.selector); - pairWavax.burn(DEV, DEV, ids, balances); + pairWnative.burn(DEV, DEV, ids, balances); ids = new uint256[](0); balances = new uint256[](0); vm.expectRevert(ILBPair.LBPair__InvalidInput.selector); - pairWavax.burn(DEV, DEV, ids, balances); + pairWnative.burn(DEV, DEV, ids, balances); ids = new uint256[](1); balances = new uint256[](2); vm.expectRevert(ILBPair.LBPair__InvalidInput.selector); - pairWavax.burn(DEV, DEV, ids, balances); + pairWnative.burn(DEV, DEV, ids, balances); } function test_revert_BurnMoreThanBalance() external { - addLiquidity(ALICE, ALICE, pairWavax, activeId, 1e18, 1e18, 1, 0); - addLiquidity(DEV, DEV, pairWavax, activeId, 1e18, 1e18, 1, 0); + addLiquidity(ALICE, ALICE, pairWnative, activeId, 1e18, 1e18, 1, 0); + addLiquidity(DEV, DEV, pairWnative, activeId, 1e18, 1e18, 1, 0); uint256[] memory ids = new uint256[](1); uint256[] memory balances = new uint256[](1); ids[0] = activeId; - balances[0] = pairWavax.balanceOf(DEV, activeId) + 1; + balances[0] = pairWnative.balanceOf(DEV, activeId) + 1; vm.expectRevert( abi.encodeWithSelector(ILBToken.LBToken__BurnExceedsBalance.selector, DEV, activeId, balances[0]) ); - pairWavax.burn(DEV, DEV, ids, balances); + pairWnative.burn(DEV, DEV, ids, balances); } function test_revert_BurnZeroShares() external { @@ -319,7 +323,7 @@ contract LBPairLiquidityTest is TestHelper { balances[0] = 0; vm.expectRevert(abi.encodeWithSelector(ILBPair.LBPair__ZeroAmount.selector, activeId)); - pairWavax.burn(DEV, DEV, ids, balances); + pairWnative.burn(DEV, DEV, ids, balances); } function test_revert_BurnForZeroAmounts() external { @@ -330,6 +334,6 @@ contract LBPairLiquidityTest is TestHelper { balances[0] = 1; vm.expectRevert(abi.encodeWithSelector(ILBPair.LBPair__ZeroAmountsOut.selector, activeId)); - pairWavax.burn(DEV, DEV, ids, balances); + pairWnative.burn(DEV, DEV, ids, balances); } } diff --git a/test/LBPairOracle.t.sol b/test/LBPairOracle.t.sol index 5af68c59..dab0ea60 100644 --- a/test/LBPairOracle.t.sol +++ b/test/LBPairOracle.t.sol @@ -10,24 +10,25 @@ contract LBPairOracleTest is TestHelper { function setUp() public override { super.setUp(); - pairWavax = createLBPair(wavax, usdc); + pairWnative = createLBPair(wnative, usdc); - addLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 1e18, 10, 10); + addLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 1e18, 10, 10); } function testFuzz_IncreaseOracleLength(uint16 newLength) external { vm.assume(newLength > 0 && newLength < 100); // 100 is arbitrary, but a reasonable upper bound - (, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp) = pairWavax.getOracleParameters(); + (, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp) = + pairWnative.getOracleParameters(); assertEq(size, 0, "TestFuzz_IncreaseOracleLength::1"); assertEq(activeSize, 0, "TestFuzz_IncreaseOracleLength::2"); assertEq(lastUpdated, 0, "TestFuzz_IncreaseOracleLength::3"); assertEq(firstTimestamp, 0, "TestFuzz_IncreaseOracleLength::4"); - pairWavax.increaseOracleLength(newLength); + pairWnative.increaseOracleLength(newLength); - (, size, activeSize, lastUpdated, firstTimestamp) = pairWavax.getOracleParameters(); + (, size, activeSize, lastUpdated, firstTimestamp) = pairWnative.getOracleParameters(); assertEq(size, newLength, "TestFuzz_IncreaseOracleLength::5"); assertEq(activeSize, 0, "TestFuzz_IncreaseOracleLength::6"); @@ -36,14 +37,15 @@ contract LBPairOracleTest is TestHelper { } function test_1SampleAdded() external { - pairWavax.increaseOracleLength(100); + pairWnative.increaseOracleLength(100); - deal(address(wavax), BOB, 1e18); + deal(address(wnative), BOB, 1e18); vm.prank(BOB); - wavax.transfer(address(pairWavax), 1e16); - pairWavax.swap(true, BOB); + wnative.transfer(address(pairWnative), 1e16); + pairWnative.swap(true, BOB); - (, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp) = pairWavax.getOracleParameters(); + (, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp) = + pairWnative.getOracleParameters(); assertEq(size, 100, "Test_1SampleAdded::1"); assertEq(activeSize, 1, "Test_1SampleAdded::2"); @@ -53,10 +55,10 @@ contract LBPairOracleTest is TestHelper { vm.warp(block.timestamp + 1); vm.prank(BOB); - wavax.transfer(address(pairWavax), 1e16); - pairWavax.swap(true, BOB); + wnative.transfer(address(pairWnative), 1e16); + pairWnative.swap(true, BOB); - (, size, activeSize, lastUpdated, firstTimestamp) = pairWavax.getOracleParameters(); + (, size, activeSize, lastUpdated, firstTimestamp) = pairWnative.getOracleParameters(); assertEq(size, 100, "Test_1SampleAdded::5"); assertEq(activeSize, 1, "Test_1SampleAdded::6"); @@ -65,14 +67,15 @@ contract LBPairOracleTest is TestHelper { } function test_CircularOracleWith2Samples() external { - pairWavax.increaseOracleLength(2); + pairWnative.increaseOracleLength(2); - deal(address(wavax), BOB, 1e18); + deal(address(wnative), BOB, 1e18); vm.prank(BOB); - wavax.transfer(address(pairWavax), 1e16); - pairWavax.swap(true, BOB); + wnative.transfer(address(pairWnative), 1e16); + pairWnative.swap(true, BOB); - (, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp) = pairWavax.getOracleParameters(); + (, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp) = + pairWnative.getOracleParameters(); assertEq(size, 2, "Test_CircularOracle::1"); assertEq(activeSize, 1, "Test_CircularOracle::2"); @@ -82,10 +85,10 @@ contract LBPairOracleTest is TestHelper { vm.warp(block.timestamp + 121); vm.prank(BOB); - wavax.transfer(address(pairWavax), 1e16); - pairWavax.swap(true, BOB); + wnative.transfer(address(pairWnative), 1e16); + pairWnative.swap(true, BOB); - (, size, activeSize, lastUpdated, firstTimestamp) = pairWavax.getOracleParameters(); + (, size, activeSize, lastUpdated, firstTimestamp) = pairWnative.getOracleParameters(); assertEq(size, 2, "Test_CircularOracle::5"); assertEq(activeSize, 2, "Test_CircularOracle::6"); @@ -95,10 +98,10 @@ contract LBPairOracleTest is TestHelper { vm.warp(block.timestamp + 1000); vm.prank(BOB); - wavax.transfer(address(pairWavax), 1e16); - pairWavax.swap(true, BOB); + wnative.transfer(address(pairWnative), 1e16); + pairWnative.swap(true, BOB); - (, size, activeSize, lastUpdated, firstTimestamp) = pairWavax.getOracleParameters(); + (, size, activeSize, lastUpdated, firstTimestamp) = pairWnative.getOracleParameters(); assertEq(size, 2, "Test_CircularOracle::9"); assertEq(activeSize, 2, "Test_CircularOracle::10"); @@ -107,7 +110,7 @@ contract LBPairOracleTest is TestHelper { vm.warp(block.timestamp + 100); - (, size, activeSize, lastUpdated, firstTimestamp) = pairWavax.getOracleParameters(); + (, size, activeSize, lastUpdated, firstTimestamp) = pairWnative.getOracleParameters(); assertEq(size, 2, "Test_CircularOracle::13"); assertEq(activeSize, 2, "Test_CircularOracle::14"); @@ -116,19 +119,19 @@ contract LBPairOracleTest is TestHelper { } function test_CircularOracleGetSampleAt() external { - pairWavax.increaseOracleLength(2); + pairWnative.increaseOracleLength(2); - deal(address(wavax), BOB, 1e18); + deal(address(wnative), BOB, 1e18); vm.prank(BOB); - wavax.transfer(address(pairWavax), 1e16); - pairWavax.swap(true, BOB); + wnative.transfer(address(pairWnative), 1e16); + pairWnative.swap(true, BOB); uint256 dt = block.timestamp; (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = - pairWavax.getOracleSampleAt(uint40(block.timestamp)); + pairWnative.getOracleSampleAt(uint40(block.timestamp)); - uint24 activeId = pairWavax.getActiveId(); + uint24 activeId = pairWnative.getActiveId(); assertEq(cumulativeId, activeId * dt, "Test_CircularOracleGetSampleAt::1"); assertEq(cumulativeVolatility, 0, "Test_CircularOracleGetSampleAt::2"); @@ -138,17 +141,17 @@ contract LBPairOracleTest is TestHelper { vm.warp(block.timestamp + 121); vm.prank(BOB); - wavax.transfer(address(pairWavax), 1e16); - pairWavax.swap(true, BOB); + wnative.transfer(address(pairWnative), 1e16); + pairWnative.swap(true, BOB); dt = block.timestamp - dt; (uint64 previousCumulativeId, uint64 previousCumulativeVolatility, uint64 previousCumulativeBinCrossed) = (cumulativeId, cumulativeVolatility, cumulativeBinCrossed); (cumulativeId, cumulativeVolatility, cumulativeBinCrossed) = - pairWavax.getOracleSampleAt(uint40(block.timestamp)); + pairWnative.getOracleSampleAt(uint40(block.timestamp)); - activeId = pairWavax.getActiveId(); + activeId = pairWnative.getActiveId(); assertEq(cumulativeId, previousCumulativeId + activeId * dt, "Test_CircularOracleGetSampleAt::4"); assertEq(cumulativeVolatility, 0, "Test_CircularOracleGetSampleAt::5"); @@ -159,8 +162,8 @@ contract LBPairOracleTest is TestHelper { deal(address(usdc), BOB, 1e18); vm.prank(BOB); - usdc.transfer(address(pairWavax), 1e18); - pairWavax.swap(false, BOB); + usdc.transfer(address(pairWnative), 1e18); + pairWnative.swap(false, BOB); dt = block.timestamp - dt; @@ -168,47 +171,49 @@ contract LBPairOracleTest is TestHelper { (cumulativeId, cumulativeVolatility, cumulativeBinCrossed); (cumulativeId, cumulativeVolatility, cumulativeBinCrossed) = - pairWavax.getOracleSampleAt(uint40(block.timestamp)); + pairWnative.getOracleSampleAt(uint40(block.timestamp)); - (uint24 volatilityAccumulator,,,) = pairWavax.getVariableFeeParameters(); + (uint24 volatilityAccumulator,,,) = pairWnative.getVariableFeeParameters(); - assertEq(cumulativeId, previousCumulativeId + pairWavax.getActiveId() * dt, "Test_CircularOracleGetSampleAt::7"); + assertEq( + cumulativeId, previousCumulativeId + pairWnative.getActiveId() * dt, "Test_CircularOracleGetSampleAt::7" + ); assertEq(cumulativeVolatility, volatilityAccumulator * dt, "Test_CircularOracleGetSampleAt::8"); - assertEq(cumulativeBinCrossed, (pairWavax.getActiveId() - activeId) * dt, "Test_CircularOracleGetSampleAt::9"); + assertEq(cumulativeBinCrossed, (pairWnative.getActiveId() - activeId) * dt, "Test_CircularOracleGetSampleAt::9"); } function test_MaxLengthOracle() external { - deal(address(wavax), BOB, 1e36); + deal(address(wnative), BOB, 1e36); deal(address(usdc), BOB, 1e36); - pairWavax.increaseOracleLength(65535); + pairWnative.increaseOracleLength(65535); vm.warp(1_000); vm.startPrank(BOB); for (uint256 i = 0; i < 65535; i++) { - wavax.transfer(address(pairWavax), 1e10); - pairWavax.swap(true, BOB); + wnative.transfer(address(pairWnative), 1e10); + pairWnative.swap(true, BOB); vm.warp(block.timestamp + 121); } vm.stopPrank(); (, uint256 size, uint256 activeSize, uint256 lastUpdated, uint256 firstTimestamp) = - pairWavax.getOracleParameters(); + pairWnative.getOracleParameters(); assertEq(size, 65535, "Test_MaxLengthOracle::1"); assertEq(activeSize, 65535, "Test_MaxLengthOracle::2"); assertEq(lastUpdated, block.timestamp - 121, "Test_MaxLengthOracle::3"); assertEq(firstTimestamp, block.timestamp - 65535 * 121, "Test_MaxLengthOracle::4"); - uint24 activeId = pairWavax.getActiveId(); + uint24 activeId = pairWnative.getActiveId(); { (uint64 cumulativeId1, uint64 cumulativeVolatility1, uint64 cumulativeBinCrossed1) = - pairWavax.getOracleSampleAt(uint40(block.timestamp)); + pairWnative.getOracleSampleAt(uint40(block.timestamp)); (uint64 cumulativeId2, uint64 cumulativeVolatility2, uint64 cumulativeBinCrossed2) = - pairWavax.getOracleSampleAt(uint40(block.timestamp - 121)); + pairWnative.getOracleSampleAt(uint40(block.timestamp - 121)); assertEq(cumulativeId1, cumulativeId2 + uint64(activeId) * 121, "Test_MaxLengthOracle::5"); @@ -220,7 +225,7 @@ contract LBPairOracleTest is TestHelper { assertEq((cumulativeId1 - cumulativeId2) / 121, activeId, "Test_MaxLengthOracle::10"); assertEq(cumulativeBinCrossed1, 0, "Test_MaxLengthOracle::11"); - (cumulativeId2,,) = pairWavax.getOracleSampleAt(uint40(block.timestamp / 2)); + (cumulativeId2,,) = pairWnative.getOracleSampleAt(uint40(block.timestamp / 2)); assertEq( uint256(cumulativeId1) * 1e18 / block.timestamp, uint256(cumulativeId2) * 1e18 / (block.timestamp / 2), @@ -232,19 +237,19 @@ contract LBPairOracleTest is TestHelper { vm.warp(block.timestamp + 1000 - 121); vm.prank(BOB); - usdc.transfer(address(pairWavax), 1e18); - pairWavax.swap(false, BOB); + usdc.transfer(address(pairWnative), 1e18); + pairWnative.swap(false, BOB); - uint64 newActiveId = pairWavax.getActiveId(); + uint64 newActiveId = pairWnative.getActiveId(); (uint64 cumulativeIdNow, uint64 cumulativeVolatilityNow, uint64 cumulativeBinCrossedNow) = - pairWavax.getOracleSampleAt(uint40(block.timestamp)); + pairWnative.getOracleSampleAt(uint40(block.timestamp)); (uint64 cumulativeIdPastHour, uint64 cumulativeVolatilityPastHour, uint64 cumulativeBinCrossedPastHour) = - pairWavax.getOracleSampleAt(uint40(block.timestamp - 3600)); + pairWnative.getOracleSampleAt(uint40(block.timestamp - 3600)); (uint64 cumulativeIdPastDay, uint64 cumulativeVolatilityPastDay, uint64 cumulativeBinCrossedPastDay) = - pairWavax.getOracleSampleAt(uint40(block.timestamp - 86400)); + pairWnative.getOracleSampleAt(uint40(block.timestamp - 86400)); assertEq(cumulativeVolatilityPastDay, 0, "Test_MaxLengthOracle::13"); assertEq(cumulativeBinCrossedPastDay, 0, "Test_MaxLengthOracle::14"); @@ -265,7 +270,7 @@ contract LBPairOracleTest is TestHelper { function test_GetOracleParametersEmptyOracle() external { (, uint256 size, uint256 activeSize, uint256 lastUpdated, uint256 firstTimestamp) = - pairWavax.getOracleParameters(); + pairWnative.getOracleParameters(); assertEq(size, 0, "Test_GetOracleParametersEmptyOracle::1"); assertEq(activeSize, 0, "Test_GetOracleParametersEmptyOracle::2"); diff --git a/test/LBPairSwap.t.sol b/test/LBPairSwap.t.sol index 018fea63..9ea487d7 100644 --- a/test/LBPairSwap.t.sol +++ b/test/LBPairSwap.t.sol @@ -10,71 +10,71 @@ contract LBPairSwapTest is TestHelper { function setUp() public override { super.setUp(); - pairWavax = createLBPair(wavax, usdc); + pairWnative = createLBPair(wnative, usdc); - addLiquidity(DEV, DEV, pairWavax, ID_ONE, 1e18, 1e18, 50, 50); + addLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 1e18, 50, 50); } function testFuzz_SwapInForY(uint128 amountOut) public { vm.assume(amountOut > 0 && amountOut < 1e18); - (uint128 amountIn, uint128 amountOutLeft,) = pairWavax.getSwapIn(amountOut, true); + (uint128 amountIn, uint128 amountOutLeft,) = pairWnative.getSwapIn(amountOut, true); assertEq(amountOutLeft, 0, "TestFuzz_SwapInForY::1"); - deal(address(wavax), ALICE, amountIn); + deal(address(wnative), ALICE, amountIn); vm.startPrank(ALICE); - wavax.transfer(address(pairWavax), amountIn); - pairWavax.swap(true, ALICE); + wnative.transfer(address(pairWnative), amountIn); + pairWnative.swap(true, ALICE); vm.stopPrank(); - assertEq(wavax.balanceOf(ALICE), 0, "TestFuzz_SwapInForY::2"); + assertEq(wnative.balanceOf(ALICE), 0, "TestFuzz_SwapInForY::2"); assertEq(usdc.balanceOf(ALICE), amountOut, "TestFuzz_SwapInForY::3"); } function testFuzz_SwapInForX(uint128 amountOut) public { vm.assume(amountOut > 0 && amountOut < 1e18); - (uint128 amountIn, uint128 amountOutLeft,) = pairWavax.getSwapIn(amountOut, false); + (uint128 amountIn, uint128 amountOutLeft,) = pairWnative.getSwapIn(amountOut, false); assertEq(amountOutLeft, 0, "TestFuzz_SwapInForX::1"); deal(address(usdc), ALICE, amountIn); vm.startPrank(ALICE); - usdc.transfer(address(pairWavax), amountIn); - pairWavax.swap(false, ALICE); + usdc.transfer(address(pairWnative), amountIn); + pairWnative.swap(false, ALICE); vm.stopPrank(); assertEq(usdc.balanceOf(ALICE), 0, "TestFuzz_SwapInForX::2"); - assertEq(wavax.balanceOf(ALICE), amountOut, "TestFuzz_SwapInForX::3"); + assertEq(wnative.balanceOf(ALICE), amountOut, "TestFuzz_SwapInForX::3"); } function testFuzz_SwapOutForY(uint128 amountIn) public { vm.assume(amountIn > 0 && amountIn <= 1e18); - (uint128 amountInLeft, uint128 amountOut,) = pairWavax.getSwapOut(amountIn, true); + (uint128 amountInLeft, uint128 amountOut,) = pairWnative.getSwapOut(amountIn, true); vm.assume(amountOut > 0); assertEq(amountInLeft, 0, "TestFuzz_SwapOutForY::1"); - deal(address(wavax), ALICE, amountIn); + deal(address(wnative), ALICE, amountIn); vm.startPrank(ALICE); - wavax.transfer(address(pairWavax), amountIn); - pairWavax.swap(true, ALICE); + wnative.transfer(address(pairWnative), amountIn); + pairWnative.swap(true, ALICE); vm.stopPrank(); - assertEq(wavax.balanceOf(ALICE), 0, "TestFuzz_SwapOutForY::2"); + assertEq(wnative.balanceOf(ALICE), 0, "TestFuzz_SwapOutForY::2"); assertEq(usdc.balanceOf(ALICE), amountOut, "TestFuzz_SwapOutForY::3"); } function testFuzz_SwapOutForX(uint128 amountIn) public { vm.assume(amountIn > 0 && amountIn <= 1e18); - (uint128 amountInLeft, uint128 amountOut,) = pairWavax.getSwapOut(amountIn, false); + (uint128 amountInLeft, uint128 amountOut,) = pairWnative.getSwapOut(amountIn, false); vm.assume(amountOut > 0); @@ -83,53 +83,53 @@ contract LBPairSwapTest is TestHelper { deal(address(usdc), ALICE, amountIn); vm.startPrank(ALICE); - usdc.transfer(address(pairWavax), amountIn); - pairWavax.swap(false, ALICE); + usdc.transfer(address(pairWnative), amountIn); + pairWnative.swap(false, ALICE); vm.stopPrank(); assertEq(usdc.balanceOf(ALICE), 0, "TestFuzz_SwapOutForX::2"); - assertEq(wavax.balanceOf(ALICE), amountOut, "TestFuzz_SwapOutForX::3"); + assertEq(wnative.balanceOf(ALICE), amountOut, "TestFuzz_SwapOutForX::3"); } function test_revert_SwapInsufficientAmountIn() external { vm.expectRevert(ILBPair.LBPair__InsufficientAmountIn.selector); - pairWavax.swap(true, ALICE); + pairWnative.swap(true, ALICE); vm.expectRevert(ILBPair.LBPair__InsufficientAmountIn.selector); - pairWavax.swap(false, ALICE); + pairWnative.swap(false, ALICE); } function test_revert_SwapInsufficientAmountOut() external { - deal(address(wavax), ALICE, 1); + deal(address(wnative), ALICE, 1); deal(address(usdc), ALICE, 1); vm.prank(ALICE); - wavax.transfer(address(pairWavax), 1); + wnative.transfer(address(pairWnative), 1); vm.expectRevert(ILBPair.LBPair__InsufficientAmountOut.selector); - pairWavax.swap(true, ALICE); + pairWnative.swap(true, ALICE); vm.prank(ALICE); - usdc.transfer(address(pairWavax), 1); + usdc.transfer(address(pairWnative), 1); vm.expectRevert(ILBPair.LBPair__InsufficientAmountOut.selector); - pairWavax.swap(false, ALICE); + pairWnative.swap(false, ALICE); } function test_revert_SwapOutOfLiquidity() external { - deal(address(wavax), ALICE, 2e18); + deal(address(wnative), ALICE, 2e18); deal(address(usdc), ALICE, 2e18); vm.prank(ALICE); - wavax.transfer(address(pairWavax), 2e18); + wnative.transfer(address(pairWnative), 2e18); vm.expectRevert(ILBPair.LBPair__OutOfLiquidity.selector); - pairWavax.swap(true, ALICE); + pairWnative.swap(true, ALICE); vm.prank(ALICE); - usdc.transfer(address(pairWavax), 2e18); + usdc.transfer(address(pairWnative), 2e18); vm.expectRevert(ILBPair.LBPair__OutOfLiquidity.selector); - pairWavax.swap(false, ALICE); + pairWnative.swap(false, ALICE); } } diff --git a/test/LBRouter.Liquidity.t.sol b/test/LBRouter.Liquidity.t.sol index 0c8f778a..402ef85a 100644 --- a/test/LBRouter.Liquidity.t.sol +++ b/test/LBRouter.Liquidity.t.sol @@ -9,9 +9,9 @@ import "test/helpers/TestHelper.sol"; * 1. Receive * 2. Create LBPair * 3. Add Liquidity - * 4. Add liquidity AVAX + * 4. Add liquidity NATIVE * 5. Remove liquidity - * 6. Remove liquidity AVAX + * 6. Remove liquidity NATIVE * 7. Sweep ERC20s * 8. Sweep LBToken */ @@ -25,7 +25,7 @@ contract LiquidityBinRouterTest is TestHelper { // Create necessary pairs router.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); - router.createLBPair(wavax, usdc, ID_ONE, DEFAULT_BIN_STEP); + router.createLBPair(wnative, usdc, ID_ONE, DEFAULT_BIN_STEP); router.createLBPair(taxToken, usdc, ID_ONE, DEFAULT_BIN_STEP); uint256 startingBalance = type(uint112).max; @@ -40,20 +40,20 @@ contract LiquidityBinRouterTest is TestHelper { assertEq(address(router.getLegacyFactory()), address(legacyFactoryV2), "test_Constructor::2"); assertEq(address(router.getV1Factory()), address(factoryV1), "test_Constructor::3"); assertEq(address(router.getLegacyRouter()), address(legacyRouterV2), "test_Constructor::4"); - assertEq(address(router.getWAVAX()), address(wavax), "test_Constructor::5"); + assertEq(address(router.getWNATIVE()), address(wnative), "test_Constructor::5"); } - function test_ReceiveAVAX() public { - // Users can't send AVAX to the router - vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__SenderIsNotWAVAX.selector)); + function test_ReceiveNATIVE() public { + // Users can't send NATIVE to the router + vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__SenderIsNotWNATIVE.selector)); (bool success,) = address(router).call{value: 1e18}(""); - // WAVAX can - deal(address(wavax), 1e18); - vm.prank(address(wavax)); + // WNATIVE can + deal(address(wnative), 1e18); + vm.prank(address(wnative)); (success,) = address(router).call{value: 1e18}(""); - assertTrue(success, "test_ReceiveAVAX::1"); + assertTrue(success, "test_ReceiveNATIVE::1"); } function test_CreatePair() public { @@ -234,13 +234,13 @@ contract LiquidityBinRouterTest is TestHelper { router.addLiquidity(liquidityParameters); } - function test_AddLiquidityAVAX() public { + function test_AddLiquidityNATIVE() public { uint256 amountYIn = 1e18; uint24 binNumber = 7; uint24 gap = 2; ILBRouter.LiquidityParameters memory liquidityParameters = - getLiquidityParameters(wavax, usdc, amountYIn, ID_ONE, binNumber, gap); + getLiquidityParameters(wnative, usdc, amountYIn, ID_ONE, binNumber, gap); // Add liquidity ( @@ -250,44 +250,44 @@ contract LiquidityBinRouterTest is TestHelper { uint256 amountYLeft, uint256[] memory depositIds, uint256[] memory liquidityMinted - ) = router.addLiquidityAVAX{value: liquidityParameters.amountX}(liquidityParameters); + ) = router.addLiquidityNATIVE{value: liquidityParameters.amountX}(liquidityParameters); // Check amounts - assertEq(amountXAdded, liquidityParameters.amountX, "test_AddLiquidityAVAX::1"); - assertEq(amountYAdded, liquidityParameters.amountY, "test_AddLiquidityAVAX::2"); - assertLt(amountXLeft, amountXAdded, "test_AddLiquidityAVAX::3"); - assertLt(amountYLeft, amountYAdded, "test_AddLiquidityAVAX::4"); + assertEq(amountXAdded, liquidityParameters.amountX, "test_AddLiquidityNATIVE::1"); + assertEq(amountYAdded, liquidityParameters.amountY, "test_AddLiquidityNATIVE::2"); + assertLt(amountXLeft, amountXAdded, "test_AddLiquidityNATIVE::3"); + assertLt(amountYLeft, amountYAdded, "test_AddLiquidityNATIVE::4"); // Check liquidity minted - assertEq(liquidityMinted.length, binNumber, "test_AddLiquidityAVAX::5"); - assertEq(depositIds.length, binNumber, "test_AddLiquidityAVAX::6"); + assertEq(liquidityMinted.length, binNumber, "test_AddLiquidityNATIVE::5"); + assertEq(depositIds.length, binNumber, "test_AddLiquidityNATIVE::6"); - // Test with AVAX as token Y - router.createLBPair(bnb, wavax, ID_ONE, DEFAULT_BIN_STEP); + // Test with NATIVE as token Y + router.createLBPair(bnb, wnative, ID_ONE, DEFAULT_BIN_STEP); - liquidityParameters = getLiquidityParameters(bnb, wavax, amountYIn, ID_ONE, binNumber, gap); + liquidityParameters = getLiquidityParameters(bnb, wnative, amountYIn, ID_ONE, binNumber, gap); - router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); + router.addLiquidityNATIVE{value: liquidityParameters.amountY}(liquidityParameters); } - function test_revert_AddLiquidityAVAX() public { + function test_revert_AddLiquidityNATIVE() public { uint256 amountYIn = 1e18; uint24 binNumber = 7; uint24 gap = 2; // Revert if tokens are in the wrong order ILBRouter.LiquidityParameters memory liquidityParameters = - getLiquidityParameters(usdc, wavax, amountYIn, ID_ONE, binNumber, gap); + getLiquidityParameters(usdc, wnative, amountYIn, ID_ONE, binNumber, gap); vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__WrongTokenOrder.selector)); - router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); + router.addLiquidityNATIVE{value: liquidityParameters.amountY}(liquidityParameters); - // Revert if for non WAVAX pairs + // Revert if for non WNATIVE pairs liquidityParameters = getLiquidityParameters(usdt, usdc, amountYIn, ID_ONE, binNumber, gap); vm.expectRevert( abi.encodeWithSelector( - ILBRouter.LBRouter__WrongAvaxLiquidityParameters.selector, + ILBRouter.LBRouter__WrongNativeLiquidityParameters.selector, address(liquidityParameters.tokenX), address(liquidityParameters.tokenY), liquidityParameters.amountX, @@ -295,14 +295,14 @@ contract LiquidityBinRouterTest is TestHelper { liquidityParameters.amountY ) ); - router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); + router.addLiquidityNATIVE{value: liquidityParameters.amountY}(liquidityParameters); - // Revert if the amount of AVAX isn't correct - liquidityParameters = getLiquidityParameters(wavax, usdc, amountYIn, ID_ONE, binNumber, gap); + // Revert if the amount of NATIVE isn't correct + liquidityParameters = getLiquidityParameters(wnative, usdc, amountYIn, ID_ONE, binNumber, gap); vm.expectRevert( abi.encodeWithSelector( - ILBRouter.LBRouter__WrongAvaxLiquidityParameters.selector, + ILBRouter.LBRouter__WrongNativeLiquidityParameters.selector, address(liquidityParameters.tokenX), address(liquidityParameters.tokenY), liquidityParameters.amountX, @@ -311,7 +311,7 @@ contract LiquidityBinRouterTest is TestHelper { ) ); // liquidityParameters.amountX should be sent as message value - router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); + router.addLiquidityNATIVE{value: liquidityParameters.amountY}(liquidityParameters); } function test_RemoveLiquidity() public { @@ -428,64 +428,64 @@ contract LiquidityBinRouterTest is TestHelper { ); } - function test_RemoveLiquidityAVAX() public { + function test_RemoveLiquidityNATIVE() public { uint256 amountYIn = 1e18; uint24 binNumber = 7; uint24 gap = 2; ILBRouter.LiquidityParameters memory liquidityParameters = - getLiquidityParameters(wavax, usdc, amountYIn, ID_ONE, binNumber, gap); + getLiquidityParameters(wnative, usdc, amountYIn, ID_ONE, binNumber, gap); // Add liquidity (uint256 amountXAdded, uint256 amountYAdded,,, uint256[] memory depositIds, uint256[] memory liquidityMinted) = - router.addLiquidityAVAX{value: liquidityParameters.amountX}(liquidityParameters); + router.addLiquidityNATIVE{value: liquidityParameters.amountX}(liquidityParameters); - ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP).LBPair; + ILBPair pair = factory.getLBPairInformation(wnative, usdc, DEFAULT_BIN_STEP).LBPair; pair.setApprovalForAll(address(router), true); - uint256 balanceAVAXBefore = address(this).balance; + uint256 balanceNATIVEBefore = address(this).balance; uint256 balanceUSDCBefore = usdc.balanceOf(address(this)); - (uint256 amountToken, uint256 amountAVAX) = router.removeLiquidityAVAX( + (uint256 amountToken, uint256 amountNATIVE) = router.removeLiquidityNATIVE( usdc, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, payable(address(this)), block.timestamp ); - assertApproxEqAbs(amountAVAX, amountXAdded, 10, "test_RemoveLiquidityAVAX::1"); - assertApproxEqAbs(amountToken, amountYAdded, 10, "test_RemoveLiquidityAVAX::2"); + assertApproxEqAbs(amountNATIVE, amountXAdded, 10, "test_RemoveLiquidityNATIVE::1"); + assertApproxEqAbs(amountToken, amountYAdded, 10, "test_RemoveLiquidityNATIVE::2"); - assertEq(address(this).balance, balanceAVAXBefore + amountAVAX, "test_RemoveLiquidityAVAX::3"); - assertEq(usdc.balanceOf(address(this)), balanceUSDCBefore + amountToken, "test_RemoveLiquidityAVAX::4"); + assertEq(address(this).balance, balanceNATIVEBefore + amountNATIVE, "test_RemoveLiquidityNATIVE::3"); + assertEq(usdc.balanceOf(address(this)), balanceUSDCBefore + amountToken, "test_RemoveLiquidityNATIVE::4"); } - function test_revert_RemoveLiquidityAVAX() public { + function test_revert_RemoveLiquidityNATIVE() public { uint256 amountYIn = 1e18; uint24 binNumber = 7; uint24 gap = 2; ILBRouter.LiquidityParameters memory liquidityParameters = - getLiquidityParameters(wavax, usdc, amountYIn, ID_ONE, binNumber, gap); + getLiquidityParameters(wnative, usdc, amountYIn, ID_ONE, binNumber, gap); // Add liquidity (uint256 amountXAdded,,,, uint256[] memory depositIds, uint256[] memory liquidityMinted) = - router.addLiquidityAVAX{value: liquidityParameters.amountX}(liquidityParameters); + router.addLiquidityNATIVE{value: liquidityParameters.amountX}(liquidityParameters); - ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP).LBPair; + ILBPair pair = factory.getLBPairInformation(wnative, usdc, DEFAULT_BIN_STEP).LBPair; pair.setApprovalForAll(address(router), true); // Revert if the deadline is passed vm.expectRevert( abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) ); - router.removeLiquidityAVAX( + router.removeLiquidityNATIVE( usdc, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, payable(address(this)), block.timestamp - 1 ); // Revert if the contract does not have a receive function blockReceive = true; vm.expectRevert( - abi.encodeWithSelector(ILBRouter.LBRouter__FailedToSendAVAX.selector, address(this), amountXAdded - 2) + abi.encodeWithSelector(ILBRouter.LBRouter__FailedToSendNATIVE.selector, address(this), amountXAdded - 2) ); - router.removeLiquidityAVAX( + router.removeLiquidityNATIVE( usdc, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, payable(address(this)), block.timestamp ); } diff --git a/test/LBRouter.Swap.t.sol b/test/LBRouter.Swap.t.sol index 5f2de562..b2b70579 100644 --- a/test/LBRouter.Swap.t.sol +++ b/test/LBRouter.Swap.t.sol @@ -8,14 +8,14 @@ import "test/helpers/TestHelper.sol"; * This file only test single hop swaps using V2.1 pairs * Test scenarios: * 1. swapExactTokensForTokens - * 2. swapExactTokensForAVAX - * 3. swapExactAVAXForTokens + * 2. swapExactTokensForNATIVE + * 3. swapExactNATIVEForTokens * 4. swapTokensForExactTokens - * 5. swapTokensForExactAVAX - * 6. swapAVAXForExactTokens + * 5. swapTokensForExactNATIVE + * 6. swapNATIVEForExactTokens * 7. swapExactTokensForTokensSupportingFeeOnTransferTokens - * 8. swapExactTokensForAVAXSupportingFeeOnTransferTokens - * 9. swapExactAVAXForTokensSupportingFeeOnTransferTokens + * 8. swapExactTokensForNATIVESupportingFeeOnTransferTokens + * 9. swapExactNATIVEForTokensSupportingFeeOnTransferTokens */ contract LiquidityBinRouterSwapTest is TestHelper { function setUp() public override { @@ -25,8 +25,8 @@ contract LiquidityBinRouterSwapTest is TestHelper { // Create necessary pairs router.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); - router.createLBPair(wavax, usdc, ID_ONE, DEFAULT_BIN_STEP); - router.createLBPair(taxToken, wavax, ID_ONE, DEFAULT_BIN_STEP); + router.createLBPair(wnative, usdc, ID_ONE, DEFAULT_BIN_STEP); + router.createLBPair(taxToken, wnative, ID_ONE, DEFAULT_BIN_STEP); uint256 startingBalance = type(uint112).max; deal(address(usdc), address(this), startingBalance); @@ -37,11 +37,11 @@ contract LiquidityBinRouterSwapTest is TestHelper { getLiquidityParameters(usdt, usdc, 100e18, ID_ONE, 15, 0); router.addLiquidity(liquidityParameters); - liquidityParameters = getLiquidityParameters(wavax, usdc, 100e18, ID_ONE, 15, 0); - router.addLiquidityAVAX{value: liquidityParameters.amountX}(liquidityParameters); + liquidityParameters = getLiquidityParameters(wnative, usdc, 100e18, ID_ONE, 15, 0); + router.addLiquidityNATIVE{value: liquidityParameters.amountX}(liquidityParameters); - liquidityParameters = getLiquidityParameters(taxToken, wavax, 200e18, ID_ONE, 15, 0); - router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); + liquidityParameters = getLiquidityParameters(taxToken, wnative, 200e18, ID_ONE, 15, 0); + router.addLiquidityNATIVE{value: liquidityParameters.amountY}(liquidityParameters); } function test_GetIdFromPrice() public view { @@ -98,22 +98,22 @@ contract LiquidityBinRouterSwapTest is TestHelper { router.swapExactTokensForTokens(amountIn, amountOutExpected, path, address(this), block.timestamp + 1); } - function test_SwapExactTokensForAVAX() public { + function test_SwapExactTokensForNATIVE() public { uint128 amountIn = 20e18; - ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP).LBPair; + ILBPair pair = factory.getLBPairInformation(wnative, usdc, DEFAULT_BIN_STEP).LBPair; (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, false); - ILBRouter.Path memory path = _buildPath(usdc, wavax); + ILBRouter.Path memory path = _buildPath(usdc, wnative); uint256 balanceBefore = address(this).balance; - (uint256 amountOut) = router.swapExactTokensForAVAX( + (uint256 amountOut) = router.swapExactTokensForNATIVE( amountIn, amountOutExpected, path, payable(address(this)), block.timestamp + 1 ); - assertEq(amountOut, amountOutExpected, "test_SwapExactTokensForAVAX::1"); - assertEq(address(this).balance, balanceBefore + amountOut, "test_SwapExactTokensForAVAX::2"); + assertEq(amountOut, amountOutExpected, "test_SwapExactTokensForNATIVE::1"); + assertEq(address(this).balance, balanceBefore + amountOut, "test_SwapExactTokensForNATIVE::2"); // Reverts if amountOut is less than amountOutMin (, amountOutExpected,) = router.getSwapOut(pair, amountIn, false); @@ -122,14 +122,14 @@ contract LiquidityBinRouterSwapTest is TestHelper { ILBRouter.LBRouter__InsufficientAmountOut.selector, amountOutExpected + 1, amountOutExpected ) ); - router.swapExactTokensForAVAX( + router.swapExactTokensForNATIVE( amountIn, amountOutExpected + 1, path, payable(address(this)), block.timestamp + 1 ); - // Revert if token out isn't WAVAX + // Revert if token out isn't WNATIVE path.tokenPath[1] = usdt; vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); - router.swapExactTokensForAVAX( + router.swapExactTokensForNATIVE( amountIn, amountOutExpected + 1, path, payable(address(this)), block.timestamp + 1 ); @@ -137,29 +137,30 @@ contract LiquidityBinRouterSwapTest is TestHelper { vm.expectRevert( abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) ); - router.swapExactTokensForAVAX(amountIn, amountOutExpected, path, payable(address(this)), block.timestamp - 1); + router.swapExactTokensForNATIVE(amountIn, amountOutExpected, path, payable(address(this)), block.timestamp - 1); // Revert if the path arrays are not valid path.tokenPath = new IERC20[](0); vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); - router.swapExactTokensForAVAX(amountIn, amountOutExpected, path, payable(address(this)), block.timestamp + 1); + router.swapExactTokensForNATIVE(amountIn, amountOutExpected, path, payable(address(this)), block.timestamp + 1); } - function test_SwapExactAVAXForTokens() public { + function test_SwapExactNATIVEForTokens() public { uint128 amountIn = 20e18; - ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP).LBPair; + ILBPair pair = factory.getLBPairInformation(wnative, usdc, DEFAULT_BIN_STEP).LBPair; (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, true); - ILBRouter.Path memory path = _buildPath(wavax, usdc); + ILBRouter.Path memory path = _buildPath(wnative, usdc); uint256 balanceBefore = usdc.balanceOf(address(this)); - (uint256 amountOut) = - router.swapExactAVAXForTokens{value: amountIn}(amountOutExpected, path, address(this), block.timestamp + 1); + (uint256 amountOut) = router.swapExactNATIVEForTokens{value: amountIn}( + amountOutExpected, path, address(this), block.timestamp + 1 + ); - assertEq(amountOut, amountOutExpected, "test_SwapExactAVAXForTokens::1"); - assertEq(usdc.balanceOf(address(this)), balanceBefore + amountOut, "test_SwapExactAVAXForTokens::2"); + assertEq(amountOut, amountOutExpected, "test_SwapExactNATIVEForTokens::1"); + assertEq(usdc.balanceOf(address(this)), balanceBefore + amountOut, "test_SwapExactNATIVEForTokens::2"); (, amountOutExpected,) = router.getSwapOut(pair, amountIn, true); @@ -169,23 +170,25 @@ contract LiquidityBinRouterSwapTest is TestHelper { ILBRouter.LBRouter__InsufficientAmountOut.selector, amountOutExpected + 1, amountOutExpected ) ); - router.swapExactAVAXForTokens{value: amountIn}(amountOutExpected + 1, path, address(this), block.timestamp + 1); + router.swapExactNATIVEForTokens{value: amountIn}( + amountOutExpected + 1, path, address(this), block.timestamp + 1 + ); - // Revert if token in isn't WAVAX + // Revert if token in isn't WNATIVE path.tokenPath[0] = usdt; vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); - router.swapExactAVAXForTokens{value: amountIn}(amountOutExpected, path, address(this), block.timestamp + 1); + router.swapExactNATIVEForTokens{value: amountIn}(amountOutExpected, path, address(this), block.timestamp + 1); // Revert is dealine passed vm.expectRevert( abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) ); - router.swapExactAVAXForTokens{value: amountIn}(amountOutExpected, path, address(this), block.timestamp - 1); + router.swapExactNATIVEForTokens{value: amountIn}(amountOutExpected, path, address(this), block.timestamp - 1); // Revert if the path arrays are not valid path.tokenPath = new IERC20[](0); vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); - router.swapExactAVAXForTokens{value: amountIn}(amountOutExpected, path, address(this), block.timestamp + 1); + router.swapExactNATIVEForTokens{value: amountIn}(amountOutExpected, path, address(this), block.timestamp + 1); } function test_SwapTokensForExactTokens() public { @@ -226,22 +229,22 @@ contract LiquidityBinRouterSwapTest is TestHelper { router.swapTokensForExactTokens(amountOut, amountInExpected, path, address(this), block.timestamp + 1); } - function test_SwapTokensForExactAVAX() public { + function test_SwapTokensForExactNATIVE() public { uint128 amountOut = 20e18; - ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP).LBPair; + ILBPair pair = factory.getLBPairInformation(wnative, usdc, DEFAULT_BIN_STEP).LBPair; (uint128 amountInExpected,,) = router.getSwapIn(pair, amountOut, false); - ILBRouter.Path memory path = _buildPath(usdc, wavax); + ILBRouter.Path memory path = _buildPath(usdc, wnative); uint256 balanceBefore = usdc.balanceOf(address(this)); - (uint256[] memory amountsIn) = router.swapTokensForExactAVAX( + (uint256[] memory amountsIn) = router.swapTokensForExactNATIVE( amountOut, amountInExpected, path, payable(address(this)), block.timestamp + 1 ); - assertEq(amountsIn[0], amountInExpected, "test_SwapTokensForExactAVAX::1"); - assertEq(usdc.balanceOf(address(this)), balanceBefore - amountsIn[0], "test_SwapTokensForExactAVAX::2"); + assertEq(amountsIn[0], amountInExpected, "test_SwapTokensForExactNATIVE::1"); + assertEq(usdc.balanceOf(address(this)), balanceBefore - amountsIn[0], "test_SwapTokensForExactNATIVE::2"); (amountInExpected,,) = router.getSwapIn(pair, amountOut, false); @@ -253,10 +256,10 @@ contract LiquidityBinRouterSwapTest is TestHelper { ); router.swapTokensForExactTokens(amountOut, amountInExpected - 1, path, address(this), block.timestamp + 1); - // Revert if token out isn't WAVAX + // Revert if token out isn't WNATIVE path.tokenPath[1] = usdt; vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); - router.swapTokensForExactAVAX(amountOut, amountInExpected, path, payable(address(this)), block.timestamp + 1); + router.swapTokensForExactNATIVE(amountOut, amountInExpected, path, payable(address(this)), block.timestamp + 1); // Revert is dealine passed vm.expectRevert( @@ -270,58 +273,60 @@ contract LiquidityBinRouterSwapTest is TestHelper { router.swapTokensForExactTokens(amountOut, amountInExpected, path, address(this), block.timestamp + 1); } - function test_SwapAVAXForExactTokens() public { + function test_SwapNATIVEForExactTokens() public { uint128 amountOut = 20e18; - ILBPair pair = factory.getLBPairInformation(wavax, usdc, DEFAULT_BIN_STEP).LBPair; + ILBPair pair = factory.getLBPairInformation(wnative, usdc, DEFAULT_BIN_STEP).LBPair; (uint128 amountInExpected,,) = router.getSwapIn(pair, amountOut, true); - ILBRouter.Path memory path = _buildPath(wavax, usdc); + ILBRouter.Path memory path = _buildPath(wnative, usdc); uint256 balanceBefore = address(this).balance; - // Sending too much AVAX to test the refund - (uint256[] memory amountsIn) = router.swapAVAXForExactTokens{value: amountInExpected + 100}( + // Sending too much NATIVE to test the refund + (uint256[] memory amountsIn) = router.swapNATIVEForExactTokens{value: amountInExpected + 100}( amountOut, path, address(this), block.timestamp + 1 ); - assertEq(amountsIn[0], amountInExpected, "test_SwapAVAXForExactTokens::1"); - assertEq(address(this).balance, balanceBefore - amountsIn[0], "test_SwapAVAXForExactTokens::2"); + assertEq(amountsIn[0], amountInExpected, "test_SwapNATIVEForExactTokens::1"); + assertEq(address(this).balance, balanceBefore - amountsIn[0], "test_SwapNATIVEForExactTokens::2"); (amountInExpected,,) = router.getSwapIn(pair, amountOut, true); - // Revert if not enough AVAX has been sent + // Revert if not enough NATIVE has been sent vm.expectRevert( abi.encodeWithSelector( ILBRouter.LBRouter__MaxAmountInExceeded.selector, amountInExpected / 2, amountInExpected ) ); - router.swapAVAXForExactTokens{value: amountInExpected / 2}(amountOut, path, address(this), block.timestamp + 1); + router.swapNATIVEForExactTokens{value: amountInExpected / 2}( + amountOut, path, address(this), block.timestamp + 1 + ); - // Revert if token in isn't WAVAX + // Revert if token in isn't WNATIVE path.tokenPath[0] = usdt; vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); - router.swapAVAXForExactTokens{value: amountInExpected}(amountOut, path, address(this), block.timestamp + 1); + router.swapNATIVEForExactTokens{value: amountInExpected}(amountOut, path, address(this), block.timestamp + 1); // Revert is dealine passed vm.expectRevert( abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) ); - router.swapAVAXForExactTokens{value: amountInExpected}(amountOut, path, address(this), block.timestamp - 1); + router.swapNATIVEForExactTokens{value: amountInExpected}(amountOut, path, address(this), block.timestamp - 1); // Revert if the path arrays are not valid path.tokenPath = new IERC20[](0); vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); - router.swapAVAXForExactTokens{value: amountInExpected}(amountOut, path, address(this), block.timestamp + 1); + router.swapNATIVEForExactTokens{value: amountInExpected}(amountOut, path, address(this), block.timestamp + 1); } - function test_SwapExactTokensForAVAXSupportingFeeOnTransferTokens() public { + function test_SwapExactTokensForNATIVESupportingFeeOnTransferTokens() public { uint128 amountIn = 20e18; - ILBPair pair = factory.getLBPairInformation(taxToken, wavax, DEFAULT_BIN_STEP).LBPair; + ILBPair pair = factory.getLBPairInformation(taxToken, wnative, DEFAULT_BIN_STEP).LBPair; (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, true); - ILBRouter.Path memory path = _buildPath(taxToken, wavax); + ILBRouter.Path memory path = _buildPath(taxToken, wnative); // Reverts if amountOut is less than amountOutMin vm.expectRevert( @@ -329,27 +334,27 @@ contract LiquidityBinRouterSwapTest is TestHelper { ILBRouter.LBRouter__InsufficientAmountOut.selector, amountOutExpected, amountOutExpected / 2 ) ); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + router.swapExactTokensForNATIVESupportingFeeOnTransferTokens( amountIn, amountOutExpected, path, payable(address(this)), block.timestamp + 1 ); uint256 balanceBefore = address(this).balance; - (uint256 amountOut) = router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + (uint256 amountOut) = router.swapExactTokensForNATIVESupportingFeeOnTransferTokens( amountIn, amountOutExpected / 2, path, payable(address(this)), block.timestamp + 1 ); - assertEq(amountOut, amountOutExpected / 2, "test_SwapExactTokensForAVAXSupportingFeeOnTransferTokens::1"); + assertEq(amountOut, amountOutExpected / 2, "test_SwapExactTokensForNATIVESupportingFeeOnTransferTokens::1"); assertEq( address(this).balance, balanceBefore + amountOut, - "test_SwapExactTokensForAVAXSupportingFeeOnTransferTokens::2" + "test_SwapExactTokensForNATIVESupportingFeeOnTransferTokens::2" ); - // Revert if token out isn't WAVAX + // Revert if token out isn't WNATIVE path.tokenPath[1] = usdt; vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + router.swapExactTokensForNATIVESupportingFeeOnTransferTokens( amountIn, amountOutExpected + 1, path, payable(address(this)), block.timestamp + 1 ); @@ -357,25 +362,25 @@ contract LiquidityBinRouterSwapTest is TestHelper { vm.expectRevert( abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) ); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + router.swapExactTokensForNATIVESupportingFeeOnTransferTokens( amountIn, amountOutExpected, path, payable(address(this)), block.timestamp - 1 ); // Revert if the path arrays are not valid path.tokenPath = new IERC20[](0); vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( + router.swapExactTokensForNATIVESupportingFeeOnTransferTokens( amountIn, amountOutExpected, path, payable(address(this)), block.timestamp + 1 ); } - function test_SwapExactAVAXForTokensSupportingFeeOnTransferTokens() public { + function test_SwapExactNATIVEForTokensSupportingFeeOnTransferTokens() public { uint128 amountIn = 20e18; - ILBPair pair = factory.getLBPairInformation(taxToken, wavax, DEFAULT_BIN_STEP).LBPair; + ILBPair pair = factory.getLBPairInformation(taxToken, wnative, DEFAULT_BIN_STEP).LBPair; (, uint128 amountOutExpected,) = router.getSwapOut(pair, amountIn, false); - ILBRouter.Path memory path = _buildPath(wavax, taxToken); + ILBRouter.Path memory path = _buildPath(wnative, taxToken); // Reverts if amountOut is less than amountOutMin vm.expectRevert( @@ -383,27 +388,27 @@ contract LiquidityBinRouterSwapTest is TestHelper { ILBRouter.LBRouter__InsufficientAmountOut.selector, amountOutExpected, amountOutExpected / 2 + 1 ) ); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( + router.swapExactNATIVEForTokensSupportingFeeOnTransferTokens{value: amountIn}( amountOutExpected, path, address(this), block.timestamp + 1 ); uint256 balanceBefore = taxToken.balanceOf(address(this)); - (uint256 amountOut) = router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( + (uint256 amountOut) = router.swapExactNATIVEForTokensSupportingFeeOnTransferTokens{value: amountIn}( amountOutExpected / 2, path, address(this), block.timestamp + 1 ); - assertEq(amountOut, amountOutExpected / 2 + 1, "test_SwapExactAVAXForTokensSupportingFeeOnTransferTokens::1"); + assertEq(amountOut, amountOutExpected / 2 + 1, "test_SwapExactNATIVEForTokensSupportingFeeOnTransferTokens::1"); assertEq( taxToken.balanceOf(address(this)), balanceBefore + amountOut, - "test_SwapExactAVAXForTokensSupportingFeeOnTransferTokens::2" + "test_SwapExactNATIVEForTokensSupportingFeeOnTransferTokens::2" ); - // Revert if token in isn't WAVAX + // Revert if token in isn't WNATIVE path.tokenPath[0] = usdt; vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__InvalidTokenPath.selector, address(usdt))); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( + router.swapExactNATIVEForTokensSupportingFeeOnTransferTokens{value: amountIn}( amountOutExpected, path, address(this), block.timestamp + 1 ); @@ -411,14 +416,14 @@ contract LiquidityBinRouterSwapTest is TestHelper { vm.expectRevert( abi.encodeWithSelector(ILBRouter.LBRouter__DeadlineExceeded.selector, block.timestamp - 1, block.timestamp) ); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( + router.swapExactNATIVEForTokensSupportingFeeOnTransferTokens{value: amountIn}( amountOutExpected, path, address(this), block.timestamp - 1 ); // Revert if the path arrays are not valid path.tokenPath = new IERC20[](0); vm.expectRevert(abi.encodeWithSelector(ILBRouter.LBRouter__LengthsMismatch.selector)); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( + router.swapExactNATIVEForTokensSupportingFeeOnTransferTokens{value: amountIn}( amountOutExpected, path, address(this), block.timestamp + 1 ); } diff --git a/test/helpers/TestHelper.sol b/test/helpers/TestHelper.sol index ac9a5782..6c0c99b3 100644 --- a/test/helpers/TestHelper.sol +++ b/test/helpers/TestHelper.sol @@ -22,7 +22,7 @@ import "../../src/interfaces/IPendingOwnable.sol"; import "./Utils.sol"; -import "test/mocks/WAVAX.sol"; +import "test/mocks/WNATIVE.sol"; import "test/mocks/ERC20.sol"; import "test/mocks/FlashBorrower.sol"; import "test/mocks/ERC20TransferTax.sol"; @@ -54,7 +54,7 @@ abstract contract TestHelper is Test { address payable immutable BOB = payable(makeAddr("bob")); // Wrapped Native - WAVAX internal wavax; + WNATIVE internal wnative; // 6 decimals ERC20Mock internal usdc; @@ -74,7 +74,7 @@ abstract contract TestHelper is Test { LBFactory internal factory; LBRouter internal router; LBPair internal localPair; - LBPair internal pairWavax; + LBPair internal pairWnative; LBQuoter internal quoter; LBPair internal pairImplementation; @@ -85,10 +85,10 @@ abstract contract TestHelper is Test { ILBLegacyFactory internal legacyFactoryV2; function setUp() public virtual { - wavax = WAVAX(AvalancheAddresses.WAVAX); + wnative = WNATIVE(AvalancheAddresses.WNATIVE); // If not forking, deploy mock - if (address(wavax).code.length == 0) { - vm.etch(address(wavax), address(new WAVAX()).code); + if (address(wnative).code.length == 0) { + vm.etch(address(wnative), address(new WNATIVE()).code); } // Create mocks @@ -101,7 +101,7 @@ abstract contract TestHelper is Test { taxToken = new ERC20TransferTaxMock(); // Label mocks - vm.label(address(wavax), "wavax"); + vm.label(address(wnative), "wnative"); vm.label(address(usdc), "usdc"); vm.label(address(usdt), "usdt"); vm.label(address(wbtc), "wbtc"); @@ -126,7 +126,7 @@ abstract contract TestHelper is Test { setDefaultFactoryPresets(DEFAULT_BIN_STEP); // Create router - router = new LBRouter(factory, factoryV1, legacyFactoryV2, legacyRouterV2, IWAVAX(address(wavax))); + router = new LBRouter(factory, factoryV1, legacyFactoryV2, legacyRouterV2, IWNATIVE(address(wnative))); // Create quoter quoter = @@ -145,7 +145,7 @@ abstract contract TestHelper is Test { vm.label(address(legacyFactoryV2), "legacyFactoryV2"); // Give approvals to routers - wavax.approve(address(routerV1), type(uint256).max); + wnative.approve(address(routerV1), type(uint256).max); usdc.approve(address(routerV1), type(uint256).max); usdt.approve(address(routerV1), type(uint256).max); wbtc.approve(address(routerV1), type(uint256).max); @@ -154,7 +154,7 @@ abstract contract TestHelper is Test { bnb.approve(address(routerV1), type(uint256).max); taxToken.approve(address(routerV1), type(uint256).max); - wavax.approve(address(legacyRouterV2), type(uint256).max); + wnative.approve(address(legacyRouterV2), type(uint256).max); usdc.approve(address(legacyRouterV2), type(uint256).max); usdt.approve(address(legacyRouterV2), type(uint256).max); wbtc.approve(address(legacyRouterV2), type(uint256).max); @@ -163,7 +163,7 @@ abstract contract TestHelper is Test { bnb.approve(address(legacyRouterV2), type(uint256).max); taxToken.approve(address(legacyRouterV2), type(uint256).max); - wavax.approve(address(router), type(uint256).max); + wnative.approve(address(router), type(uint256).max); usdc.approve(address(router), type(uint256).max); usdt.approve(address(router), type(uint256).max); wbtc.approve(address(router), type(uint256).max); @@ -186,7 +186,7 @@ abstract contract TestHelper is Test { } function addAllAssetsToQuoteWhitelist() internal { - if (address(wavax) != address(0)) factory.addQuoteAsset(wavax); + if (address(wnative) != address(0)) factory.addQuoteAsset(wnative); if (address(usdc) != address(0)) factory.addQuoteAsset(usdc); if (address(usdt) != address(0)) factory.addQuoteAsset(usdt); if (address(wbtc) != address(0)) factory.addQuoteAsset(wbtc); @@ -301,7 +301,7 @@ abstract contract TestHelper is Test { uint8 nbBinX, uint8 nbBinY ) public { - deal(address(wavax), from, amountX); + deal(address(wnative), from, amountX); deal(address(usdc), from, amountY); uint256 total = getTotalBins(nbBinX, nbBinY); @@ -318,7 +318,7 @@ abstract contract TestHelper is Test { } vm.startPrank(from); - wavax.transfer(address(lbPair), amountX); + wnative.transfer(address(lbPair), amountX); usdc.transfer(address(lbPair), amountY); vm.stopPrank(); diff --git a/test/integration/Addresses.sol b/test/integration/Addresses.sol index fec25f67..d7615def 100644 --- a/test/integration/Addresses.sol +++ b/test/integration/Addresses.sol @@ -8,7 +8,7 @@ library AvalancheAddresses { address internal constant JOE_V1_ROUTER = 0x60aE616a2155Ee3d9A68541Ba4544862310933d4; address internal constant JOE_V2_FACTORY = 0x6E77932A92582f504FF6c4BdbCef7Da6c198aEEf; address internal constant JOE_V2_ROUTER = 0xE3Ffc583dC176575eEA7FD9dF2A7c65F7E23f4C3; - address internal constant WAVAX = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7; + address internal constant WNATIVE = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7; address internal constant USDC = 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E; address internal constant USDT = 0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7; address internal constant WETH = 0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB; diff --git a/test/integration/LBQuoter.t.sol b/test/integration/LBQuoter.t.sol index 22d3a389..f5276452 100644 --- a/test/integration/LBQuoter.t.sol +++ b/test/integration/LBQuoter.t.sol @@ -7,7 +7,7 @@ import "../helpers/TestHelper.sol"; /** * Market deployed: * - USDT/USDC, V1 with low liquidity, V2 with high liquidity - * - WAVAX/USDC, V1 with high liquidity, V2 with low liquidity + * - WNATIVE/USDC, V1 with high liquidity, V2 with low liquidity * - WETH/USDC, V1 with low liquidity, V2.1 with high liquidity * - BNB/USDC, V2 with high liquidity, V2.1 with low liquidity * @@ -31,7 +31,7 @@ contract LiquidityBinQuoterTest is TestHelper { // Get tokens to add liquidity deal(address(usdc), address(this), 10 * highLiquidityAmount); deal(address(usdt), address(this), 10 * highLiquidityAmount); - deal(address(wavax), address(this), 10 * highLiquidityAmount); + deal(address(wnative), address(this), 10 * highLiquidityAmount); deal(address(weth), address(this), 10 * highLiquidityAmount); deal(address(bnb), address(this), 10 * highLiquidityAmount); @@ -48,9 +48,9 @@ contract LiquidityBinQuoterTest is TestHelper { ); routerV1.addLiquidity( - address(wavax), + address(wnative), address(usdc), - highLiquidityAmount, // 1 AVAX = 1 USDC + highLiquidityAmount, // 1 NATIVE = 1 USDC highLiquidityAmount, 0, 0, @@ -72,7 +72,7 @@ contract LiquidityBinQuoterTest is TestHelper { vm.startPrank(AvalancheAddresses.V2_FACTORY_OWNER); legacyFactoryV2.addQuoteAsset(usdc); legacyFactoryV2.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); // 1 USDT = 1 USDC - legacyFactoryV2.createLBPair(wavax, usdc, ID_ONE + 50, DEFAULT_BIN_STEP); // 1 AVAX > 1 USDC + legacyFactoryV2.createLBPair(wnative, usdc, ID_ONE + 50, DEFAULT_BIN_STEP); // 1 NATIVE > 1 USDC legacyFactoryV2.createLBPair(bnb, usdc, ID_ONE, DEFAULT_BIN_STEP); // 1 BNB = 1 USDC vm.stopPrank(); @@ -84,7 +84,7 @@ contract LiquidityBinQuoterTest is TestHelper { getLiquidityParameters(usdt, usdc, highLiquidityAmount, ID_ONE, 7, 0); legacyRouterV2.addLiquidity(liquidityParameters.toLegacy()); - liquidityParameters = getLiquidityParameters(wavax, usdc, lowLiquidityAmount, ID_ONE + 50, 7, 0); + liquidityParameters = getLiquidityParameters(wnative, usdc, lowLiquidityAmount, ID_ONE + 50, 7, 0); legacyRouterV2.addLiquidity(liquidityParameters.toLegacy()); liquidityParameters = getLiquidityParameters(weth, usdc, highLiquidityAmount, ID_ONE, 7, 0); @@ -158,9 +158,9 @@ contract LiquidityBinQuoterTest is TestHelper { } function test_Scenario2() public { - // WAVAX/USDC, V1 with high liquidity, V2 with low liquidity + // WNATIVE/USDC, V1 with high liquidity, V2 with low liquidity address[] memory route = new address[](2); - route[0] = address(wavax); + route[0] = address(wnative); route[1] = address(usdc); // Small amountIn diff --git a/test/integration/LBRouter.t.sol b/test/integration/LBRouter.t.sol index b8abfe2f..64401095 100644 --- a/test/integration/LBRouter.t.sol +++ b/test/integration/LBRouter.t.sol @@ -7,9 +7,9 @@ import "../helpers/TestHelper.sol"; /** * Pairs created: * USDT/USDC V1 - * AVAX/USDC V2 - * WETH/AVAX V2.1 - * TaxToken/AVAX V2.1 + * NATIVE/USDC V2 + * WETH/NATIVE V2.1 + * TaxToken/NATIVE V2.1 */ contract LiquidityBinRouterForkTest is TestHelper { using Utils for ILBRouter.LiquidityParameters; @@ -40,22 +40,22 @@ contract LiquidityBinRouterForkTest is TestHelper { vm.startPrank(AvalancheAddresses.V2_FACTORY_OWNER); legacyFactoryV2.addQuoteAsset(usdc); - legacyFactoryV2.createLBPair(wavax, usdc, ID_ONE, DEFAULT_BIN_STEP); // 1 AVAX = 1 USDC + legacyFactoryV2.createLBPair(wnative, usdc, ID_ONE, DEFAULT_BIN_STEP); // 1 NATIVE = 1 USDC vm.stopPrank(); - factory.createLBPair(weth, wavax, ID_ONE, DEFAULT_BIN_STEP); // 1 WETH = 1 AVAX - factory.createLBPair(taxToken, wavax, ID_ONE, DEFAULT_BIN_STEP); // 1 TaxToken = 1 AVAX + factory.createLBPair(weth, wnative, ID_ONE, DEFAULT_BIN_STEP); // 1 WETH = 1 NATIVE + factory.createLBPair(taxToken, wnative, ID_ONE, DEFAULT_BIN_STEP); // 1 TaxToken = 1 NATIVE // Add liquidity to V2 ILBRouter.LiquidityParameters memory liquidityParameters = - getLiquidityParameters(wavax, usdc, liquidityAmount, ID_ONE, 7, 0); + getLiquidityParameters(wnative, usdc, liquidityAmount, ID_ONE, 7, 0); legacyRouterV2.addLiquidityAVAX{value: liquidityParameters.amountX}(liquidityParameters.toLegacy()); - liquidityParameters = getLiquidityParameters(weth, wavax, liquidityAmount, ID_ONE, 7, 0); - router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); + liquidityParameters = getLiquidityParameters(weth, wnative, liquidityAmount, ID_ONE, 7, 0); + router.addLiquidityNATIVE{value: liquidityParameters.amountY}(liquidityParameters); - liquidityParameters = getLiquidityParameters(taxToken, wavax, liquidityAmount, ID_ONE, 7, 0); - router.addLiquidityAVAX{value: liquidityParameters.amountY}(liquidityParameters); + liquidityParameters = getLiquidityParameters(taxToken, wnative, liquidityAmount, ID_ONE, 7, 0); + router.addLiquidityNATIVE{value: liquidityParameters.amountY}(liquidityParameters); } function test_SwapExactTokensForTokens() public { @@ -136,7 +136,7 @@ contract LiquidityBinRouterForkTest is TestHelper { if (tokenIn == usdt) { path.tokenPath[0] = tokenIn; path.tokenPath[1] = usdc; - path.tokenPath[2] = wavax; + path.tokenPath[2] = wnative; path.tokenPath[3] = tokenOut; path.pairBinSteps[0] = 0; @@ -148,7 +148,7 @@ contract LiquidityBinRouterForkTest is TestHelper { path.versions[2] = ILBRouter.Version.V2_1; } else { path.tokenPath[0] = tokenIn; - path.tokenPath[1] = wavax; + path.tokenPath[1] = wnative; path.tokenPath[2] = usdc; path.tokenPath[3] = tokenOut; diff --git a/test/libraries/BinHelper.t.sol b/test/libraries/BinHelper.t.sol index cebffe7b..fe3d9ac5 100644 --- a/test/libraries/BinHelper.t.sol +++ b/test/libraries/BinHelper.t.sol @@ -339,11 +339,11 @@ contract BinHelperTest is TestHelper { address pair = address(this); deal(address(usdc), pair, reserveX + sentX); - deal(address(wavax), pair, reserveY + sentY); + deal(address(wnative), pair, reserveY + sentY); bytes32 reserves = reserveX.encode(reserveY); - bytes32 received = reserves.received(IERC20(address(usdc)), IERC20(address(wavax))); + bytes32 received = reserves.received(IERC20(address(usdc)), IERC20(address(wnative))); (uint256 receivedX, uint256 receivedY) = received.decode(); @@ -355,7 +355,7 @@ contract BinHelperTest is TestHelper { assertEq(receivedX, sentX, "test_Received::3"); - received = reserves.receivedY(IERC20(address(wavax))); + received = reserves.receivedY(IERC20(address(wnative))); receivedY = received.decodeY(); assertEq(receivedY, sentY, "test_Received::4"); @@ -365,32 +365,32 @@ contract BinHelperTest is TestHelper { address recipient = address(1); deal(address(usdc), address(this), amountX); - deal(address(wavax), address(this), amountY); + deal(address(wnative), address(this), amountY); bytes32 amounts = amountX.encode(amountY); bytes32 firstHalf = amounts.sub((amountX / 2).encode((amountY / 2))); bytes32 secondHalf = amounts.sub(firstHalf); - firstHalf.transfer(IERC20(address(usdc)), IERC20(address(wavax)), recipient); + firstHalf.transfer(IERC20(address(usdc)), IERC20(address(wnative)), recipient); assertEq(usdc.balanceOf(recipient), firstHalf.decodeX(), "test_Transfer::1"); - assertEq(wavax.balanceOf(recipient), firstHalf.decodeY(), "test_Transfer::2"); + assertEq(wnative.balanceOf(recipient), firstHalf.decodeY(), "test_Transfer::2"); assertEq(usdc.balanceOf(address(this)), secondHalf.decodeX(), "test_Transfer::3"); - assertEq(wavax.balanceOf(address(this)), secondHalf.decodeY(), "test_Transfer::4"); + assertEq(wnative.balanceOf(address(this)), secondHalf.decodeY(), "test_Transfer::4"); secondHalf.transferX(IERC20(address(usdc)), recipient); assertEq(usdc.balanceOf(recipient), amounts.decodeX(), "test_Transfer::5"); - assertEq(wavax.balanceOf(recipient), firstHalf.decodeY(), "test_Transfer::6"); + assertEq(wnative.balanceOf(recipient), firstHalf.decodeY(), "test_Transfer::6"); assertEq(usdc.balanceOf(address(this)), 0, "test_Transfer::7"); - assertEq(wavax.balanceOf(address(this)), secondHalf.decodeY(), "test_Transfer::8"); + assertEq(wnative.balanceOf(address(this)), secondHalf.decodeY(), "test_Transfer::8"); - secondHalf.transferY(IERC20(address(wavax)), recipient); + secondHalf.transferY(IERC20(address(wnative)), recipient); assertEq(usdc.balanceOf(recipient), amounts.decodeX(), "test_Transfer::9"); - assertEq(wavax.balanceOf(recipient), amounts.decodeY(), "test_Transfer::10"); + assertEq(wnative.balanceOf(recipient), amounts.decodeY(), "test_Transfer::10"); assertEq(usdc.balanceOf(address(this)), 0, "test_Transfer::11"); - assertEq(wavax.balanceOf(address(this)), 0, "test_Transfer::12"); + assertEq(wnative.balanceOf(address(this)), 0, "test_Transfer::12"); } } diff --git a/test/mocks/Faucet.sol b/test/mocks/Faucet.sol index 86e983b4..30ec3f1a 100644 --- a/test/mocks/Faucet.sol +++ b/test/mocks/Faucet.sol @@ -9,8 +9,8 @@ import {TokenHelper, IERC20} from "src/libraries/TokenHelper.sol"; /// @author Trader Joe /// @dev This contract should only be used for testnet /// @notice Create a faucet contract that create test tokens and allow user to request for tokens. -/// This faucet will also provide AVAX if avax were sent to the contract (either during the construction or after). -/// This contract will not fail if its avax balance becomes too low, it will just not send AVAX but will mint the different tokens. +/// This faucet will also provide NATIVE if native were sent to the contract (either during the construction or after). +/// This contract will not fail if its native balance becomes too low, it will just not send NATIVE but will mint the different tokens. contract Faucet is PendingOwnable { using TokenHelper for IERC20; @@ -55,15 +55,15 @@ contract Faucet is PendingOwnable { _; } - /// @notice Constructor of the faucet, set the request cooldown and add avax to the faucet - /// @param _avaxPerRequest The avax received per request + /// @notice Constructor of the faucet, set the request cooldown and add native to the faucet + /// @param _nativePerRequest The native received per request /// @param _requestCooldown The request cooldown - constructor(uint96 _avaxPerRequest, uint256 _requestCooldown) payable { + constructor(uint96 _nativePerRequest, uint256 _requestCooldown) payable { _setRequestCooldown(_requestCooldown); - _addFaucetToken(FaucetToken({ERC20: IERC20(address(0)), amountPerRequest: _avaxPerRequest})); + _addFaucetToken(FaucetToken({ERC20: IERC20(address(0)), amountPerRequest: _nativePerRequest})); } - /// @notice Allows to receive AVAX directly + /// @notice Allows to receive NATIVE directly receive() external payable {} /// @notice Returns the number of tokens given by the faucet @@ -71,7 +71,7 @@ contract Faucet is PendingOwnable { return faucetTokens.length; } - /// @notice User needs to call this function in order to receive test tokens and avax + /// @notice User needs to call this function in order to receive test tokens and native /// @dev Can be called only once per `requestCooldown` seconds function request() external onlyEOA isRequestUnlocked verifyRequest(msg.sender) { lastRequest[msg.sender] = block.timestamp; @@ -79,7 +79,7 @@ contract Faucet is PendingOwnable { _request(msg.sender); } - /// @notice User needs to call this function in order to receive test tokens and avax + /// @notice User needs to call this function in order to receive test tokens and native /// @dev Can be called only once per `requestCooldown` seconds for every address /// Can only be called by the operator /// @param _to The address that will receive the tokens @@ -98,7 +98,7 @@ contract Faucet is PendingOwnable { } /// @notice Remove a token from the faucet - /// @dev Token needs to be in the set, and AVAX can't be removed + /// @dev Token needs to be in the set, and NATIVE can't be removed /// @param _token The address of the token function removeFaucetToken(IERC20 _token) external onlyOwner { uint256 index = tokenToIndices[_token]; @@ -139,7 +139,7 @@ contract Faucet is PendingOwnable { /// @param _to The recipient address /// @param _amount The token amount to send function withdrawToken(IERC20 _token, address _to, uint256 _amount) external onlyOwner { - if (address(_token) == address(0)) _sendAvax(_to, _amount); + if (address(_token) == address(0)) _sendNative(_to, _amount); else _token.safeTransfer(_to, _amount); } @@ -164,7 +164,7 @@ contract Faucet is PendingOwnable { FaucetToken memory token = faucetTokens[0]; if (token.amountPerRequest > 0 && address(this).balance >= token.amountPerRequest) { - _sendAvax(_to, token.amountPerRequest); + _sendNative(_to, token.amountPerRequest); } for (uint256 i = 1; i < len; ++i) { @@ -206,11 +206,11 @@ contract Faucet is PendingOwnable { faucetTokens[index - 1].amountPerRequest = _amountPerRequest; } - /// @notice Private function to send `amount` AVAX to `to` + /// @notice Private function to send `amount` NATIVE to `to` /// @param _to The recipient address - /// @param _amount The AVAX amount to send - function _sendAvax(address _to, uint256 _amount) private { + /// @param _amount The NATIVE amount to send + function _sendNative(address _to, uint256 _amount) private { (bool success,) = _to.call{value: _amount}(""); - require(success, "AVAX transfer failed"); + require(success, "NATIVE transfer failed"); } } diff --git a/test/mocks/WAVAX.sol b/test/mocks/WNATIVE.sol similarity index 83% rename from test/mocks/WAVAX.sol rename to test/mocks/WNATIVE.sol index 3d1b1a22..af1a601d 100644 --- a/test/mocks/WAVAX.sol +++ b/test/mocks/WNATIVE.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.10; import {ERC20} from "openzeppelin/token/ERC20/ERC20.sol"; -contract WAVAX is ERC20 { - constructor() ERC20("Wrapped Avax", "WAVAX") {} +contract WNATIVE is ERC20 { + constructor() ERC20("Wrapped Native", "WNATIVE") {} function deposit() external payable { _mint(msg.sender, msg.value); diff --git a/test_old/LBFactory.MultiPools.t.sol b/test_old/LBFactory.MultiPools.t.sol deleted file mode 100644 index e9a0265a..00000000 --- a/test_old/LBFactory.MultiPools.t.sol +++ /dev/null @@ -1,174 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; - -contract LiquidityBinFactoryTestM is TestHelper { - LBPair internal pair0; - LBPair internal pair1; - LBPair internal pair2; - - function setUp() public override { - factory = new LBFactory(DEV, 8e14); - addAllAssetsToQuoteWhitelist(factory); - - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - - router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(wavax))); - - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - pair0 = createLBPairDefaultFeesFromStartIdAndBinStep(usdc, weth, ID_ONE, DEFAULT_BIN_STEP); - factory.setPreset( - 75, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - 5, - 10, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME - ); - pair1 = createLBPairDefaultFeesFromStartIdAndBinStep(usdc, weth, ID_ONE, 75); - factory.setPreset( - 98, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - 5, - 5, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME - ); - pair2 = createLBPairDefaultFeesFromStartIdAndBinStep(usdc, weth, ID_ONE, 98); - } - - function testSetPresets() public { - ( - uint256 baseFactor, - uint256 filterPeriod, - uint256 decayPeriod, - uint256 reductionFactor, - uint256 variableFeeControl, - uint256 protocolShare, - uint256 maxVolatilityAccumulated, - uint256 sampleLifetime - ) = factory.getPreset(DEFAULT_BIN_STEP); - - assertEq(baseFactor, DEFAULT_BASE_FACTOR); - assertEq(filterPeriod, DEFAULT_FILTER_PERIOD); - assertEq(decayPeriod, DEFAULT_DECAY_PERIOD); - assertEq(reductionFactor, DEFAULT_REDUCTION_FACTOR); - assertEq(variableFeeControl, DEFAULT_VARIABLE_FEE_CONTROL); - assertEq(protocolShare, DEFAULT_PROTOCOL_SHARE); - assertEq(maxVolatilityAccumulated, DEFAULT_MAX_VOLATILITY_ACCUMULATED); - assertEq(sampleLifetime, DEFAULT_SAMPLE_LIFETIME); - - factory.setPreset( - DEFAULT_BIN_STEP, - DEFAULT_BASE_FACTOR - 1, - DEFAULT_FILTER_PERIOD - 1, - DEFAULT_DECAY_PERIOD - 1, - DEFAULT_REDUCTION_FACTOR - 1, - DEFAULT_VARIABLE_FEE_CONTROL - 1, - DEFAULT_PROTOCOL_SHARE - 1, - DEFAULT_MAX_VOLATILITY_ACCUMULATED - 1, - DEFAULT_SAMPLE_LIFETIME - 1 - ); - - ( - baseFactor, - filterPeriod, - decayPeriod, - reductionFactor, - variableFeeControl, - protocolShare, - maxVolatilityAccumulated, - sampleLifetime - ) = factory.getPreset(DEFAULT_BIN_STEP); - - assertEq(baseFactor, DEFAULT_BASE_FACTOR - 1); - assertEq(filterPeriod, DEFAULT_FILTER_PERIOD - 1); - assertEq(decayPeriod, DEFAULT_DECAY_PERIOD - 1); - assertEq(reductionFactor, DEFAULT_REDUCTION_FACTOR - 1); - assertEq(variableFeeControl, DEFAULT_VARIABLE_FEE_CONTROL - 1); - assertEq(protocolShare, DEFAULT_PROTOCOL_SHARE - 1); - assertEq(maxVolatilityAccumulated, DEFAULT_MAX_VOLATILITY_ACCUMULATED - 1); - assertEq(sampleLifetime, DEFAULT_SAMPLE_LIFETIME - 1); - - vm.expectRevert(abi.encodeWithSelector(LBFactory__BinStepHasNoPreset.selector, 3)); - factory.getPreset(3); - } - - function testAddRemovePresets() public { - uint256[] memory binSteps = factory.getAllBinSteps(); - assertEq(binSteps.length, 3); - assertEq(binSteps[0], DEFAULT_BIN_STEP); - assertEq(binSteps[1], 75); - assertEq(binSteps[2], 98); - - setDefaultFactoryPresets(12); - binSteps = factory.getAllBinSteps(); - assertEq(binSteps.length, 4); - assertEq(binSteps[0], 12); - assertEq(binSteps[1], DEFAULT_BIN_STEP); - assertEq(binSteps[2], 75); - assertEq(binSteps[3], 98); - - factory.removePreset(75); - binSteps = factory.getAllBinSteps(); - assertEq(binSteps.length, 3); - assertEq(binSteps[0], 12); - assertEq(binSteps[1], DEFAULT_BIN_STEP); - assertEq(binSteps[2], 98); - - vm.expectRevert(abi.encodeWithSelector(LBFactory__BinStepHasNoPreset.selector, 75)); - factory.removePreset(75); - } - - function testAvailableBinSteps() public { - ILBFactory.LBPairInformation[] memory LBPairBinSteps = factory.getAllLBPairs(usdc, weth); - assertEq(LBPairBinSteps.length, 3); - assertEq(LBPairBinSteps[0].binStep, DEFAULT_BIN_STEP); - assertEq(LBPairBinSteps[1].binStep, 75); - assertEq(LBPairBinSteps[2].binStep, 98); - assertEq(LBPairBinSteps[0].createdByOwner, true); - assertEq(LBPairBinSteps[1].createdByOwner, true); - assertEq(LBPairBinSteps[2].createdByOwner, true); - - ILBFactory.LBPairInformation[] memory LBPairBinStepsReversed = factory.getAllLBPairs(weth, usdc); - assertEq(LBPairBinStepsReversed.length, 3); - assertEq(LBPairBinStepsReversed[0].binStep, DEFAULT_BIN_STEP); - assertEq(LBPairBinStepsReversed[1].binStep, 75); - assertEq(LBPairBinStepsReversed[2].binStep, 98); - - factory.removePreset(75); - factory.removePreset(98); - - ILBFactory.LBPairInformation[] memory LBPairBinStepsAfterPresetRemoval = factory.getAllLBPairs(usdc, weth); - assertEq(LBPairBinStepsAfterPresetRemoval.length, 3); - assertEq(LBPairBinStepsAfterPresetRemoval[0].binStep, DEFAULT_BIN_STEP); - assertEq(LBPairBinStepsAfterPresetRemoval[1].binStep, 75); - assertEq(LBPairBinStepsAfterPresetRemoval[2].binStep, 98); - - factory.setLBPairIgnored(usdc, weth, DEFAULT_BIN_STEP, true); - factory.setLBPairIgnored(weth, usdc, 98, true); - - ILBFactory.LBPairInformation[] memory LBPairBinStepsAfterIgnored = factory.getAllLBPairs(usdc, weth); - assertEq(LBPairBinStepsAfterIgnored.length, 3); - assertEq(LBPairBinStepsAfterIgnored[0].ignoredForRouting, true); - assertEq(LBPairBinStepsAfterIgnored[1].ignoredForRouting, false); - assertEq(LBPairBinStepsAfterIgnored[2].ignoredForRouting, true); - - factory.setLBPairIgnored(usdc, weth, DEFAULT_BIN_STEP, false); - - ILBFactory.LBPairInformation[] memory LBPairBinStepsAfterRemovalOfIgnored = factory.getAllLBPairs(usdc, weth); - assertEq(LBPairBinStepsAfterRemovalOfIgnored.length, 3); - assertEq(LBPairBinStepsAfterRemovalOfIgnored[0].ignoredForRouting, false); - assertEq(LBPairBinStepsAfterRemovalOfIgnored[1].ignoredForRouting, false); - assertEq(LBPairBinStepsAfterRemovalOfIgnored[2].ignoredForRouting, true); - } -} diff --git a/test_old/LBPair.Fees.t.sol b/test_old/LBPair.Fees.t.sol deleted file mode 100644 index 4d9d38dd..00000000 --- a/test_old/LBPair.Fees.t.sol +++ /dev/null @@ -1,404 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; -import "src/libraries/Math512Bits.sol"; - -contract LiquidityBinPairFeesTest is TestHelper { - using Math512Bits for uint256; - - function setUp() public override { - usdc = new ERC20Mock(6); - weth = new ERC20Mock(18); - - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - addAllAssetsToQuoteWhitelist(factory); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - - router = new LBRouter(ILBFactory(DEV), IJoeFactory(DEV), IWAVAX(DEV)); - - pair = createLBPairDefaultFees(usdc, weth); - } - - function testClaimFeesY() public { - uint256 amountYInLiquidity = 100e18; - uint256 amountXOutForSwap = 1e18; - uint24 startId = ID_ONE; - - addLiquidity(amountYInLiquidity, startId, 5, 0); - - (uint256 amountYInForSwap, uint256 feesFromGetSwapIn) = router.getSwapIn(pair, amountXOutForSwap, false); - - weth.mint(address(pair), amountYInForSwap); - vm.prank(ALICE); - pair.swap(false, DEV); - - (, uint256 feesYTotal,, uint256 feesYProtocol) = pair.getGlobalFees(); - assertEq(feesYTotal, feesFromGetSwapIn); - - uint256 accumulatedYFees = feesYTotal - feesYProtocol; - - uint256[] memory orderedIds = new uint256[](5); - for (uint256 i; i < 5; i++) { - orderedIds[i] = startId - 2 + i; - } - - (uint256 feeX, uint256 feeY) = pair.pendingFees(DEV, orderedIds); - - assertApproxEqAbs(accumulatedYFees, feeY, 1); - - uint256 balanceBefore = weth.balanceOf(DEV); - pair.collectFees(DEV, orderedIds); - assertEq(feeY, weth.balanceOf(DEV) - balanceBefore); - - // Trying to claim a second time - balanceBefore = weth.balanceOf(DEV); - (feeX, feeY) = pair.pendingFees(DEV, orderedIds); - assertEq(feeY, 0); - - (feeX, feeY) = pair.pendingFees(address(0), orderedIds); - assertEq(feeY, 0); - assertEq(feeX, 0); - (feeX, feeY) = pair.pendingFees(address(pair), orderedIds); - assertEq(feeY, 0); - assertEq(feeX, 0); - - pair.collectFees(DEV, orderedIds); - assertEq(weth.balanceOf(DEV), balanceBefore); - } - - function testClaimFeesX() public { - uint256 amountYInLiquidity = 100e18; - uint256 amountYOutForSwap = 1e18; - uint24 startId = ID_ONE; - - addLiquidity(amountYInLiquidity, startId, 5, 0); - - (uint256 amountXInForSwap, uint256 feesFromGetSwapIn) = router.getSwapIn(pair, amountYOutForSwap, true); - - usdc.mint(address(pair), amountXInForSwap); - vm.prank(ALICE); - pair.swap(true, DEV); - - (uint256 feesXTotal,, uint256 feesXProtocol,) = pair.getGlobalFees(); - assertEq(feesXTotal, feesFromGetSwapIn); - uint256 accumulatedXFees = feesXTotal - feesXProtocol; - - uint256[] memory orderedIds = new uint256[](5); - for (uint256 i; i < 5; i++) { - orderedIds[i] = startId - 2 + i; - } - - (uint256 feeX, uint256 feeY) = pair.pendingFees(DEV, orderedIds); - - assertApproxEqAbs(accumulatedXFees, feeX, 1); - - uint256 balanceBefore = usdc.balanceOf(DEV); - pair.collectFees(DEV, orderedIds); - assertEq(feeX, usdc.balanceOf(DEV) - balanceBefore); - - // Trying to claim a second time - balanceBefore = usdc.balanceOf(DEV); - (feeX, feeY) = pair.pendingFees(DEV, orderedIds); - assertEq(feeX, 0); - - (feeX, feeY) = pair.pendingFees(address(0), orderedIds); - assertEq(feeY, 0); - assertEq(feeX, 0); - - (feeX, feeY) = pair.pendingFees(address(pair), orderedIds); - assertEq(feeY, 0); - assertEq(feeX, 0); - - pair.collectFees(DEV, orderedIds); - assertEq(usdc.balanceOf(DEV), balanceBefore); - } - - function testFeesOnTokenTransfer() public { - uint256 amountYInLiquidity = 100e18; - uint256 amountYForSwap = 1e6; - uint24 startId = ID_ONE; - - (uint256[] memory _ids,,,) = addLiquidity(amountYInLiquidity, startId, 5, 0); - - weth.mint(address(pair), amountYForSwap); - - pair.swap(false, ALICE); - - uint256[] memory amounts = new uint256[](5); - for (uint256 i; i < 5; i++) { - amounts[i] = pair.balanceOf(DEV, _ids[i]); - } - - pair.safeBatchTransferFrom(DEV, BOB, _ids, amounts); - - weth.mint(address(pair), amountYForSwap); - pair.swap(false, ALICE); - - (uint256 feesForDevX, uint256 feesForDevY) = pair.pendingFees(DEV, _ids); - (uint256 feesForBobX, uint256 feesForBobY) = pair.pendingFees(BOB, _ids); - - assertGt(feesForDevY, 0, "DEV should have fees on token Y"); - assertGt(feesForBobY, 0, "BOB should also have fees on token Y"); - - (, uint256 feesYTotal,, uint256 feesYProtocol) = pair.getGlobalFees(); - - uint256 accumulatedYFees = feesYTotal - feesYProtocol; - - assertApproxEqAbs(feesForDevY + feesForBobY, accumulatedYFees, 1, "Sum of users fees = accumulated fees"); - - uint256 balanceBefore = weth.balanceOf(DEV); - pair.collectFees(DEV, _ids); - assertEq(feesForDevY, weth.balanceOf(DEV) - balanceBefore, "DEV gets the expected amount when withdrawing fees"); - - balanceBefore = weth.balanceOf(BOB); - pair.collectFees(BOB, _ids); - assertEq(feesForBobY, weth.balanceOf(BOB) - balanceBefore, "BOB gets the expected amount when withdrawing fees"); - } - - struct FeeInfo { - uint256 feeXTotal; - uint256 feeYTotal; - uint256 feeXProtocol; - uint256 feeYProtocol; - } - - function _getGlobalFees() internal view returns (FeeInfo memory) { - (uint256 feesXTotal, uint256 feesYTotal, uint256 feesXProtocol, uint256 feesYProtocol) = pair.getGlobalFees(); - return FeeInfo(feesXTotal, feesYTotal, feesXProtocol, feesYProtocol); - } - - function testClaimProtocolFees() public { - addLiquidity(100e18, ID_ONE, 5, 0); - - // Add Y fees - (uint256 amountIn, uint256 feesIn) = router.getSwapIn(pair, 1e6, false); - weth.mint(address(pair), amountIn); - - vm.prank(ALICE); - pair.swap(false, DEV); - - // Claiming rewards for Y - FeeInfo memory feesBefore = _getGlobalFees(); - - assertEq(feesBefore.feeXTotal, 0); - assertEq(feesBefore.feeXProtocol, 0); - - assertGt(feesBefore.feeYTotal, 0); - assertEq(feesBefore.feeYTotal, feesIn); - - assertGt(feesBefore.feeYProtocol, 0); - assertLt(feesBefore.feeYProtocol, feesBefore.feeYTotal); - - address protocolFeesReceiver = factory.feeRecipient(); - uint256 balanceBefore = weth.balanceOf(protocolFeesReceiver); - - vm.prank(protocolFeesReceiver); - pair.collectProtocolFees(); - - assertEq(weth.balanceOf(protocolFeesReceiver) - balanceBefore, feesBefore.feeYProtocol - 1); - - FeeInfo memory feesAfter = _getGlobalFees(); - - assertEq(feesAfter.feeXTotal, 0); - assertEq(feesAfter.feeXProtocol, 0); - - assertGt(feesAfter.feeYTotal, 0); - assertEq(feesAfter.feeYTotal, feesBefore.feeYTotal - (feesBefore.feeYProtocol - 1)); - assertEq(feesAfter.feeYProtocol, 1); - - // Claiming twice - pair.collectProtocolFees(); - assertEq(weth.balanceOf(protocolFeesReceiver) - balanceBefore, feesBefore.feeYProtocol - 1); - - FeeInfo memory feesAfter2 = _getGlobalFees(); - - assertEq(feesAfter2.feeXTotal, feesAfter.feeXTotal); - assertEq(feesAfter2.feeXProtocol, feesAfter.feeXProtocol); - - assertEq(feesAfter2.feeYTotal, feesAfter.feeYTotal); - assertEq(feesAfter2.feeYProtocol, feesAfter.feeYProtocol); - - // Add X fees - (amountIn, feesIn) = router.getSwapIn(pair, 1e18, true); - usdc.mint(address(pair), amountIn); - - vm.prank(ALICE); - pair.swap(true, DEV); - - // Claiming rewards for X - feesBefore = _getGlobalFees(); - - assertEq(feesBefore.feeYTotal, feesAfter2.feeYTotal); - assertEq(feesBefore.feeYProtocol, feesAfter2.feeYProtocol); - - assertGt(feesBefore.feeXTotal, 0); - assertEq(feesBefore.feeXTotal, feesIn); - - assertGt(feesBefore.feeXProtocol, 0); - assertLt(feesBefore.feeXProtocol, feesBefore.feeXTotal); - - balanceBefore = usdc.balanceOf(protocolFeesReceiver); - - vm.prank(protocolFeesReceiver); - pair.collectProtocolFees(); - - assertEq(usdc.balanceOf(protocolFeesReceiver) - balanceBefore, feesBefore.feeXProtocol - 1); - - feesAfter = _getGlobalFees(); - - assertEq(feesAfter.feeYTotal, feesBefore.feeYTotal); - assertEq(feesAfter.feeYProtocol, feesBefore.feeYProtocol); - - assertGt(feesAfter.feeXTotal, 0); - assertEq(feesAfter.feeXTotal, feesBefore.feeXTotal - (feesBefore.feeXProtocol - 1)); - - assertGt(feesAfter.feeXProtocol, 0); - assertEq(feesAfter.feeXProtocol, 1); - - // Claiming twice - pair.collectProtocolFees(); - assertEq(usdc.balanceOf(protocolFeesReceiver) - balanceBefore, feesBefore.feeXProtocol - 1); - - FeeInfo memory feesAfter2X = _getGlobalFees(); - - assertEq(feesAfter2X.feeXTotal, feesAfter.feeXTotal); - assertEq(feesAfter2X.feeXProtocol, feesAfter.feeXProtocol); - - assertEq(feesAfter2X.feeXTotal, feesAfter.feeXTotal); - assertEq(feesAfter2X.feeXProtocol, feesAfter.feeXProtocol); - - assertEq(feesAfter2X.feeYTotal, feesAfter.feeYTotal); - assertEq(feesAfter2X.feeYProtocol, feesAfter.feeYProtocol); - } - - function testForceDecay() public { - uint256 amountYInLiquidity = 100e18; - uint24 startId = ID_ONE; - - FeeHelper.FeeParameters memory _feeParameters = pair.feeParameters(); - addLiquidity(amountYInLiquidity, startId, 51, 5); - - (uint256 amountYInForSwap,) = router.getSwapIn(pair, amountYInLiquidity / 4, true); - usdc.mint(address(pair), amountYInForSwap); - vm.prank(ALICE); - pair.swap(true, ALICE); - - vm.warp(block.timestamp + 90); - - (amountYInForSwap,) = router.getSwapIn(pair, amountYInLiquidity / 4, true); - usdc.mint(address(pair), amountYInForSwap); - vm.prank(ALICE); - pair.swap(true, ALICE); - - _feeParameters = pair.feeParameters(); - uint256 referenceBeforeForceDecay = _feeParameters.volatilityReference; - uint256 referenceAfterForceDecayExpected = - (uint256(_feeParameters.reductionFactor) * referenceBeforeForceDecay) / Constants.BASIS_POINT_MAX; - - factory.forceDecay(pair); - - _feeParameters = pair.feeParameters(); - uint256 referenceAfterForceDecay = _feeParameters.volatilityReference; - assertEq(referenceAfterForceDecay, referenceAfterForceDecayExpected); - } - - function testClaimFeesComplex(uint256 amountY, uint256 amountX) public { - vm.assume(amountY < 10e18); - vm.assume(amountY > 1); - vm.assume(amountX < 10e18); - vm.assume(amountX > 1); - - uint256 amountYInLiquidity = 100e18; - uint256 totalFeesFromGetSwapX; - uint256 totalFeesFromGetSwapY; - - addLiquidity(amountYInLiquidity, ID_ONE, 5, 0); - - //swap X -> Y and accrue X fees - (uint256 amountXInForSwap, uint256 feesXFromGetSwap) = router.getSwapIn(pair, amountY, true); - totalFeesFromGetSwapX += feesXFromGetSwap; - - usdc.mint(address(pair), amountXInForSwap); - vm.prank(ALICE); - pair.swap(true, DEV); - (uint256 feesXTotal,, uint256 feesXProtocol,) = pair.getGlobalFees(); - assertEq(feesXTotal, totalFeesFromGetSwapX); - - //swap Y -> X and accrue Y fees - (uint256 amountYInForSwap, uint256 feesYFromGetSwap) = router.getSwapIn(pair, amountX, false); - totalFeesFromGetSwapY += feesYFromGetSwap; - weth.mint(address(pair), amountYInForSwap); - vm.prank(ALICE); - pair.swap(false, DEV); - - (, uint256 feesYTotal,, uint256 feesYProtocol) = pair.getGlobalFees(); - assertEq(feesYTotal, totalFeesFromGetSwapY); - - //swap Y -> X and accrue Y fees - (, feesYFromGetSwap) = router.getSwapOut(pair, amountY, false); - totalFeesFromGetSwapY += feesYFromGetSwap; - weth.mint(address(pair), amountY); - vm.prank(ALICE); - pair.swap(false, DEV); - - (, feesYTotal,, feesYProtocol) = pair.getGlobalFees(); - assertEq(feesYTotal, totalFeesFromGetSwapY); - - //swap X -> Y and accrue X fees - (, feesXFromGetSwap) = router.getSwapOut(pair, amountX, true); - totalFeesFromGetSwapX += feesXFromGetSwap; - usdc.mint(address(pair), amountX); - vm.prank(ALICE); - pair.swap(true, DEV); - - (feesXTotal,, feesXProtocol,) = pair.getGlobalFees(); - assertEq(feesXTotal, totalFeesFromGetSwapX); - } - - function testFeesForPairOrZeroAddress() public { - uint256 amountYInLiquidity = 100e18; - uint256 amountXOutForSwap = 1e18; - uint256 amountYOutForSwap = 1e18; - uint24 startId = ID_ONE; - - addLiquidity(amountYInLiquidity, startId, 5, 0); - - (uint256 amountYInForSwap,) = router.getSwapIn(pair, amountXOutForSwap, false); - - // setup non-zero Y fees - weth.mint(address(pair), amountYInForSwap); - vm.prank(ALICE); - pair.swap(false, DEV); - - // setup non-zero X fees - (uint256 amountXInForSwap,) = router.getSwapIn(pair, amountYOutForSwap, true); - usdc.mint(address(pair), amountXInForSwap); - vm.prank(ALICE); - pair.swap(true, DEV); - - uint256[] memory orderedIds = new uint256[](5); - for (uint256 i; i < 5; i++) { - orderedIds[i] = startId - 2 + i; - } - - (uint256 feeX, uint256 feeY) = pair.pendingFees(DEV, orderedIds); - assertGt(feeY, 0); - (feeX, feeY) = pair.pendingFees(address(0), orderedIds); - assertEq(feeY, 0); - assertEq(feeX, 0); - (feeX, feeY) = pair.pendingFees(address(pair), orderedIds); - assertEq(feeY, 0); - assertEq(feeX, 0); - - vm.expectRevert(LBPair__AddressZeroOrThis.selector); - pair.collectFees(address(pair), orderedIds); - vm.expectRevert(LBPair__AddressZeroOrThis.selector); - pair.collectFees(address(0), orderedIds); - - pair.collectFees(DEV, orderedIds); - } -} diff --git a/test_old/LBPair.FlashLoans.t.sol b/test_old/LBPair.FlashLoans.t.sol deleted file mode 100644 index 4cfcc568..00000000 --- a/test_old/LBPair.FlashLoans.t.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; - -contract LiquidityBinPairFlashLoansTest is TestHelper { - FlashBorrower private borrower; - - event CalldataTransmitted(); - - function setUp() public override { - usdc = new ERC20Mock(6); - weth = new ERC20Mock(18); - - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - addAllAssetsToQuoteWhitelist(factory); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - - router = new LBRouter(ILBFactory(DEV), IJoeFactory(DEV), IWAVAX(DEV)); - - pair = createLBPairDefaultFees(usdc, weth); - - borrower = new FlashBorrower(pair); - } - - function testFlashloan() public { - (uint256[] memory _ids,,,) = addLiquidity(100e18, ID_ONE, 9, 5); - uint256 amountXBorrowed = 10e18; - uint256 amountYBorrowed = 10e18; - - // Paying for fees - usdc.mint(address(borrower), 1e18); - weth.mint(address(borrower), 1e18); - - vm.expectEmit(false, false, false, false); - emit CalldataTransmitted(); - - borrower.flashBorrow(amountXBorrowed, amountYBorrowed); - - (uint256 feesForDevX, uint256 feesForDevY) = pair.pendingFees(DEV, _ids); - assertGt(feesForDevX, 0, "DEV should have fees on token X"); - assertGt(feesForDevY, 0, "DEV should have fees on token Y"); - } - - function testFailFlashloanMoreThanReserves() public { - uint256 amountXBorrowed = 150e18; - - usdc.mint(address(borrower), 1e18); - - borrower.flashBorrow(amountXBorrowed, 0); - } - - function testFailFlashlaonWithReentrancy() public { - uint256 amountXBorrowed = 150e18; - - usdc.mint(address(borrower), 1e18); - - borrower.flashBorrowWithReentrancy(amountXBorrowed, 0); - } -} diff --git a/test_old/LBPair.Liquidity.t.sol b/test_old/LBPair.Liquidity.t.sol deleted file mode 100644 index 7d3bbafc..00000000 --- a/test_old/LBPair.Liquidity.t.sol +++ /dev/null @@ -1,252 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; - -contract LiquidityBinPairLiquidityTest is TestHelper { - function setUp() public override { - usdc = new ERC20Mock(6); - weth = new ERC20Mock(18); - - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - addAllAssetsToQuoteWhitelist(factory); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - - router = new LBRouter(ILBFactory(DEV), IJoeFactory(DEV), IWAVAX(DEV)); - } - - function testConstructor( - uint16 _binStep, - uint16 _baseFactor, - uint16 _filterPeriod, - uint16 _decayPeriod, - uint16 _reductionFactor, - uint24 _variableFeeControl, - uint16 _protocolShare, - uint24 _maxVolatilityAccumulated - ) public { - bytes32 _packedFeeParameters = bytes32( - abi.encodePacked( - uint136(_maxVolatilityAccumulated), // The first 112 bits are reserved for the dynamic parameters - _protocolShare, - _variableFeeControl, - _reductionFactor, - _decayPeriod, - _filterPeriod, - _baseFactor, - _binStep - ) - ); - - pair = new LBPair(ILBFactory(DEV)); - pair.initialize(usdc, weth, ID_ONE, DEFAULT_SAMPLE_LIFETIME, _packedFeeParameters); - - assertEq(address(pair.factory()), DEV); - assertEq(address(pair.tokenX()), address(usdc)); - assertEq(address(pair.tokenY()), address(weth)); - - FeeHelper.FeeParameters memory feeParameters = pair.feeParameters(); - assertEq(feeParameters.volatilityAccumulated, 0, "volatilityAccumulated should be 0"); - assertEq(feeParameters.volatilityReference, 0, "volatilityReference should be 0"); - assertEq(feeParameters.indexRef, 0, "indexRef should be 0"); - assertEq(feeParameters.time, 0, "Time should be zero"); - assertEq( - feeParameters.maxVolatilityAccumulated, - _maxVolatilityAccumulated, - "Max volatilityAccumulated should be correctly set" - ); - assertEq(feeParameters.filterPeriod, _filterPeriod, "Filter Period should be correctly set"); - assertEq(feeParameters.decayPeriod, _decayPeriod, "Decay Period should be correctly set"); - assertEq(feeParameters.binStep, _binStep, "Bin Step should be correctly set"); - assertEq(feeParameters.baseFactor, _baseFactor, "Base Factor should be correctly set"); - assertEq(feeParameters.protocolShare, _protocolShare, "Protocol Share should be correctly set"); - } - - function testFuzzingAddLiquidity(uint256 _price) public { - // Avoids Math__Exp2InputTooBig and very small x amounts - vm.assume(_price < 2 ** 238); - // Avoids LBPair__BinReserveOverflows (very big x amounts) - vm.assume(_price > 2 ** 18); - - uint24 startId = getIdFromPrice(_price); - - uint256 _calculatedPrice = getPriceFromId(startId); - - // Can't use `assertApproxEqRel` as it overflow when multiplying by 1e18 - // Assert that price is at most `binStep`% away from the calculated price - assertEq( - ( - ( - ((_price * (Constants.BASIS_POINT_MAX - DEFAULT_BIN_STEP)) / 10_000) <= _calculatedPrice - && _calculatedPrice <= (_price * (Constants.BASIS_POINT_MAX + DEFAULT_BIN_STEP)) / 10_000 - ) - ), - true, - "Wrong log2" - ); - - pair = createLBPairDefaultFeesFromStartId(usdc, weth, startId); - - uint256 amountYIn = _price < type(uint128).max ? 2 ** 18 : type(uint112).max; - uint256 amountXIn = (amountYIn << 112) / _price + 3; - - console.log(amountXIn, amountYIn); - - usdc.mint(address(pair), amountXIn); - weth.mint(address(pair), amountYIn); - - uint256[] memory ids = new uint256[](3); - uint256[] memory distribX = new uint256[](3); - uint256[] memory distribY = new uint256[](3); - - ids[0] = startId - 1; - ids[1] = startId; - ids[2] = startId + 1; - - distribY[0] = Constants.PRECISION / 2; - distribX[1] = Constants.PRECISION / 2; - - distribY[1] = Constants.PRECISION / 2; - distribX[2] = Constants.PRECISION / 2; - - pair.mint(ids, distribX, distribY, DEV); - - (uint256 binXReserve0, uint256 binYReserve0) = pair.getBin(uint24(ids[0])); - (uint256 binXReserve1, uint256 binYReserve1) = pair.getBin(uint24(ids[1])); - (uint256 binXReserve2, uint256 binYReserve2) = pair.getBin(uint24(ids[2])); - - assertEq(binXReserve0, 0, "binXReserve0"); - assertApproxEqRel(binYReserve0, amountYIn / 2, 1e3, "binYReserve0"); - - assertApproxEqRel(binXReserve1, amountXIn / 2, 1e3, "currentBinReserveX"); - assertApproxEqRel(binYReserve1, amountYIn / 2, 1e3, "currentBinReserveY"); - - assertEq(binYReserve2, 0, "binYReserve2"); - assertApproxEqRel(binXReserve2, amountXIn / 2, 1e3, "binXReserve2"); - } - - function testBurnLiquidity() public { - pair = createLBPairDefaultFees(usdc, weth); - uint256 amount1In = 3e12; - (uint256[] memory _ids, uint256[] memory _distributionX, uint256[] memory _distributionY, uint256 amount0In) = - spreadLiquidity(amount1In * 2, ID_ONE, 5, 0); - - usdc.mint(address(pair), amount0In); - weth.mint(address(pair), amount1In); - - pair.mint(_ids, _distributionX, _distributionY, ALICE); - - usdc.mint(address(pair), amount0In); - weth.mint(address(pair), amount1In); - - pair.mint(_ids, _distributionX, _distributionY, BOB); - - uint256[] memory amounts = new uint256[](5); - for (uint256 i; i < 5; i++) { - amounts[i] = pair.balanceOf(BOB, _ids[i]); - } - - vm.startPrank(BOB); - pair.safeBatchTransferFrom(BOB, address(pair), _ids, amounts); - pair.burn(_ids, amounts, BOB); - pair.collectFees(BOB, _ids); // the excess token were sent to fees, so they need to be claimed - vm.stopPrank(); - - assertEq(usdc.balanceOf(BOB), amount0In); - assertEq(weth.balanceOf(BOB), amount1In); - } - - function testFlawedCompositionFactor() public { - uint24 _numberBins = 5; - uint24 startId = ID_ONE; - uint256 amount0In = 3e12; - uint256 amount1In = 3e12; - pair = createLBPairDefaultFees(usdc, weth); - - addLiquidity(amount1In, startId, _numberBins, 0); - - (uint256 reserveX, uint256 reserveY, uint256 activeId) = pair.getReservesAndId(); - - uint256[] memory _ids = new uint256[](3); - _ids[0] = activeId - 1; - _ids[1] = activeId; - _ids[2] = activeId + 1; - uint256[] memory _distributionX = new uint256[](3); - _distributionX[0] = Constants.PRECISION / 3; - _distributionX[1] = Constants.PRECISION / 3; - _distributionX[2] = Constants.PRECISION / 3; - uint256[] memory _distributionY = new uint256[](3); - - usdc.mint(address(pair), amount0In); - weth.mint(address(pair), amount1In); - - vm.expectRevert(abi.encodeWithSelector(LBPair__CompositionFactorFlawed.selector, _ids[0])); - pair.mint(_ids, _distributionX, _distributionY, ALICE); - - _distributionX[2] = 0; - _distributionX[1] = 0; - _distributionX[0] = 0; - _distributionY[0] = Constants.PRECISION / 3; - _distributionY[1] = Constants.PRECISION / 3; - _distributionY[2] = Constants.PRECISION / 3; - - vm.expectRevert(abi.encodeWithSelector(LBPair__CompositionFactorFlawed.selector, _ids[2])); - pair.mint(_ids, _distributionX, _distributionY, ALICE); - - uint256[] memory _ids2 = new uint256[](1); - uint256[] memory _distributionX2 = new uint256[](1); - uint256[] memory _distributionY2 = new uint256[](1); - - _ids2[0] = activeId - 1; - _distributionX2[0] = Constants.PRECISION; - vm.expectRevert(abi.encodeWithSelector(LBPair__CompositionFactorFlawed.selector, _ids2[0])); - pair.mint(_ids2, _distributionX2, _distributionY2, ALICE); - - _ids2[0] = activeId + 1; - _distributionY2[0] = Constants.PRECISION; - _distributionX2[0] = 0; - vm.expectRevert(abi.encodeWithSelector(LBPair__CompositionFactorFlawed.selector, _ids2[0])); - pair.mint(_ids2, _distributionX2, _distributionY2, ALICE); - } - - function testInsufficientLiquidityMinted() public { - uint24 _numberBins = 5; - uint24 startId = ID_ONE; - uint256 amount0In = 3e12; - uint256 amount1In = 3e12; - pair = createLBPairDefaultFees(usdc, weth); - - addLiquidity(amount1In, startId, _numberBins, 0); - - (uint256 reserveX, uint256 reserveY, uint256 activeId) = pair.getReservesAndId(); - - uint256[] memory _ids = new uint256[](3); - _ids[0] = activeId - 1; - _ids[1] = activeId; - _ids[2] = activeId + 1; - uint256[] memory _distributionX = new uint256[](3); - _distributionX[0] = 0; - _distributionX[1] = Constants.PRECISION / 3; - _distributionX[2] = Constants.PRECISION / 3; - uint256[] memory _distributionY = new uint256[](3); - - usdc.mint(address(pair), amount0In); - weth.mint(address(pair), amount1In); - - vm.expectRevert(abi.encodeWithSelector(LBPair__InsufficientLiquidityMinted.selector, _ids[0])); - pair.mint(_ids, _distributionX, _distributionY, ALICE); - - _distributionX[2] = 0; - _distributionX[1] = 0; - _distributionX[0] = 0; - _distributionY[0] = Constants.PRECISION / 3; - _distributionY[1] = Constants.PRECISION / 3; - _distributionY[2] = 0; - - vm.expectRevert(abi.encodeWithSelector(LBPair__InsufficientLiquidityMinted.selector, _ids[2])); - pair.mint(_ids, _distributionX, _distributionY, ALICE); - } -} diff --git a/test_old/LBPair.Oracle.t.sol b/test_old/LBPair.Oracle.t.sol deleted file mode 100644 index 2771d4fe..00000000 --- a/test_old/LBPair.Oracle.t.sol +++ /dev/null @@ -1,247 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; -import "src/libraries/Oracle.sol"; - -contract LiquidityBinPairOracleTest is TestHelper { - function setUp() public override { - usdc = new ERC20Mock(6); - weth = new ERC20Mock(18); - - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - addAllAssetsToQuoteWhitelist(factory); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - - router = new LBRouter(ILBFactory(DEV), IJoeFactory(DEV), IWAVAX(DEV)); - - pair = createLBPairDefaultFees(usdc, weth); - } - - function testVerifyOracleInitialParams() public { - ( - uint256 oracleSampleLifetime, - uint256 oracleSize, - uint256 oracleActiveSize, - uint256 oracleLastTimestamp, - uint256 oracleId, - uint256 min, - uint256 max - ) = pair.getOracleParameters(); - - assertEq(oracleSampleLifetime, 120); - assertEq(oracleSize, 2); - assertEq(oracleActiveSize, 0); - assertEq(oracleLastTimestamp, 0); - assertEq(oracleId, 0); - assertEq(min, 0); - assertEq(max, 0); - } - - function testIncreaseOracleLength() public { - ( - uint256 oracleSampleLifetime, - uint256 oracleSize, - uint256 oracleActiveSize, - uint256 oracleLastTimestamp, - uint256 oracleId, - uint256 min, - uint256 max - ) = pair.getOracleParameters(); - - pair.increaseOracleLength(100); - - ( - uint256 newOracleSampleLifetime, - uint256 newOracleSize, - uint256 newOracleActiveSize, - uint256 newOracleLastTimestamp, - uint256 newOracleId, - uint256 newMin, - uint256 newMax - ) = pair.getOracleParameters(); - - assertEq(newOracleSampleLifetime, oracleSampleLifetime); - assertEq(newOracleSize, 100); - assertEq(newOracleActiveSize, oracleActiveSize); - assertEq(newOracleLastTimestamp, oracleLastTimestamp); - assertEq(newOracleId, oracleId); - assertEq(newMin, min); - assertEq(newMax, max); - } - - function testOracleSampleFromEdgeCases() public { - uint256 tokenAmount = 100e18; - weth.mint(address(pair), tokenAmount); - - uint256[] memory _ids = new uint256[](1); - _ids[0] = ID_ONE; - - uint256[] memory _liquidities = new uint256[](1); - _liquidities[0] = Constants.PRECISION; - - pair.mint(_ids, new uint256[](1), _liquidities, DEV); - - usdc.mint(address(pair), 5e18); - - vm.expectRevert(Oracle__NotInitialized.selector); - pair.getOracleSampleFrom(0); - - vm.warp(10_000); - - pair.swap(true, DEV); - - vm.expectRevert(abi.encodeWithSelector(Oracle__LookUpTimestampTooOld.selector, 10_000, 9_000)); - pair.getOracleSampleFrom(1_000); - } - - function testOracleSampleFromWith2Samples() public { - uint256 tokenAmount = 100e18; - weth.mint(address(pair), tokenAmount); - - uint256[] memory _ids = new uint256[](1); - _ids[0] = ID_ONE; - - uint256[] memory _liquidities = new uint256[](1); - _liquidities[0] = Constants.PRECISION; - - pair.mint(_ids, new uint256[](1), _liquidities, DEV); - - usdc.mint(address(pair), 5e18); - - pair.swap(true, DEV); - - vm.warp(block.timestamp + 250); - - usdc.mint(address(pair), 5e18); - - pair.swap(true, DEV); - - uint256 _ago = 130; - uint256 _time = block.timestamp - _ago; - - (uint256 cumulativeId, uint256 cumulativeVolatilityAccumulated, uint256 cumulativeBinCrossed) = - pair.getOracleSampleFrom(_ago); - assertEq(cumulativeId / _time, ID_ONE); - assertEq(cumulativeVolatilityAccumulated, 0); - assertEq(cumulativeBinCrossed, 0); - } - - function testOracleSampleFromWith100Samples() public { - uint256 amount1In = 200e18; - (uint256[] memory _ids, uint256[] memory _distributionX, uint256[] memory _distributionY, uint256 amount0In) = - spreadLiquidity(amount1In * 2, ID_ONE, 99, 100); - - usdc.mint(address(pair), amount0In); - weth.mint(address(pair), amount1In); - - pair.mint(_ids, _distributionX, _distributionY, DEV); - pair.increaseOracleLength(100); - - uint256 startTimestamp; - - for (uint256 i; i < 200; ++i) { - usdc.mint(address(pair), 1e18); - - vm.warp(1500 + 100 * i); - pair.swap(true, DEV); - - if (i == 1) startTimestamp = block.timestamp; - } - - (uint256 cId, uint256 cAcc, uint256 cBin) = pair.getOracleSampleFrom(0); - - for (uint256 i; i < 99; ++i) { - uint256 _ago = ((block.timestamp - startTimestamp) * i) / 100; - - (uint256 cumulativeId, uint256 cumulativeVolatilityAccumulated, uint256 cumulativeBinCrossed) = - pair.getOracleSampleFrom(_ago); - assertGe(cId, cumulativeId); - assertGe(cAcc, cumulativeVolatilityAccumulated); - assertGe(cBin, cumulativeBinCrossed); - - (cId, cAcc, cBin) = (cumulativeId, cumulativeVolatilityAccumulated, cumulativeBinCrossed); - } - } - - function testOracleSampleFromWith100SamplesNotAllInitialized() public { - uint256 amount1In = 101e18; - (uint256[] memory _ids, uint256[] memory _distributionX, uint256[] memory _distributionY, uint256 amount0In) = - spreadLiquidity(amount1In * 2, ID_ONE, 99, 100); - - usdc.mint(address(pair), amount0In); - weth.mint(address(pair), amount1In); - - pair.mint(_ids, _distributionX, _distributionY, DEV); - - uint256 startTimestamp; - - uint16 newSize = 2; - for (uint256 i; i < 50; ++i) { - usdc.mint(address(pair), 1e18); - - newSize += 2; - pair.increaseOracleLength(newSize); - - vm.warp(1500 + 100 * i); - pair.swap(true, DEV); - - if (i == 1) startTimestamp = block.timestamp; - } - - (uint256 cId, uint256 cAcc, uint256 cBin) = pair.getOracleSampleFrom(0); - - for (uint256 i; i < 49; ++i) { - uint256 _ago = ((block.timestamp - startTimestamp) * i) / 50; - - (uint256 cumulativeId, uint256 cumulativeVolatilityAccumulated, uint256 cumulativeBinCrossed) = - pair.getOracleSampleFrom(_ago); - assertGe(cId, cumulativeId); - assertGe(cAcc, cumulativeVolatilityAccumulated); - assertGe(cBin, cumulativeBinCrossed); - - (cId, cAcc, cBin) = (cumulativeId, cumulativeVolatilityAccumulated, cumulativeBinCrossed); - } - } - - function testTLowerThanTimestamp() public { - uint256 amountYInLiquidity = 100e18; - uint24 startId = ID_ONE; - - FeeHelper.FeeParameters memory _feeParameters = pair.feeParameters(); - addLiquidity(amountYInLiquidity, startId, 51, 5); - - (uint256 amountYInForSwap,) = router.getSwapIn(pair, amountYInLiquidity / 4, true); - usdc.mint(address(pair), amountYInForSwap); - vm.prank(ALICE); - pair.swap(true, ALICE); - - (uint256 cumulativeId, uint256 cumulativeVolatilityAccumulated, uint256 cumulativeBinCrossed) = - pair.getOracleSampleFrom(0); - - vm.warp(block.timestamp + 90); - (uint256 cumulativeIdAfter, uint256 cumulativeVolatilityAccumulatedAfter, uint256 cumulativeBinCrossedAfter) = - pair.getOracleSampleFrom(0); - - assertEq(cumulativeId * block.timestamp, cumulativeIdAfter); - assertLt(cumulativeVolatilityAccumulated, cumulativeVolatilityAccumulatedAfter); - assertEq(cumulativeBinCrossed, cumulativeBinCrossedAfter); - } - - function testTheSameOracleSizeReverts() public { - (, uint256 currentOracleSize,,,,,) = pair.getOracleParameters(); - vm.expectRevert( - abi.encodeWithSelector(LBPair__OracleNewSizeTooSmall.selector, currentOracleSize, currentOracleSize) - ); - pair.increaseOracleLength(uint16(currentOracleSize)); - pair.increaseOracleLength(uint16(currentOracleSize + 22)); - (, currentOracleSize,,,,,) = pair.getOracleParameters(); - vm.expectRevert( - abi.encodeWithSelector(LBPair__OracleNewSizeTooSmall.selector, currentOracleSize, currentOracleSize) - ); - pair.increaseOracleLength(uint16(currentOracleSize)); - } -} diff --git a/test_old/LBPair.Swaps.t.sol b/test_old/LBPair.Swaps.t.sol deleted file mode 100644 index 1ef6bf52..00000000 --- a/test_old/LBPair.Swaps.t.sol +++ /dev/null @@ -1,310 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; - -contract LiquidityBinPairSwapsTest is TestHelper { - function setUp() public override { - usdc = new ERC20Mock(6); - weth = new ERC20Mock(18); - - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - addAllAssetsToQuoteWhitelist(factory); - router = new LBRouter(ILBFactory(DEV), IJoeFactory(DEV), IWAVAX(DEV)); - - pair = createLBPairDefaultFees(usdc, weth); - } - - function testSwapInsufficientAmountReverts() public { - vm.expectRevert(LBPair__InsufficientAmounts.selector); - pair.swap(true, DEV); - vm.expectRevert(LBPair__InsufficientAmounts.selector); - pair.swap(false, DEV); - } - - function testSwapXtoYSingleBinFromGetSwapOut() public { - uint256 tokenAmount = 100e18; - weth.mint(address(pair), tokenAmount); - - uint256[] memory _ids = new uint256[](1); - _ids[0] = ID_ONE; - - uint256[] memory _liquidities = new uint256[](1); - _liquidities[0] = Constants.PRECISION; - - pair.mint(_ids, new uint256[](1), _liquidities, DEV); - - uint256 amountXIn = 1e12; - - (uint256 amountYOut,) = router.getSwapOut(pair, amountXIn, true); - - usdc.mint(address(pair), amountXIn); - - pair.swap(true, DEV); - - assertEq(usdc.balanceOf(DEV), 0); - assertEq(weth.balanceOf(DEV), amountYOut); - - (uint256 binReserveX, uint256 binReserveY) = pair.getBin(ID_ONE); - - (uint256 feesXTotal,,,) = pair.getGlobalFees(); - - assertEq(binReserveX, amountXIn - feesXTotal); - assertEq(binReserveY, tokenAmount - amountYOut); - } - - function testSwapYtoXSingleBinFromGetSwapOut() public { - uint256 tokenAmount = 100e18; - usdc.mint(address(pair), tokenAmount); - - uint256[] memory _ids = new uint256[](1); - _ids[0] = ID_ONE; - - // uint256 price = router.getPriceFromId(pair, uint24(_ids[0])); - - uint256[] memory _liquidities = new uint256[](1); - _liquidities[0] = Constants.PRECISION; - - pair.mint(_ids, _liquidities, new uint256[](1), DEV); - - uint256 amountYIn = 1e12; - - (uint256 amountXOut,) = router.getSwapOut(pair, amountYIn, false); - - weth.mint(address(pair), amountYIn); - - pair.swap(false, DEV); - - assertEq(usdc.balanceOf(DEV), amountXOut); - assertEq(weth.balanceOf(DEV), 0); - - (uint256 binReserveX, uint256 binReserveY) = pair.getBin(uint24(_ids[0])); - - (, uint256 feesYTotal,,) = pair.getGlobalFees(); - - assertEq(binReserveX, tokenAmount - amountXOut); - assertEq(binReserveY, amountYIn - feesYTotal); - } - - function testSwapXtoYSingleBinFromGetSwapIn() public { - uint256 tokenAmount = 100e18; - weth.mint(address(pair), tokenAmount); - - uint256[] memory _ids = new uint256[](1); - _ids[0] = ID_ONE; - - uint256[] memory _liquidities = new uint256[](1); - _liquidities[0] = Constants.PRECISION; - - pair.mint(_ids, new uint256[](1), _liquidities, DEV); - - uint256 amountYOut = 1e12; - - (uint256 amountXIn,) = router.getSwapIn(pair, amountYOut, true); - - usdc.mint(address(pair), amountXIn); - - pair.swap(true, DEV); - - assertEq(usdc.balanceOf(DEV), 0); - assertEq(weth.balanceOf(DEV), amountYOut); - - (uint256 binReserveX, uint256 binReserveY) = pair.getBin(ID_ONE); - - (uint256 feesXTotal,,,) = pair.getGlobalFees(); - - assertEq(binReserveX, amountXIn - feesXTotal); - assertEq(binReserveY, tokenAmount - amountYOut); - } - - function testSwapYtoXSingleBinFromGetSwapIn() public { - uint256 tokenAmount = 100e18; - usdc.mint(address(pair), tokenAmount); - - uint256[] memory _ids = new uint256[](1); - _ids[0] = ID_ONE + 1; - - uint256[] memory _liquidities = new uint256[](1); - _liquidities[0] = Constants.PRECISION; - - pair.mint(_ids, _liquidities, new uint256[](1), DEV); - - uint256 amountXOut = 1e12; - - (uint256 amountYIn,) = router.getSwapIn(pair, amountXOut, false); - - weth.mint(address(pair), amountYIn); - - pair.swap(false, DEV); - - assertEq(usdc.balanceOf(DEV), amountXOut); - assertEq(weth.balanceOf(DEV), 0); - - (uint256 binReserveX, uint256 binReserveY) = pair.getBin(uint24(_ids[0])); - - (, uint256 feesYTotal,,) = pair.getGlobalFees(); - - assertEq(binReserveX, tokenAmount - amountXOut); - assertEq(binReserveY, amountYIn - feesYTotal); - } - - function testSwapYtoXConsecutiveBinFromGetSwapIn() public { - uint256 amountYInLiquidity = 100e18; - uint256 amountXOutForSwap = 30e18; - uint24 startId = ID_ONE; - - addLiquidity(amountYInLiquidity, startId, 9, 0); - - (uint256 amountYInForSwap,) = router.getSwapIn(pair, amountXOutForSwap, false); - - weth.mint(address(pair), amountYInForSwap); - - pair.swap(false, ALICE); - - assertEq(usdc.balanceOf(ALICE), amountXOutForSwap); - } - - function testSwapXtoYConsecutiveBinFromGetSwapIn() public { - uint256 amountYInLiquidity = 100e18; - uint256 amountYOutForSwap = 30e18; - uint24 startId = ID_ONE; - - addLiquidity(amountYInLiquidity, startId, 9, 0); - - (uint256 amountXInForSwap,) = router.getSwapIn(pair, amountYOutForSwap, true); - - usdc.mint(address(pair), amountXInForSwap); - - pair.swap(true, ALICE); - - assertEq(weth.balanceOf(ALICE), amountYOutForSwap); - } - - function testSwapYtoXConsecutiveBinFromGetSwapOut() public { - uint256 amountYInLiquidity = 100e18; - uint256 amountYInForSwap = 30e18; - uint24 startId = ID_ONE; - - addLiquidity(amountYInLiquidity, startId, 9, 0); - - (uint256 amountXOutForSwap,) = router.getSwapOut(pair, amountYInForSwap, false); - - weth.mint(address(pair), amountYInForSwap); - - pair.swap(false, ALICE); - - assertEq(usdc.balanceOf(ALICE), amountXOutForSwap); - } - - function testSwapXtoYConsecutiveBinFromGetSwapOut() public { - uint256 amountYInLiquidity = 100e18; - uint256 amountXInForSwap = 30e18; - uint24 startId = ID_ONE; - - addLiquidity(amountYInLiquidity, startId, 9, 0); - - (uint256 amountYOutForSwap,) = router.getSwapOut(pair, amountXInForSwap, true); - - usdc.mint(address(pair), amountXInForSwap); - - pair.swap(true, ALICE); - - assertEq(weth.balanceOf(ALICE), amountYOutForSwap); - } - - function testSwapYtoXDistantBinsFromGetSwapIn() public { - uint256 amountYInLiquidity = 100e18; - uint256 amountXOutForSwap = 30e18; - uint24 startId = ID_ONE; - - addLiquidity(amountYInLiquidity, startId, 9, 100); - - (uint256 amountYInForSwap,) = router.getSwapIn(pair, amountXOutForSwap, false); - - weth.mint(address(pair), amountYInForSwap); - - pair.swap(false, ALICE); - - assertEq(usdc.balanceOf(ALICE), amountXOutForSwap); - } - - function testSwapXtoYDistantBinsFromGetSwapIn() public { - uint256 amountYInLiquidity = 100e18; - uint256 amountYOutForSwap = 30e18; - uint24 startId = ID_ONE; - - addLiquidity(amountYInLiquidity, startId, 9, 100); - - (uint256 amountXInForSwap,) = router.getSwapIn(pair, amountYOutForSwap, true); - - usdc.mint(address(pair), amountXInForSwap); - - pair.swap(true, ALICE); - - assertEq(weth.balanceOf(ALICE), amountYOutForSwap); - } - - function testSwapYtoXDistantBinsFromGetSwapOut() public { - uint256 amountYInLiquidity = 100e18; - uint256 amountYInForSwap = 30e18; - uint24 startId = ID_ONE; - - addLiquidity(amountYInLiquidity, startId, 9, 100); - - (uint256 amountXOutForSwap,) = router.getSwapOut(pair, amountYInForSwap, false); - - weth.mint(address(pair), amountYInForSwap); - - pair.swap(false, ALICE); - - assertEq(usdc.balanceOf(ALICE), amountXOutForSwap); - } - - function testSwapXtoYDistantBinsFromGetSwapOut() public { - uint256 amountYInLiquidity = 100e18; - uint256 amountXInForSwap = 30e18; - uint24 startId = ID_ONE; - - addLiquidity(amountYInLiquidity, startId, 9, 100); - - (uint256 amountYOutForSwap,) = router.getSwapOut(pair, amountXInForSwap, true); - - usdc.mint(address(pair), amountXInForSwap); - - pair.swap(true, ALICE); - - assertEq(weth.balanceOf(ALICE), amountYOutForSwap); - } - - function testInvalidTokenPathReverts() public { - uint256 _amountIn = 10e18; - uint256 _amountOutMinAVAX = 10e18; - uint256[] memory _pairBinSteps = new uint256[](1); - IERC20[] memory _tokenPath = new IERC20[](2); - _tokenPath[0] = usdc; - _tokenPath[1] = weth; - - vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, _tokenPath[1])); - router.swapExactTokensForAVAX(_amountIn, _amountOutMinAVAX, _pairBinSteps, _tokenPath, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, _tokenPath[1])); - router.swapTokensForExactAVAX(_amountIn, _amountOutMinAVAX, _pairBinSteps, _tokenPath, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, _tokenPath[1])); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - _amountIn, _amountOutMinAVAX, _pairBinSteps, _tokenPath, DEV, block.timestamp - ); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, _tokenPath[0])); - router.swapAVAXForExactTokens(_amountIn, _pairBinSteps, _tokenPath, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, _tokenPath[0])); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens( - _amountIn, _pairBinSteps, _tokenPath, DEV, block.timestamp - ); - } -} diff --git a/test_old/LBPair.t.sol b/test_old/LBPair.t.sol deleted file mode 100644 index fa0435a6..00000000 --- a/test_old/LBPair.t.sol +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; - -contract LiquidityBinPairTest is TestHelper { - ILBPair _LBPairImplementation; - - function setUp() public override { - usdc = new ERC20Mock(6); - weth = new ERC20Mock(18); - - factory = new LBFactory(DEV, 8e14); - _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - addAllAssetsToQuoteWhitelist(factory); - router = new LBRouter(ILBFactory(DEV), IJoeFactory(DEV), IWAVAX(DEV)); - - pair = createLBPairDefaultFees(usdc, weth); - } - - function testPendingFeesNotIncreasingReverts() public { - uint256[] memory _ids = new uint256[](2); - _ids[0] = uint256(ID_ONE); - _ids[1] = uint256(ID_ONE) - 1; - vm.expectRevert(LBPair__OnlyStrictlyIncreasingId.selector); - pair.pendingFees(DEV, _ids); - } - - function testMintWrongLengthsReverts() public { - uint256[] memory _ids; - uint256[] memory _distributionX; - uint256[] memory _distributionY; - uint256 _numberBins = 0; - _ids = new uint256[](_numberBins); - _distributionX = new uint256[](_numberBins); - _distributionY = new uint256[](_numberBins); - - vm.expectRevert(LBPair__WrongLengths.selector); - pair.mint(_ids, _distributionX, _distributionY, DEV); - _numberBins = 2; - _ids = new uint256[](_numberBins); - _distributionX = new uint256[](_numberBins - 1); - _distributionY = new uint256[](_numberBins); - vm.expectRevert(LBPair__WrongLengths.selector); - pair.mint(_ids, _distributionX, _distributionY, DEV); - _distributionX = new uint256[](_numberBins); - _distributionY = new uint256[](_numberBins - 1); - vm.expectRevert(LBPair__WrongLengths.selector); - pair.mint(_ids, _distributionX, _distributionY, DEV); - } - - function testBurnWrongLengthsReverts() public { - uint256[] memory ids; - uint256[] memory amounts; - uint256 _numberBins = 0; - ids = new uint256[](_numberBins); - amounts = new uint256[](_numberBins); - - vm.expectRevert(LBPair__WrongLengths.selector); - pair.burn(ids, amounts, DEV); - _numberBins = 2; - ids = new uint256[](_numberBins); - amounts = new uint256[](_numberBins - 1); - vm.expectRevert(LBPair__WrongLengths.selector); - pair.burn(ids, amounts, DEV); - } - - function testDistributionOverflowReverts() public { - uint256 amount = 10e18; - usdc.mint(address(pair), amount); - weth.mint(address(pair), amount); - uint256[] memory _ids; - uint256[] memory _distributionX; - uint256[] memory _distributionY; - uint256 _numberBins = 1; - _ids = new uint256[](_numberBins); - _distributionX = new uint256[](_numberBins); - _distributionY = new uint256[](_numberBins); - - _ids[0] = ID_ONE; - - _distributionY = new uint256[](_numberBins); - _distributionX[0] = Constants.PRECISION + 1; - vm.expectRevert(LBPair__DistributionsOverflow.selector); - pair.mint(_ids, _distributionX, _distributionY, DEV); - - _distributionX[0] = 0; - _distributionY[0] = Constants.PRECISION + 1; - vm.expectRevert(LBPair__DistributionsOverflow.selector); - pair.mint(_ids, _distributionX, _distributionY, DEV); - - _numberBins = 2; - _ids = new uint256[](_numberBins); - _ids[0] = ID_ONE; - _ids[1] = ID_ONE + 1; - _distributionX = new uint256[](_numberBins); - _distributionY = new uint256[](_numberBins); - _distributionX[0] = Constants.PRECISION / 2; - _distributionX[1] = Constants.PRECISION / 2 + 1; - vm.expectRevert(LBPair__DistributionsOverflow.selector); - pair.mint(_ids, _distributionX, _distributionY, DEV); - - _ids[0] = ID_ONE - 1; - _ids[1] = ID_ONE; - _distributionX[0] = 0; - _distributionX[1] = 0; - _distributionY[0] = Constants.PRECISION / 2; - _distributionY[1] = Constants.PRECISION / 2 + 1; - vm.expectRevert(LBPair__DistributionsOverflow.selector); - pair.mint(_ids, _distributionX, _distributionY, DEV); - } - - function testInsufficientLiquidityBurnedReverts() public { - uint256 _numberBins = 2; - uint256[] memory _ids; - uint256[] memory _amounts; - _ids = new uint256[](_numberBins); - _amounts = new uint256[](_numberBins); - _ids[0] = ID_ONE; - _ids[1] = ID_ONE + 1; - _amounts[0] = 0; - _amounts[1] = 0; - vm.expectRevert(abi.encodeWithSelector(LBPair__InsufficientLiquidityBurned.selector, _ids[0])); - pair.burn(_ids, _amounts, DEV); - } - - function testCollectingFeesOnlyFeeRecipient() public { - vm.prank(ALICE); - vm.expectRevert(abi.encodeWithSelector(LBPair__OnlyFeeRecipient.selector, DEV, ALICE)); - pair.collectProtocolFees(); - } - - function testDeployingPairWithoutChecks() public { - uint24 _activeId = ID_ONE; - uint16 _sampleLifetime; - bytes32 _packedFeeParameters; - vm.startPrank(address(factory)); - - vm.expectRevert(LBPair__AddressZero.selector); - _LBPairImplementation.initialize(IERC20(address(0)), weth, _activeId, _sampleLifetime, _packedFeeParameters); - vm.expectRevert(LBPair__AddressZero.selector); - _LBPairImplementation.initialize(weth, IERC20(address(0)), _activeId, _sampleLifetime, _packedFeeParameters); - - _LBPairImplementation.initialize(weth, usdc, _activeId, _sampleLifetime, _packedFeeParameters); - vm.expectRevert(LBPair__AlreadyInitialized.selector); - _LBPairImplementation.initialize(weth, usdc, _activeId, _sampleLifetime, _packedFeeParameters); - } -} diff --git a/test_old/LBQuoter.t.sol b/test_old/LBQuoter.t.sol deleted file mode 100644 index 4e5fa9ca..00000000 --- a/test_old/LBQuoter.t.sol +++ /dev/null @@ -1,227 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "../helpers/TestHelper.sol"; -import {Addresses} from "./Addresses.sol"; - -contract _LiquidityBinQuoterTest is TestHelper { - using Math512Bits for uint256; - - IJoeFactory private factoryV1; - ERC20Mock private testWavax; - IJoeRouter02 internal routerV1; - - uint256 private defaultBaseFee = DEFAULT_BIN_STEP * uint256(DEFAULT_BASE_FACTOR) * 1e10; - - function setUp() public override { - vm.createSelectFork(vm.rpcUrl("avalanche"), 19_358_000); - - usdc = new ERC20Mock(6); - usdt = new ERC20Mock(6); - testWavax = new ERC20Mock(18); - - routerV1 = IJoeRouter02(Addresses.JOE_V1_ROUTER_ADDRESS); - - factoryV1 = IJoeFactory(Addresses.JOE_V1_FACTORY_ADDRESS); - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - addAllAssetsToQuoteWhitelist(factory); - factory.addQuoteAsset(testWavax); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - - router = - new LBRouter(factory, IJoeFactory(Addresses.JOE_V1_FACTORY_ADDRESS), IWAVAX(Addresses.WAVAX_AVALANCHE_ADDRESS)); - quoter = new LBQuoter(address(router), Addresses.JOE_V1_FACTORY_ADDRESS, address(factory)); - - // Minting and giving approval - testWavax.mint(DEV, 1_000_000e18); - testWavax.approve(address(routerV1), type(uint256).max); - usdc.mint(DEV, 1_000_000e6); - usdc.approve(address(routerV1), type(uint256).max); - usdt.mint(DEV, 1_000_000e6); - usdt.approve(address(routerV1), type(uint256).max); - - // Create pairs - factoryV1.createPair(address(testWavax), address(usdc)); - factoryV1.createPair(address(usdt), address(usdc)); - createLBPairDefaultFees(usdt, usdc); - createLBPairDefaultFeesFromStartId(testWavax, usdc, convertIdAvaxToUSD(DEFAULT_BIN_STEP)); - } - - function testConstructor() public { - assertEq(address(quoter.routerV2()), address(router)); - assertEq(address(quoter.factoryV1()), Addresses.JOE_V1_FACTORY_ADDRESS); - assertEq(address(quoter.factoryV2()), address(factory)); - } - - function testGetSwapOutOnV2Pair() public { - addLiquidityFromRouter(testWavax, usdc, 20_000e6, convertIdAvaxToUSD(DEFAULT_BIN_STEP), 9, 2, DEFAULT_BIN_STEP); - - address[] memory route; - route = new address[](2); - route[0] = address(testWavax); - route[1] = address(usdc); - - LBQuoter.Quote memory quote = quoter.findBestPathFromAmountIn(route, 1e18); - - assertApproxEqAbs(quote.amounts[1], 20e6, 2e6, "Price of 1 AVAX should be approx 20 USDC"); - assertEq(quote.binSteps[0], DEFAULT_BIN_STEP); - assertApproxEqAbs(quote.fees[0], defaultBaseFee, 1e14); - // The tested swap stays in the current active bin, so the slippage is zero - assertApproxEqAbs(quote.amounts[0], quote.virtualAmountsWithoutSlippage[0], 1); - } - - function testGetSwapInOnV2Pair() public { - addLiquidityFromRouter(testWavax, usdc, 20_000e6, convertIdAvaxToUSD(DEFAULT_BIN_STEP), 9, 2, DEFAULT_BIN_STEP); - - address[] memory route; - route = new address[](2); - route[0] = address(testWavax); - route[1] = address(usdc); - - LBQuoter.Quote memory quote = quoter.findBestPathFromAmountOut(route, 20e6); - - assertApproxEqAbs(quote.amounts[0], 1e18, 0.1e18, "Price of 1 AVAX should be approx 20 USDT"); - assertEq(quote.binSteps[0], DEFAULT_BIN_STEP); - assertApproxEqAbs(quote.fees[0], defaultBaseFee, 1e14); - // The tested swap stays in the current active bin, so the slippage is zero - assertApproxEqAbs(quote.amounts[0], quote.virtualAmountsWithoutSlippage[0], 1); - } - - function testGetSwapOutOnV1Pair() public { - uint256 amountIn = 1e18; - routerV1.addLiquidity(address(testWavax), address(usdc), 1_000e18, 20_000e6, 0, 0, DEV, block.timestamp); - - address[] memory route; - route = new address[](2); - route[0] = address(testWavax); - route[1] = address(usdc); - - LBQuoter.Quote memory quote = quoter.findBestPathFromAmountIn(route, amountIn); - - assertApproxEqAbs(quote.amounts[1], 20e6, 2e6, "Price of 1 AVAX should be approx 20 USDC"); - assertEq(quote.binSteps[0], 0); - assertEq(quote.fees[0], 0.003e18); - - uint256 quoteAmountOut = JoeLibrary.quote(amountIn, 1_000e18, 20_000e6); - uint256 feePaid = (quoteAmountOut * quote.fees[0]) / 1e18; - assertEq(quote.virtualAmountsWithoutSlippage[1], quoteAmountOut - feePaid); - } - - function testGetSwapInOnV1Pair() public { - uint256 amountOut = 20e6; - routerV1.addLiquidity(address(testWavax), address(usdc), 1_000e18, 20_000e6, 0, 0, DEV, block.timestamp); - - address[] memory route; - route = new address[](2); - route[0] = address(testWavax); - route[1] = address(usdc); - - LBQuoter.Quote memory quote = quoter.findBestPathFromAmountOut(route, amountOut); - - assertApproxEqAbs(quote.amounts[0], 1e18, 0.1e18, "Price of 1 AVAX should be approx 20 USDT"); - assertEq(quote.binSteps[0], 0); - assertEq(quote.fees[0], 0.003e18); - - // Fees are expressed in the In token - // To get the theorical amountIn without slippage but with the fees, the calculation is amountIn = amountOut / price / (1 - fees) - uint256 quoteAmountInWithFees = - JoeLibrary.quote((amountOut * 1e18) / (1e18 - quote.fees[0]), 20_000e6, 1_000e18); - assertApproxEqAbs(quote.virtualAmountsWithoutSlippage[0], quoteAmountInWithFees, 1e12); - } - - function testGetSwapOutOnComplexRoute() public { - routerV1.addLiquidity(address(testWavax), address(usdc), 1_000e18, 20_000e6, 0, 0, DEV, block.timestamp); - addLiquidityFromRouter(usdt, usdc, 10_000e6, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - - address[] memory route; - route = new address[](3); - route[0] = address(testWavax); - route[1] = address(usdc); - route[2] = address(usdt); - - LBQuoter.Quote memory quote = quoter.findBestPathFromAmountIn(route, 1e18); - - assertApproxEqAbs(quote.amounts[2], 20e6, 2e6, "Price of 1 AVAX should be approx 20 USDT"); - assertEq(quote.binSteps[0], 0); - assertEq(quote.binSteps[1], DEFAULT_BIN_STEP); - assertEq(quote.fees[0], 0.003e18); - assertApproxEqAbs(quote.fees[1], defaultBaseFee, 1e14); - } - - function testGetSwapInOnComplexRoute() public { - routerV1.addLiquidity(address(testWavax), address(usdc), 1_000e18, 20_000e6, 0, 0, DEV, block.timestamp); - addLiquidityFromRouter(usdt, usdc, 10_000e6, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - - address[] memory route; - route = new address[](3); - route[0] = address(testWavax); - route[1] = address(usdc); - route[2] = address(usdt); - - LBQuoter.Quote memory quote = quoter.findBestPathFromAmountOut(route, 20e6); - - assertApproxEqAbs(quote.amounts[0], 1e18, 0.1e18, "Price of 1 AVAX should be approx 20 USDT"); - - assertEq(quote.binSteps[0], 0); - assertEq(quote.binSteps[1], DEFAULT_BIN_STEP); - assertEq(quote.fees[0], 0.003e18); - assertApproxEqAbs(quote.fees[1], defaultBaseFee, 1e14); - } - - function testGetSwapInWithMultipleChoices() public { - // On V1, 1 AVAX = 19 USDC - routerV1.addLiquidity(address(testWavax), address(usdc), 1_000e18, 19_000e6, 0, 0, DEV, block.timestamp); - // On V2, 1 AVAX = 20 USDC - addLiquidityFromRouter(testWavax, usdc, 20_000e6, convertIdAvaxToUSD(DEFAULT_BIN_STEP), 9, 2, DEFAULT_BIN_STEP); - - address[] memory route; - route = new address[](2); - route[0] = address(testWavax); - route[1] = address(usdc); - - LBQuoter.Quote memory quote = quoter.findBestPathFromAmountOut(route, 20e6); - - assertApproxEqAbs(quote.amounts[0], 1e18, 0.1e18, "Price of 1 AVAX should be approx 20 USDC"); - assertEq(quote.binSteps[0], DEFAULT_BIN_STEP, "LBPair should be picked as it has the best price"); - } - - function testGetSwapOutWithMultipleChoices() public { - // On V1, 1 AVAX = 20 USDT - routerV1.addLiquidity(address(testWavax), address(usdt), 1_000e18, 20_000e6, 0, 0, DEV, block.timestamp); - // On V2, 1 AVAX = 19 USDT - uint24 desiredId = getIdFromPrice(uint256(19e6).shiftDivRoundDown(128, 1e18)); - createLBPairDefaultFeesFromStartId(testWavax, usdt, desiredId); - addLiquidityFromRouter(testWavax, usdt, 20_000e6, desiredId, 9, 2, DEFAULT_BIN_STEP); - - address[] memory route; - route = new address[](2); - route[0] = address(testWavax); - route[1] = address(usdt); - - LBQuoter.Quote memory quote = quoter.findBestPathFromAmountIn(route, 1e18); - - assertApproxEqAbs(quote.amounts[1], 20e6, 2e6, "Price of 1 AVAX should be approx 20 USDT"); - assertEq(quote.binSteps[0], 0, "V1 pair should be picked as it has the best price"); - } - - function testInvalidLength() public { - address[] memory route; - route = new address[](1); - vm.expectRevert(LBQuoter_InvalidLength.selector); - quoter.findBestPathFromAmountIn(route, 1e18); - vm.expectRevert(LBQuoter_InvalidLength.selector); - quoter.findBestPathFromAmountOut(route, 20e6); - } - - function convertIdAvaxToUSD(uint16 _binStep) internal pure returns (uint24 id) { - uint256 price = uint256(20e6).shiftDivRoundDown(128, 1e18); - id = getIdFromPrice(price, _binStep); - } - - function getIdFromPrice(uint256 _price, uint16 _binStep) internal pure returns (uint24 id) { - id = BinHelper.getIdFromPrice(_price, _binStep); - } -} diff --git a/test_old/LBRouter.FeesOnLiquidityAdd.t.sol b/test_old/LBRouter.FeesOnLiquidityAdd.t.sol deleted file mode 100644 index e5ffcac5..00000000 --- a/test_old/LBRouter.FeesOnLiquidityAdd.t.sol +++ /dev/null @@ -1,238 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; - -contract LiquidityBinRouterTest is TestHelper { - event AVAXreceived(); - - function setUp() public override { - usdc = new ERC20Mock(6); - weth = new ERC20Mock(18); - wavax = new WAVAX(); - uint16 binStep = 100; - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - addAllAssetsToQuoteWhitelist(factory); - factory.setPreset( - binStep, - uint16(Constants.BASIS_POINT_MAX), - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED / 6, - DEFAULT_SAMPLE_LIFETIME - ); - - router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(wavax))); - - pair = LBPair(address(factory.createLBPair(usdc, weth, ID_ONE, 100))); - } - - function testFeeOnActiveBin() public { - //setup pool with only Y liquidity - uint16 binStep = 100; - uint256 _amountYIn = 100e18; - uint24 _numberBins = 1; - int256[] memory _deltaIds; - uint256[] memory _distributionX; - uint256[] memory _distributionY; - _deltaIds = new int256[](_numberBins); - _distributionX = new uint256[](_numberBins); - _distributionY = new uint256[](_numberBins); - _deltaIds[0] = 0; - _distributionX[0] = 0; - _distributionY[0] = Constants.PRECISION; - - vm.prank(BOB); - weth.approve(address(router), _amountYIn); - - weth.mint(BOB, _amountYIn); - - ILBRouter.LiquidityParameters memory _liquidityParameters = ILBRouter.LiquidityParameters( - usdc, - weth, - binStep, - 0, - _amountYIn, - 0, - _amountYIn, - ID_ONE, - ID_ONE, - _deltaIds, - _distributionX, - _distributionY, - BOB, - block.timestamp - ); - vm.prank(BOB); - router.addLiquidity(_liquidityParameters); - - //setup liquidity add with only tokenX - uint256 amountXIn = 100e18; - _distributionX[0] = Constants.PRECISION; - _distributionY[0] = 0; - - usdc.mint(ALICE, amountXIn); - vm.prank(ALICE); - usdc.approve(address(router), amountXIn); - - uint256 feesXTotal; - (feesXTotal,,,) = pair.getGlobalFees(); - assertEq(feesXTotal, 0); - - _liquidityParameters = ILBRouter.LiquidityParameters( - usdc, - weth, - binStep, - amountXIn, - 0, - 0, - 0, - ID_ONE, - ID_ONE, - _deltaIds, - _distributionX, - _distributionY, - ALICE, - block.timestamp - ); - - vm.prank(ALICE); - router.addLiquidity(_liquidityParameters); - - uint256[] memory amounts = new uint256[](_numberBins); - uint256[] memory ids = new uint256[](_numberBins); - for (uint256 i; i < _numberBins; i++) { - ids[i] = uint256(int256(uint256(ID_ONE)) + _deltaIds[i]); - amounts[i] = pair.balanceOf(ALICE, ids[i]); - } - - vm.prank(ALICE); - pair.setApprovalForAll(address(router), true); - vm.prank(ALICE); - router.removeLiquidity(usdc, weth, binStep, 0, 0, ids, amounts, ALICE, block.timestamp); - - (feesXTotal,,,) = pair.getGlobalFees(); - assertGt(feesXTotal, amountXIn / 199); - - //remove BOB's liquidity to ALICE account - for (uint256 i; i < _numberBins; i++) { - ids[i] = uint256(int256(uint256(ID_ONE)) + _deltaIds[i]); - amounts[i] = pair.balanceOf(BOB, ids[i]); - } - vm.prank(BOB); - pair.setApprovalForAll(address(router), true); - vm.prank(BOB); - router.removeLiquidity(usdc, weth, binStep, 0, 0, ids, amounts, ALICE, block.timestamp); - - uint256 ALICE6DbalanceAfterSecondRemove = usdc.balanceOf(ALICE); - - assertEq(ALICE6DbalanceAfterSecondRemove + feesXTotal, amountXIn); - } - - function testFeeOnActiveBinReverse() public { - //setup pool with only X liquidity - uint16 binStep = 100; - uint256 _amountXIn = 100e18; - uint24 _numberBins = 1; - int256[] memory _deltaIds; - uint256[] memory _distributionX; - uint256[] memory _distributionY; - _deltaIds = new int256[](_numberBins); - _distributionX = new uint256[](_numberBins); - _distributionY = new uint256[](_numberBins); - _deltaIds[0] = 0; - _distributionX[0] = Constants.PRECISION; - _distributionY[0] = 0; - - vm.prank(BOB); - usdc.approve(address(router), _amountXIn); - - usdc.mint(BOB, _amountXIn); - - ILBRouter.LiquidityParameters memory _liquidityParameters = ILBRouter.LiquidityParameters( - usdc, - weth, - binStep, - _amountXIn, - 0, - _amountXIn, - 0, - ID_ONE, - ID_ONE, - _deltaIds, - _distributionX, - _distributionY, - BOB, - block.timestamp - ); - vm.prank(BOB); - router.addLiquidity(_liquidityParameters); - - //setup liquidity add with only tokenX - uint256 amountYIn = 100e18; - _distributionX[0] = 0; - _distributionY[0] = Constants.PRECISION; - - weth.mint(ALICE, amountYIn); - vm.prank(ALICE); - weth.approve(address(router), amountYIn); - - uint256 feesYTotal; - (, feesYTotal,,) = pair.getGlobalFees(); - assertEq(feesYTotal, 0); - - _liquidityParameters = ILBRouter.LiquidityParameters( - usdc, - weth, - binStep, - 0, - amountYIn, - 0, - 0, - ID_ONE, - ID_ONE, - _deltaIds, - _distributionX, - _distributionY, - ALICE, - block.timestamp - ); - - vm.prank(ALICE); - router.addLiquidity(_liquidityParameters); - - uint256[] memory amounts = new uint256[](_numberBins); - uint256[] memory ids = new uint256[](_numberBins); - for (uint256 i; i < _numberBins; i++) { - ids[i] = uint256(int256(uint256(ID_ONE)) + _deltaIds[i]); - amounts[i] = pair.balanceOf(ALICE, ids[i]); - } - - vm.prank(ALICE); - pair.setApprovalForAll(address(router), true); - vm.prank(ALICE); - router.removeLiquidity(usdc, weth, binStep, 0, 0, ids, amounts, ALICE, block.timestamp); - - //remove BOB's liquidity to ALICE account - for (uint256 i; i < _numberBins; i++) { - ids[i] = uint256(int256(uint256(ID_ONE)) + _deltaIds[i]); - amounts[i] = pair.balanceOf(BOB, ids[i]); - } - vm.prank(BOB); - pair.setApprovalForAll(address(router), true); - vm.prank(BOB); - router.removeLiquidity(usdc, weth, binStep, 0, 0, ids, amounts, ALICE, block.timestamp); - - uint256 ALICE6DbalanceAfterSecondRemove = weth.balanceOf(ALICE); - - (, feesYTotal,,) = pair.getGlobalFees(); - assertGt(feesYTotal, amountYIn / 199); - assertEq(ALICE6DbalanceAfterSecondRemove + feesYTotal, amountYIn); - } -} diff --git a/test_old/LBRouter.Fork.Swaps.t.sol b/test_old/LBRouter.Fork.Swaps.t.sol deleted file mode 100644 index 150c7b5e..00000000 --- a/test_old/LBRouter.Fork.Swaps.t.sol +++ /dev/null @@ -1,268 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; - -import {Addresses} from "./Addresses.sol"; - -contract LiquidityBinRouterForkTest is TestHelper { - LBPair internal pair0; - LBPair internal pair1; - LBPair internal pair2; - LBPair internal pair3; - LBPair internal taxTokenPair; - - function setUp() public override { - vm.createSelectFork(vm.rpcUrl("avalanche"), 19_358_000); - super.setUp(); - // usdc = new ERC20Mock(6); - // link = new ERC20Mock(10); - // wbtc = new ERC20Mock(12); - // weth = new ERC20Mock(18); - // wbtc = new ERC20Mock(24); - - // taxToken = new ERC20TransferTaxMock(); - - wavax = WAVAX(Addresses.WAVAX_AVALANCHE_ADDRESS); - usdc = ERC20Mock(Addresses.USDC_AVALANCHE_ADDRESS); - - // factory = new LBFactory(DEV, 8e14); - factory.addQuoteAsset(IERC20(address(wavax))); - factory.addQuoteAsset(IERC20(address(usdc))); - // ILBPair _LBPairImplementation = new LBPair(factory); - // factory.setLBPairImplementation(address(_LBPairImplementation)); - // addAllAssetsToQuoteWhitelist(factory); - // setDefaultFactoryPresets(DEFAULT_BIN_STEP); - - router = new LBRouter(factory, IJoeFactory(Addresses.JOE_V1_FACTORY_ADDRESS), IWAVAX(address(wavax))); - - pair = createLBPairDefaultFees(usdc, weth); - addLiquidityFromRouter(usdc, weth, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - - pair0 = createLBPairDefaultFees(usdc, link); - addLiquidityFromRouter(usdc, link, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - pair1 = createLBPairDefaultFees(link, wbtc); - addLiquidityFromRouter(link, wbtc, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - pair2 = createLBPairDefaultFees(wbtc, weth); - addLiquidityFromRouter(wbtc, weth, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - pair3 = createLBPairDefaultFees(weth, bnb); - addLiquidityFromRouter(weth, bnb, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - - taxTokenPair = createLBPairDefaultFees(taxToken, wavax); - addLiquidityFromRouter( - ERC20Mock(address(taxToken)), ERC20Mock(address(wavax)), 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP - ); - - pairWavax = createLBPairDefaultFees(usdc, wavax); - addLiquidityFromRouter(usdc, ERC20Mock(address(wavax)), 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - } - - function testSwapExactTokensForTokensMultiplePairsWithV1() public { - if (block.number < 1000) { - console.log("fork mainnet for V1 testing support"); - return; - } - - uint256 amountIn = 1e18; - - deal(address(usdc), DEV, amountIn); - - usdc.approve(address(router), amountIn); - - IERC20[] memory tokenList; - uint256[] memory pairVersions; - - tokenList = new IERC20[](3); - tokenList[0] = usdc; - tokenList[1] = wavax; - tokenList[2] = usdc; - - pairVersions = new uint256[](2); - pairVersions[0] = DEFAULT_BIN_STEP; - pairVersions[1] = 0; - - router.swapExactTokensForTokens(amountIn, 0, pairVersions, tokenList, DEV, block.timestamp); - - assertGt(usdc.balanceOf(DEV), 0); - - tokenList[0] = usdc; - tokenList[1] = wavax; - tokenList[2] = usdc; - - pairVersions[0] = 0; - pairVersions[1] = DEFAULT_BIN_STEP; - - uint256 balanceBefore = usdc.balanceOf(DEV); - - usdc.approve(address(router), usdc.balanceOf(DEV)); - router.swapExactTokensForTokens(usdc.balanceOf(DEV), 0, pairVersions, tokenList, DEV, block.timestamp); - - assertGt(usdc.balanceOf(DEV) - balanceBefore, 0); - } - - function testSwapTokensForExactTokensMultiplePairsWithV1() public { - if (block.number < 1000) { - console.log("fork mainnet for V1 testing support"); - return; - } - - uint256 amountOut = 1e6; - - deal(address(usdc), DEV, 1e6); - - usdc.approve(address(router), 100e18); - - IERC20[] memory tokenList; - uint256[] memory pairVersions; - - tokenList = new IERC20[](3); - tokenList[0] = usdc; - tokenList[1] = wavax; - tokenList[2] = usdc; - - pairVersions = new uint256[](2); - pairVersions[0] = DEFAULT_BIN_STEP; - pairVersions[1] = 0; - - router.swapTokensForExactTokens(amountOut, 100e18, pairVersions, tokenList, DEV, block.timestamp); - - assertEq(usdc.balanceOf(DEV), amountOut); - - tokenList[0] = usdc; - tokenList[1] = wavax; - tokenList[2] = usdc; - - pairVersions[0] = 0; - pairVersions[1] = DEFAULT_BIN_STEP; - - uint256 balanceBefore = usdc.balanceOf(DEV); - - usdc.approve(address(router), usdc.balanceOf(DEV)); - router.swapTokensForExactTokens(amountOut, usdc.balanceOf(DEV), pairVersions, tokenList, DEV, block.timestamp); - - assertEq(usdc.balanceOf(DEV) - balanceBefore, amountOut); - - vm.stopPrank(); - } - - function testTaxTokenSwappedOnV1Pairs() public { - if (block.number < 1000) { - console.log("fork mainnet for V1 testing support"); - return; - } - uint256 amountIn = 100e18; - - IJoeFactory factoryv1 = IJoeFactory(Addresses.JOE_V1_FACTORY_ADDRESS); - //create taxToken-AVAX pair in DEXv1 - address taxPairv11 = factoryv1.createPair(address(taxToken), address(wavax)); - taxToken.mint(taxPairv11, amountIn); - vm.deal(DEV, amountIn); - wavax.deposit{value: amountIn}(); - wavax.transfer(taxPairv11, amountIn); - IJoePair(taxPairv11).mint(DEV); - - //create taxToken-usdc pair in DEXv1 - address taxPairv12 = factoryv1.createPair(address(taxToken), address(usdc)); - taxToken.mint(taxPairv12, amountIn); - usdc.mint(taxPairv12, amountIn); - IJoePair(taxPairv12).mint(DEV); - - usdc.mint(DEV, amountIn); - usdc.approve(address(router), amountIn); - - IERC20[] memory tokenList; - uint256[] memory pairVersions; - - tokenList = new IERC20[](3); - tokenList[0] = usdc; - tokenList[1] = taxToken; - tokenList[2] = wavax; - - pairVersions = new uint256[](2); - pairVersions[0] = 0; - pairVersions[1] = 0; - uint256 amountIn2 = 1e18; - - vm.expectRevert("Joe: K"); - router.swapExactTokensForTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp - ); - vm.deal(DEV, amountIn2); - vm.expectRevert("Joe: K"); - router.swapExactTokensForAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp - ); - - tokenList[0] = wavax; - tokenList[1] = taxToken; - tokenList[2] = usdc; - - vm.deal(DEV, amountIn2); - vm.expectRevert("Joe: K"); - router.swapExactAVAXForTokens{value: amountIn2}(0, pairVersions, tokenList, DEV, block.timestamp); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn2}( - 0, pairVersions, tokenList, DEV, block.timestamp - ); - } - - function testSwappingOnNotExistingV1PairReverts() public { - IERC20[] memory tokenListAvaxIn; - IERC20[] memory tokenList; - uint256[] memory pairVersions; - - uint256 amountIn2 = 1e18; - vm.deal(DEV, amountIn2); - - tokenList = new IERC20[](3); - tokenList[0] = usdc; - tokenList[1] = weth; - tokenList[2] = wavax; - - pairVersions = new uint256[](2); - pairVersions[0] = DEFAULT_BIN_STEP; - pairVersions[1] = 0; - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); - router.swapExactTokensForTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); - router.swapExactTokensForAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); - router.swapTokensForExactTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); - router.swapTokensForExactAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp - ); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp - ); - - tokenListAvaxIn = new IERC20[](3); - tokenListAvaxIn[0] = wavax; - tokenListAvaxIn[1] = usdc; - tokenListAvaxIn[2] = weth; - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, usdc, weth, pairVersions[1])); - router.swapExactAVAXForTokens{value: amountIn2}(0, pairVersions, tokenListAvaxIn, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, usdc, weth, pairVersions[1])); - router.swapAVAXForExactTokens{value: amountIn2}(0, pairVersions, tokenListAvaxIn, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, usdc, weth, pairVersions[1])); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn2}( - 0, pairVersions, tokenListAvaxIn, DEV, block.timestamp - ); - } - - receive() external payable {} -} diff --git a/test_old/LBRouter.Liquidity.t.sol b/test_old/LBRouter.Liquidity.t.sol deleted file mode 100644 index 8f4ec6f4..00000000 --- a/test_old/LBRouter.Liquidity.t.sol +++ /dev/null @@ -1,429 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; - -contract LiquidityBinRouterTest is TestHelper { - event AVAXreceived(); - - function setUp() public override { - usdc = new ERC20Mock(6); - weth = new ERC20Mock(18); - wavax = new WAVAX(); - - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - addAllAssetsToQuoteWhitelist(factory); - router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(wavax))); - - pair = createLBPairDefaultFees(usdc, weth); - } - - function testAddLiquidityNoSlippage() public { - uint256 _amountYIn = 100e18; - uint24 _startId = ID_ONE; - uint24 _numberBins = 9; - uint24 _gap = 2; - - (int256[] memory _deltaIds,,, uint256 amountXIn) = - addLiquidityFromRouter(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - - uint256[] memory amounts = new uint256[](_numberBins); - uint256[] memory ids = new uint256[](_numberBins); - uint256 totalXbalance; - uint256 totalYBalance; - for (uint256 i; i < _numberBins; i++) { - ids[i] = uint256(int256(uint256(ID_ONE)) + _deltaIds[i]); - uint256 LBTokenAmount = pair.balanceOf(DEV, ids[i]); - amounts[i] = LBTokenAmount; - (uint256 reserveX, uint256 reserveY) = pair.getBin(uint24(ids[i])); - bool hasXBalanceInBin = (LBTokenAmount != 0) && (reserveX != 0); - bool hasYBalanceInBin = (LBTokenAmount != 0) && (reserveY != 0); - totalXbalance += hasXBalanceInBin ? (LBTokenAmount * reserveX - 1) / pair.totalSupply(ids[i]) + 1 : 0; - totalYBalance += hasYBalanceInBin ? (LBTokenAmount * reserveY - 1) / pair.totalSupply(ids[i]) + 1 : 0; - } - assertApproxEqAbs(totalXbalance, amountXIn, 1000); - assertApproxEqAbs(totalYBalance, _amountYIn, 1000); - - pair.setApprovalForAll(address(router), true); - - router.removeLiquidity( - usdc, weth, DEFAULT_BIN_STEP, totalXbalance, totalYBalance, ids, amounts, DEV, block.timestamp - ); - - assertEq(usdc.balanceOf(DEV), amountXIn); - assertEq(weth.balanceOf(DEV), _amountYIn); - } - - function testRemoveLiquidityReverseOrder() public { - uint256 _amountYIn = 100e18; - uint24 _startId = ID_ONE; - uint24 _numberBins = 9; - uint24 _gap = 2; - - (int256[] memory _deltaIds,,, uint256 amountXIn) = - addLiquidityFromRouter(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - - uint256[] memory amounts = new uint256[](_numberBins); - uint256[] memory ids = new uint256[](_numberBins); - uint256 totalXbalance; - uint256 totalYBalance; - for (uint256 i; i < _numberBins; i++) { - ids[i] = uint256(int256(uint256(ID_ONE)) + _deltaIds[i]); - uint256 LBTokenAmount = pair.balanceOf(DEV, ids[i]); - amounts[i] = LBTokenAmount; - (uint256 reserveX, uint256 reserveY) = pair.getBin(uint24(ids[i])); - bool hasXBalanceInBin = (LBTokenAmount != 0) && (reserveX != 0); - bool hasYBalanceInBin = (LBTokenAmount != 0) && (reserveY != 0); - totalXbalance += hasXBalanceInBin ? (LBTokenAmount * reserveX - 1) / pair.totalSupply(ids[i]) + 1 : 0; - totalYBalance += hasYBalanceInBin ? (LBTokenAmount * reserveY - 1) / pair.totalSupply(ids[i]) + 1 : 0; - } - assertApproxEqAbs(totalXbalance, amountXIn, 1000); - assertApproxEqAbs(totalYBalance, _amountYIn, 1000); - - pair.setApprovalForAll(address(router), true); - - router.removeLiquidity( - weth, usdc, DEFAULT_BIN_STEP, _amountYIn, totalXbalance, ids, amounts, DEV, block.timestamp - ); - - assertEq(usdc.balanceOf(DEV), amountXIn); - assertEq(weth.balanceOf(DEV), _amountYIn); - } - - function testRemoveLiquiditySlippageReverts() public { - uint256 _amountYIn = 100e18; - uint24 _startId = ID_ONE; - uint24 _numberBins = 23; - uint24 _gap = 2; - - (int256[] memory _deltaIds,,, uint256 amountXIn) = - addLiquidityFromRouter(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - - uint256[] memory amounts = new uint256[](_numberBins); - uint256[] memory ids = new uint256[](_numberBins); - uint256 totalXbalance; - uint256 totalYBalance; - for (uint256 i; i < _numberBins; i++) { - ids[i] = uint256(int256(uint256(ID_ONE)) + _deltaIds[i]); - uint256 LBTokenAmount = pair.balanceOf(DEV, ids[i]); - amounts[i] = LBTokenAmount; - (uint256 reserveX, uint256 reserveY) = pair.getBin(uint24(ids[i])); - bool hasXBalanceInBin = (LBTokenAmount != 0) && (reserveX != 0); - bool hasYBalanceInBin = (LBTokenAmount != 0) && (reserveY != 0); - totalXbalance += hasXBalanceInBin ? (LBTokenAmount * reserveX - 1) / pair.totalSupply(ids[i]) + 1 : 0; - totalYBalance += hasYBalanceInBin ? (LBTokenAmount * reserveY - 1) / pair.totalSupply(ids[i]) + 1 : 0; - } - assertApproxEqAbs(totalXbalance, amountXIn, 1000); - assertApproxEqAbs(totalYBalance, _amountYIn, 1000); - - pair.setApprovalForAll(address(router), true); - vm.expectRevert( - abi.encodeWithSelector( - LBRouter__AmountSlippageCaught.selector, totalXbalance + 1, totalXbalance, totalYBalance, totalYBalance - ) - ); - router.removeLiquidity( - usdc, weth, DEFAULT_BIN_STEP, totalXbalance + 1, totalYBalance, ids, amounts, DEV, block.timestamp - ); - vm.expectRevert( - abi.encodeWithSelector( - LBRouter__AmountSlippageCaught.selector, totalXbalance, totalXbalance, totalYBalance + 1, totalYBalance - ) - ); - router.removeLiquidity( - usdc, weth, DEFAULT_BIN_STEP, totalXbalance, totalYBalance + 1, ids, amounts, DEV, block.timestamp - ); - } - - function testAddLiquidityAVAX() public { - pair = createLBPairDefaultFees(usdc, wavax); - uint24 _numberBins = 23; - uint256 _amountYIn = 100e18; //AVAX - uint24 _gap = 2; - - (int256[] memory _deltaIds,,, uint256 amountXIn) = addLiquidityFromRouter( - usdc, ERC20Mock(address(wavax)), _amountYIn, ID_ONE, _numberBins, _gap, DEFAULT_BIN_STEP - ); - - uint256[] memory amounts = new uint256[](_numberBins); - uint256[] memory ids = new uint256[](_numberBins); - uint256 totalXbalance; - uint256 totalYBalance; - for (uint256 i; i < _numberBins; i++) { - ids[i] = uint256(int256(uint256(ID_ONE)) + _deltaIds[i]); - uint256 LBTokenAmount = pair.balanceOf(DEV, ids[i]); - amounts[i] = LBTokenAmount; - (uint256 reserveX, uint256 reserveY) = pair.getBin(uint24(ids[i])); - bool hasXBalanceInBin = (LBTokenAmount != 0) && (reserveX != 0); - bool hasYBalanceInBin = (LBTokenAmount != 0) && (reserveY != 0); - totalXbalance += hasXBalanceInBin ? (LBTokenAmount * reserveX - 1) / pair.totalSupply(ids[i]) + 1 : 0; - totalYBalance += hasYBalanceInBin ? (LBTokenAmount * reserveY - 1) / pair.totalSupply(ids[i]) + 1 : 0; - } - assertApproxEqAbs(totalXbalance, amountXIn, 1000); - assertApproxEqAbs(totalYBalance, _amountYIn, 1000); - - pair.setApprovalForAll(address(router), true); - - uint256 AVAXBalanceBefore = address(DEV).balance; - { - router.removeLiquidityAVAX( - usdc, DEFAULT_BIN_STEP, totalXbalance, totalYBalance, ids, amounts, DEV, block.timestamp - ); - } - assertEq(usdc.balanceOf(DEV), amountXIn); - assertEq(address(DEV).balance - AVAXBalanceBefore, totalYBalance); - } - - function testAddLiquidityAVAXReversed() public { - pair = createLBPairDefaultFees(wavax, usdc); - uint24 _numberBins = 21; - uint256 amountTokenIn = 100e18; - uint24 _gap = 2; - (int256[] memory _deltaIds,,, uint256 _amountAVAXIn) = addLiquidityFromRouter( - ERC20Mock(address(wavax)), usdc, amountTokenIn, ID_ONE, _numberBins, _gap, DEFAULT_BIN_STEP - ); - uint256[] memory amounts = new uint256[](_numberBins); - uint256[] memory ids = new uint256[](_numberBins); - uint256 totalXbalance; - uint256 totalYBalance; - for (uint256 i; i < _numberBins; i++) { - ids[i] = uint256(int256(uint256(ID_ONE)) + _deltaIds[i]); - uint256 LBTokenAmount = pair.balanceOf(DEV, ids[i]); - amounts[i] = LBTokenAmount; - (uint256 reserveX, uint256 reserveY) = pair.getBin(uint24(ids[i])); - bool hasXBalanceInBin = (LBTokenAmount != 0) && (reserveX != 0); - bool hasYBalanceInBin = (LBTokenAmount != 0) && (reserveY != 0); - totalXbalance += hasXBalanceInBin ? (LBTokenAmount * reserveX - 1) / pair.totalSupply(ids[i]) + 1 : 0; - totalYBalance += hasYBalanceInBin ? (LBTokenAmount * reserveY - 1) / pair.totalSupply(ids[i]) + 1 : 0; - } - assertApproxEqAbs(totalXbalance, _amountAVAXIn, 1000); - assertApproxEqAbs(totalYBalance, amountTokenIn, 1000); - - pair.setApprovalForAll(address(router), true); - uint256 AVAXBalanceBefore = address(DEV).balance; - { - router.removeLiquidityAVAX( - usdc, DEFAULT_BIN_STEP, totalYBalance, totalXbalance, ids, amounts, DEV, block.timestamp - ); - } - assertEq(usdc.balanceOf(DEV), amountTokenIn); - assertEq(address(DEV).balance - AVAXBalanceBefore, totalXbalance); - } - - function testAddLiquidityTaxToken() public { - taxToken = new ERC20TransferTaxMock(); - pair = createLBPairDefaultFees(taxToken, wavax); - uint24 _numberBins = 9; - uint256 _amountAVAXIn = 100e18; - uint24 _gap = 2; - - (int256[] memory _deltaIds,,, uint256 amountTokenIn) = addLiquidityFromRouter( - ERC20Mock(address(taxToken)), - ERC20Mock(address(wavax)), - _amountAVAXIn, - ID_ONE, - _numberBins, - _gap, - DEFAULT_BIN_STEP - ); - - uint256[] memory amounts = new uint256[](_numberBins); - uint256[] memory ids = new uint256[](_numberBins); - uint256 totalXbalance; - uint256 totalYBalance; - for (uint256 i; i < _numberBins; i++) { - ids[i] = uint256(int256(uint256(ID_ONE)) + _deltaIds[i]); - uint256 LBTokenAmount = pair.balanceOf(DEV, ids[i]); - amounts[i] = LBTokenAmount; - (uint256 reserveX, uint256 reserveY) = pair.getBin(uint24(ids[i])); - bool hasXBalanceInBin = (LBTokenAmount != 0) && (reserveX != 0); - bool hasYBalanceInBin = (LBTokenAmount != 0) && (reserveY != 0); - totalXbalance += hasXBalanceInBin ? (LBTokenAmount * reserveX - 1) / pair.totalSupply(ids[i]) + 1 : 0; - totalYBalance += hasYBalanceInBin ? (LBTokenAmount * reserveY - 1) / pair.totalSupply(ids[i]) + 1 : 0; - } - - pair.setApprovalForAll(address(router), true); - - vm.expectRevert(bytes("ERC20: burn amount exceeds balance")); - router.removeLiquidityAVAX( - taxToken, DEFAULT_BIN_STEP, totalXbalance, _amountAVAXIn, ids, amounts, DEV, block.timestamp - ); - - router.removeLiquidity( - taxToken, wavax, DEFAULT_BIN_STEP, totalXbalance, _amountAVAXIn, ids, amounts, DEV, block.timestamp - ); - - assertEq(taxToken.balanceOf(DEV), amountTokenIn / 4 + 1); //2 transfers with 50% tax - assertEq(wavax.balanceOf(DEV), _amountAVAXIn); - } - - function testAddLiquidityIgnored() public { - uint256 _amountYIn = 100e18; - uint24 _startId = ID_ONE; - uint24 _numberBins = 9; - uint24 _gap = 2; - - addLiquidityFromRouter(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - - factory.setLBPairIgnored(usdc, weth, DEFAULT_BIN_STEP, true); - ILBRouter.LiquidityParameters memory _liquidityParameters = - prepareLiquidityParameters(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - - router.addLiquidity(_liquidityParameters); - } - - function testForIdSlippageCaughtReverts() public { - uint256 _amountYIn = 100e18; - uint24 _startId = ID_ONE; - uint24 _numberBins = 9; - uint24 _gap = 2; - - addLiquidityFromRouter(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - - (,,, uint256 amountXIn) = spreadLiquidityForRouter(_amountYIn, _startId, _numberBins, _gap); - - ILBRouter.LiquidityParameters memory _liquidityParameters = - prepareLiquidityParameters(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - _liquidityParameters.amountXMin = 0; - _liquidityParameters.amountYMin = 0; - _liquidityParameters.idSlippage = 0; - - //_liq.activeIdDesired + _liq.idSlippage < _activeId - weth.mint(address(pair), _amountYIn); - pair.swap(false, ALICE); - vm.expectRevert( - abi.encodeWithSelector( - LBRouter__IdSlippageCaught.selector, 8388608, _liquidityParameters.idSlippage, 8388620 - ) - ); - router.addLiquidity(_liquidityParameters); - - // _activeId + _liq.idSlippage < _liq.activeIdDesired - usdc.mint(address(pair), 2 * amountXIn); - pair.swap(true, ALICE); - vm.expectRevert( - abi.encodeWithSelector( - LBRouter__IdSlippageCaught.selector, 8388608, _liquidityParameters.idSlippage, 8388596 - ) - ); - router.addLiquidity(_liquidityParameters); - } - - function testForAmountSlippageCaughtReverts() public { - uint256 _amountYIn = 100e18; - uint24 _startId = ID_ONE; - uint24 _numberBins = 9; - uint24 _gap = 2; - addLiquidityFromRouter(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - - ILBRouter.LiquidityParameters memory _liquidityParameters = - prepareLiquidityParameters(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - - (,,, uint256 amountXIn) = spreadLiquidityForRouter(_amountYIn, _startId, _numberBins, _gap); - - weth.mint(address(pair), _amountYIn / 3); - pair.swap(false, ALICE); - - //no slippage allowed - _liquidityParameters.amountXMin = amountXIn; - _liquidityParameters.amountYMin = _amountYIn; - - //Amount slippage is low in every case - depends only on C [bin composition] change in active bin - vm.expectRevert( - abi.encodeWithSelector( - LBRouter__AmountSlippageCaught.selector, - 98518565614280135938, - 98515492353722968299, - 100000000000000000000, - 100000000000000000000 - ) - ); - router.addLiquidity(_liquidityParameters); - } - - function testForIdDesiredOverflowReverts() public { - uint256 _amountYIn = 100e18; - uint24 _startId = ID_ONE; - uint24 _numberBins = 9; - uint24 _gap = 2; - uint256 overflown24 = uint256(type(uint24).max) + 1; - - ILBRouter.LiquidityParameters memory _liquidityParameters = - prepareLiquidityParameters(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - //this will fail until n16 from audit will be fixed - _liquidityParameters.activeIdDesired = overflown24; - _liquidityParameters.idSlippage = 0; - vm.expectRevert(abi.encodeWithSelector(LBRouter__IdDesiredOverflows.selector, overflown24, 0)); - router.addLiquidity(_liquidityParameters); - - _liquidityParameters.activeIdDesired = 0; - _liquidityParameters.idSlippage = overflown24; - vm.expectRevert(abi.encodeWithSelector(LBRouter__IdDesiredOverflows.selector, 0, overflown24)); - router.addLiquidity(_liquidityParameters); - - _liquidityParameters.activeIdDesired = overflown24; - _liquidityParameters.idSlippage = overflown24; - vm.expectRevert(abi.encodeWithSelector(LBRouter__IdDesiredOverflows.selector, overflown24, overflown24)); - router.addLiquidity(_liquidityParameters); - } - - function testForLengthsMismatchReverts() public { - uint256 _amountYIn = 100e18; - uint24 _startId = ID_ONE; - uint24 _numberBins = 9; - uint24 _gap = 2; - - ILBRouter.LiquidityParameters memory _liquidityParameters = - prepareLiquidityParameters(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - - int256[] memory _wrongLengthDeltaIds = new int256[](_numberBins - 1); - - _liquidityParameters.deltaIds = _wrongLengthDeltaIds; - - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.addLiquidity(_liquidityParameters); - } - - function testWrongTokenOrderReverts() public { - uint256 _amountYIn = 100e18; - uint24 _startId = ID_ONE; - uint24 _numberBins = 9; - uint24 _gap = 2; - - ILBRouter.LiquidityParameters memory _liquidityParameters = - prepareLiquidityParameters(weth, usdc, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - - vm.expectRevert(LBRouter__WrongTokenOrder.selector); - router.addLiquidity(_liquidityParameters); - vm.expectRevert(LBRouter__WrongTokenOrder.selector); - router.addLiquidityAVAX(_liquidityParameters); - } - - function testAddLiquidityAVAXnotAVAXReverts() public { - uint256 _amountYIn = 100e18; - uint24 _startId = ID_ONE; - uint24 _numberBins = 9; - uint24 _gap = 2; - - ILBRouter.LiquidityParameters memory _liquidityParameters = - prepareLiquidityParameters(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - - vm.expectRevert( - abi.encodeWithSelector( - LBRouter__WrongAvaxLiquidityParameters.selector, - _liquidityParameters.tokenX, - _liquidityParameters.tokenY, - _liquidityParameters.amountX, - _liquidityParameters.amountY, - 0 - ) - ); - router.addLiquidityAVAX(_liquidityParameters); - } - - receive() external payable {} -} diff --git a/test_old/LBRouter.Swaps.t.sol b/test_old/LBRouter.Swaps.t.sol deleted file mode 100644 index d8785d1e..00000000 --- a/test_old/LBRouter.Swaps.t.sol +++ /dev/null @@ -1,474 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; - -contract LiquidityBinRouterTest is TestHelper { - LBPair internal pair0; - LBPair internal pair1; - LBPair internal pair2; - LBPair internal pair3; - LBPair internal taxTokenPair1; - LBPair internal taxTokenPair; - - function setUp() public override { - usdc = new ERC20Mock(6); - link = new ERC20Mock(10); - wbtc = new ERC20Mock(12); - weth = new ERC20Mock(18); - wbtc = new ERC20Mock(24); - - taxToken = new ERC20TransferTaxMock(); - - wavax = new WAVAX(); - - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - addAllAssetsToQuoteWhitelist(factory); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - - router = new LBRouter(factory, IJoeFactory(address(0)), IWAVAX(address(wavax))); - - pair = createLBPairDefaultFees(usdc, weth); - addLiquidityFromRouter(usdc, weth, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - - pair0 = createLBPairDefaultFees(usdc, link); - addLiquidityFromRouter(usdc, link, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - pair1 = createLBPairDefaultFees(link, wbtc); - addLiquidityFromRouter(link, wbtc, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - pair2 = createLBPairDefaultFees(wbtc, weth); - addLiquidityFromRouter(wbtc, weth, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - pair3 = createLBPairDefaultFees(weth, wbtc); - addLiquidityFromRouter(weth, wbtc, 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - taxTokenPair1 = createLBPairDefaultFees(usdc, taxToken); - addLiquidityFromRouter(usdc, ERC20Mock(address(taxToken)), 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - - taxTokenPair = createLBPairDefaultFees(taxToken, wavax); - addLiquidityFromRouter( - ERC20Mock(address(taxToken)), ERC20Mock(address(wavax)), 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP - ); - - pairWavax = createLBPairDefaultFees(usdc, wavax); - addLiquidityFromRouter(usdc, ERC20Mock(address(wavax)), 100e18, ID_ONE, 9, 2, DEFAULT_BIN_STEP); - } - - function testSwapExactTokensForTokensSinglePair() public { - uint256 amountIn = 1e18; - - usdc.mint(DEV, amountIn); - - usdc.approve(address(router), amountIn); - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = usdc; - tokenList[1] = weth; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - (uint256 amountOut,) = router.getSwapOut(pair, amountIn, true); - vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); - router.swapExactTokensForTokens(amountIn, amountOut + 1, pairVersions, tokenList, DEV, block.timestamp); - - router.swapExactTokensForTokens(amountIn, amountOut, pairVersions, tokenList, DEV, block.timestamp); - - assertApproxEqAbs(weth.balanceOf(DEV), amountOut, 10); - } - - function testSwapExactTokensForAvaxSinglePair() public { - uint256 amountIn = 1e18; - - usdc.mint(ALICE, amountIn); - - vm.startPrank(ALICE); - usdc.approve(address(router), amountIn); - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = usdc; - tokenList[1] = wavax; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - (uint256 amountOut,) = router.getSwapOut(pair, amountIn, true); - - uint256 devBalanceBefore = ALICE.balance; - vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); - router.swapExactTokensForAVAX(amountIn, amountOut + 1, pairVersions, tokenList, ALICE, block.timestamp); - - router.swapExactTokensForAVAX(amountIn, amountOut, pairVersions, tokenList, ALICE, block.timestamp); - vm.stopPrank(); - - assertEq(ALICE.balance - devBalanceBefore, amountOut); - } - - function testSwapExactAVAXForTokensSinglePair() public { - uint256 amountIn = 1e18; - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = wavax; - tokenList[1] = usdc; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - (uint256 amountOut,) = router.getSwapOut(pairWavax, amountIn, false); - - vm.deal(DEV, amountIn); - vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); - router.swapExactAVAXForTokens{value: amountIn}(amountOut + 1, pairVersions, tokenList, DEV, block.timestamp); - - router.swapExactAVAXForTokens{value: amountIn}(amountOut, pairVersions, tokenList, DEV, block.timestamp); - - assertApproxEqAbs(usdc.balanceOf(DEV), amountOut, 13); - } - - function testSwapTokensForExactTokensSinglePair() public { - uint256 amountOut = 1e18; - - (uint256 amountIn,) = router.getSwapIn(pair, amountOut, true); - usdc.mint(DEV, amountIn); - - usdc.approve(address(router), amountIn); - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = usdc; - tokenList[1] = weth; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - vm.expectRevert(abi.encodeWithSelector(LBRouter__MaxAmountInExceeded.selector, amountIn - 1, amountIn)); - router.swapTokensForExactTokens(amountOut, amountIn - 1, pairVersions, tokenList, DEV, block.timestamp); - - router.swapTokensForExactTokens(amountOut, amountIn, pairVersions, tokenList, DEV, block.timestamp); - - assertApproxEqAbs(weth.balanceOf(DEV), amountOut, 10); - } - - function testSwapTokensForExactAVAXSinglePair() public { - uint256 amountOut = 1e18; - - (uint256 amountIn,) = router.getSwapIn(pairWavax, amountOut, true); - usdc.mint(ALICE, amountIn); - - vm.startPrank(ALICE); - usdc.approve(address(router), amountIn); - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = usdc; - tokenList[1] = wavax; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - uint256 devBalanceBefore = ALICE.balance; - - vm.expectRevert(abi.encodeWithSelector(LBRouter__MaxAmountInExceeded.selector, amountIn - 1, amountIn)); - router.swapTokensForExactAVAX(amountOut, amountIn - 1, pairVersions, tokenList, ALICE, block.timestamp); - - router.swapTokensForExactAVAX(amountOut, amountIn, pairVersions, tokenList, ALICE, block.timestamp); - vm.stopPrank(); - - assertEq(ALICE.balance - devBalanceBefore, amountOut); - } - - function testSwapAVAXForExactTokensSinglePair() public { - uint256 amountOut = 1e18; - - (uint256 amountIn,) = router.getSwapIn(pairWavax, amountOut, false); - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = wavax; - tokenList[1] = usdc; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - vm.deal(DEV, amountIn); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__MaxAmountInExceeded.selector, amountIn - 1, amountIn)); - router.swapAVAXForExactTokens{value: amountIn - 1}(amountOut, pairVersions, tokenList, ALICE, block.timestamp); - router.swapAVAXForExactTokens{value: amountIn}(amountOut, pairVersions, tokenList, DEV, block.timestamp); - - assertApproxEqAbs(usdc.balanceOf(DEV), amountOut, 13); - } - - function testSwapExactTokensForTokensSupportingFeeOnTransferTokens() public { - uint256 amountIn = 1e18; - - taxToken.mint(DEV, amountIn); - - taxToken.approve(address(router), amountIn); - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = taxToken; - tokenList[1] = wavax; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - (uint256 amountOut,) = router.getSwapOut(taxTokenPair, amountIn, true); - amountOut = amountOut / 2; - vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - amountIn, amountOut + 1, pairVersions, tokenList, DEV, block.timestamp - ); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - amountIn, 0, pairVersions, tokenList, DEV, block.timestamp - ); - - // 50% tax to take into account - assertApproxEqAbs(wavax.balanceOf(DEV), amountOut, 10); - - // Swap back in the other direction - amountIn = wavax.balanceOf(DEV); - wavax.approve(address(router), amountIn); - tokenList[0] = wavax; - tokenList[1] = taxToken; - - (amountOut,) = router.getSwapOut(taxTokenPair, amountIn, true); - amountOut = amountOut / 2; - uint256 balanceBefore = taxToken.balanceOf(DEV); - vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - amountIn, amountOut + 1, pairVersions, tokenList, DEV, block.timestamp - ); - - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - amountIn, amountOut, pairVersions, tokenList, DEV, block.timestamp - ); - - assertApproxEqAbs(taxToken.balanceOf(DEV) - balanceBefore, amountOut, 10); - } - - function testSwapExactTokensForAVAXSupportingFeeOnTransferTokens() public { - uint256 amountIn = 1e18; - - taxToken.mint(ALICE, amountIn); - - vm.startPrank(ALICE); - taxToken.approve(address(router), amountIn); - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = taxToken; - tokenList[1] = wavax; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - (uint256 amountOut,) = router.getSwapOut(taxTokenPair, amountIn, true); - amountOut = amountOut / 2; - uint256 devBalanceBefore = ALICE.balance; - vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn, amountOut + 1, pairVersions, tokenList, ALICE, block.timestamp - ); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn, amountOut, pairVersions, tokenList, ALICE, block.timestamp - ); - vm.stopPrank(); - - assertGe(ALICE.balance - devBalanceBefore, amountOut); - } - - function testSwapExactAVAXForTokensSupportingFeeOnTransferTokens() public { - uint256 amountIn = 1e18; - - IERC20[] memory tokenList = new IERC20[](2); - tokenList[0] = wavax; - tokenList[1] = taxToken; - uint256[] memory pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - (uint256 amountOut,) = router.getSwapOut(taxTokenPair, amountIn, true); - amountOut = amountOut / 2; - vm.deal(DEV, amountIn); - vm.expectRevert(abi.encodeWithSelector(LBRouter__InsufficientAmountOut.selector, amountOut + 1, amountOut)); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( - amountOut + 1, pairVersions, tokenList, DEV, block.timestamp - ); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn}( - amountOut, pairVersions, tokenList, DEV, block.timestamp - ); - - assertApproxEqAbs(taxToken.balanceOf(DEV), amountOut, 10); - } - - function testSwapExactTokensForTokensMultiplePairs() public { - uint256 amountIn = 1e18; - - usdc.mint(DEV, amountIn); - - usdc.approve(address(router), amountIn); - - (IERC20[] memory tokenList, uint256[] memory pairVersions) = _buildComplexSwapRoute(); - - router.swapExactTokensForTokens(amountIn, 0, pairVersions, tokenList, DEV, block.timestamp); - - assertGt(wbtc.balanceOf(DEV), 0); - } - - function testSwapTokensForExactTokensMultiplePairs() public { - uint256 amountOut = 1e18; - - usdc.mint(DEV, 100e18); - - usdc.approve(address(router), 100e18); - - (IERC20[] memory tokenList, uint256[] memory pairVersions) = _buildComplexSwapRoute(); - vm.expectRevert( - abi.encodeWithSelector(LBRouter__MaxAmountInExceeded.selector, 100500938281494149, 1005015664148120440) - ); - router.swapTokensForExactTokens(amountOut, 100500938281494149, pairVersions, tokenList, DEV, block.timestamp); - router.swapTokensForExactTokens(amountOut, 100e18, pairVersions, tokenList, DEV, block.timestamp); - - assertEq(wbtc.balanceOf(DEV), amountOut); - } - - function testSwapWithDifferentBinSteps() public { - factory.setPreset( - 75, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - 5, - 10, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATED, - DEFAULT_SAMPLE_LIFETIME - ); - createLBPairDefaultFeesFromStartIdAndBinStep(usdc, weth, ID_ONE, 75); - addLiquidityFromRouter(usdc, weth, 100e18, ID_ONE, 9, 2, 75); - - uint256 amountIn = 1e18; - - usdc.mint(DEV, amountIn); - usdc.approve(address(router), amountIn); - - IERC20[] memory tokenList; - uint256[] memory pairVersions; - tokenList = new IERC20[](2); - tokenList[0] = usdc; - tokenList[1] = weth; - pairVersions = new uint256[](1); - pairVersions[0] = DEFAULT_BIN_STEP; - - router.swapExactTokensForTokens(amountIn, 0, pairVersions, tokenList, DEV, block.timestamp); - - assertGt(weth.balanceOf(DEV), 0); - - weth.approve(address(router), weth.balanceOf(DEV)); - - tokenList[0] = weth; - tokenList[1] = usdc; - pairVersions = new uint256[](1); - pairVersions[0] = 75; - - router.swapExactTokensForTokens(weth.balanceOf(DEV), 0, pairVersions, tokenList, DEV, block.timestamp); - assertGt(usdc.balanceOf(DEV), 0); - } - - function _buildComplexSwapRoute() private view returns (IERC20[] memory tokenList, uint256[] memory pairVersions) { - tokenList = new IERC20[](5); - tokenList[0] = usdc; - tokenList[1] = link; - tokenList[2] = wbtc; - tokenList[3] = weth; - tokenList[4] = wbtc; - - pairVersions = new uint256[](4); - pairVersions[0] = DEFAULT_BIN_STEP; - pairVersions[1] = DEFAULT_BIN_STEP; - pairVersions[2] = DEFAULT_BIN_STEP; - pairVersions[3] = DEFAULT_BIN_STEP; - } - - function testTaxTokenEqualOnlyV2Swap() public { - uint256 amountIn = 1e18; - - taxToken.mint(ALICE, amountIn); - taxToken.mint(BOB, amountIn); - usdc.mint(ALICE, amountIn); - usdc.mint(BOB, amountIn); - - IERC20[] memory tokenList = new IERC20[](3); - tokenList[0] = usdc; - tokenList[1] = taxToken; - tokenList[2] = wavax; - uint256[] memory pairVersions = new uint256[](2); - pairVersions[0] = DEFAULT_BIN_STEP; - pairVersions[1] = DEFAULT_BIN_STEP; - - vm.startPrank(ALICE); - usdc.approve(address(router), amountIn); - taxToken.approve(address(router), amountIn); - uint256 aliceBalanceBefore = ALICE.balance; - uint256 amountOutNotSupporting = - router.swapExactTokensForAVAX(amountIn, 0, pairVersions, tokenList, ALICE, block.timestamp); - vm.stopPrank(); - - vm.startPrank(BOB); - usdc.approve(address(router), amountIn); - taxToken.approve(address(router), amountIn); - uint256 bobBalanceBefore = BOB.balance; - uint256 amountOutSupporting = router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn, 0, pairVersions, tokenList, BOB, block.timestamp - ); - vm.stopPrank(); - - assertEq(ALICE.balance, BOB.balance); - assertEq(amountOutNotSupporting, amountOutSupporting); - } - - function testSwappingOnNotExistingV2PairReverts() public { - IERC20[] memory tokenListAvaxIn; - IERC20[] memory tokenList; - uint256[] memory pairVersions; - - uint256 amountIn2 = 1e18; - vm.deal(DEV, amountIn2); - - tokenList = new IERC20[](3); - tokenList[0] = usdc; - tokenList[1] = weth; - tokenList[2] = wavax; - - pairVersions = new uint256[](2); - pairVersions[0] = DEFAULT_BIN_STEP; - pairVersions[1] = DEFAULT_BIN_STEP + 1; - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); - router.swapExactTokensForTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); - router.swapExactTokensForAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); - router.swapTokensForExactTokens(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); - router.swapTokensForExactAVAX(amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp - ); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, weth, wavax, pairVersions[1])); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - amountIn2, 0, pairVersions, tokenList, DEV, block.timestamp - ); - - tokenListAvaxIn = new IERC20[](3); - tokenListAvaxIn[0] = wavax; - tokenListAvaxIn[1] = usdc; - tokenListAvaxIn[2] = weth; - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, usdc, weth, pairVersions[1])); - router.swapExactAVAXForTokens{value: amountIn2}(0, pairVersions, tokenListAvaxIn, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, usdc, weth, pairVersions[1])); - router.swapAVAXForExactTokens{value: amountIn2}(0, pairVersions, tokenListAvaxIn, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__PairNotCreated.selector, usdc, weth, pairVersions[1])); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens{value: amountIn2}( - 0, pairVersions, tokenListAvaxIn, DEV, block.timestamp - ); - } - - receive() external payable {} -} diff --git a/test_old/LBRouter.t.sol b/test_old/LBRouter.t.sol deleted file mode 100644 index 94de93ef..00000000 --- a/test_old/LBRouter.t.sol +++ /dev/null @@ -1,386 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; -import {Addresses} from "test/integration/Addresses.sol"; - -contract LiquidityBinRouterTest is TestHelper { - function setUp() public override { - super.setUp(); - } - - function testConstructor() public { - assertEq(address(router.factory()), address(factory)); - assertEq(address(router.oldFactory()), Addresses.JOE_V1_FACTORY_ADDRESS); - assertEq(address(router.wavax()), Addresses.WAVAX_AVALANCHE_ADDRESS); - } - - function testCreateLBPair() public { - factory.setFactoryLockedState(false); - - router.createLBPair(usdc, weth, ID_ONE, DEFAULT_BIN_STEP); - - assertEq(factory.getNumberOfLBPairs(), 1); - pair = LBPair(address(factory.getLBPairInformation(usdc, weth, DEFAULT_BIN_STEP).LBPair)); - - assertEq(address(pair.factory()), address(factory)); - assertEq(address(pair.tokenX()), address(usdc)); - assertEq(address(pair.tokenY()), address(weth)); - - FeeHelper.FeeParameters memory feeParameters = pair.feeParameters(); - assertEq(feeParameters.volatilityAccumulated, 0); - assertEq(feeParameters.time, 0); - assertEq(feeParameters.maxVolatilityAccumulated, DEFAULT_MAX_VOLATILITY_ACCUMULATED); - assertEq(feeParameters.filterPeriod, DEFAULT_FILTER_PERIOD); - assertEq(feeParameters.decayPeriod, DEFAULT_DECAY_PERIOD); - assertEq(feeParameters.binStep, DEFAULT_BIN_STEP); - assertEq(feeParameters.baseFactor, DEFAULT_BASE_FACTOR); - assertEq(feeParameters.protocolShare, DEFAULT_PROTOCOL_SHARE); - } - - function testModifierEnsure() public { - uint256[] memory defaultUintArray = new uint256[](2); - IERC20[] memory defaultIERCArray = new IERC20[](2); - - uint256 wrongDeadline = block.timestamp - 1; - - vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); - router.removeLiquidity( - usdc, weth, DEFAULT_BIN_STEP, 1, 1, defaultUintArray, defaultUintArray, DEV, wrongDeadline - ); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); - router.removeLiquidityAVAX(usdc, DEFAULT_BIN_STEP, 1, 1, defaultUintArray, defaultUintArray, DEV, wrongDeadline); - vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); - router.swapExactTokensForTokens(1, 1, defaultUintArray, defaultIERCArray, DEV, wrongDeadline); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); - router.swapExactTokensForAVAX(1, 1, defaultUintArray, defaultIERCArray, DEV, wrongDeadline); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); - router.swapExactAVAXForTokens(1, defaultUintArray, defaultIERCArray, DEV, wrongDeadline); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); - router.swapTokensForExactTokens(1, 1, defaultUintArray, defaultIERCArray, DEV, wrongDeadline); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); - router.swapTokensForExactAVAX(1, 1, defaultUintArray, defaultIERCArray, DEV, wrongDeadline); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); - router.swapAVAXForExactTokens(1, defaultUintArray, defaultIERCArray, DEV, wrongDeadline); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - 1, 1, defaultUintArray, defaultIERCArray, DEV, wrongDeadline - ); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - 1, 1, defaultUintArray, defaultIERCArray, DEV, wrongDeadline - ); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens( - 1, defaultUintArray, defaultIERCArray, DEV, wrongDeadline - ); - - // _addLiquidity private - } - - function testModifieronlyFactoryOwner() public { - vm.prank(ALICE); - vm.expectRevert(LBRouter__NotFactoryOwner.selector); - router.sweep(usdc, address(0), 1); - } - - function testModifierVerifyInputs() public { - IERC20[] memory defaultIERCArray = new IERC20[](1); - uint256[] memory pairBinStepsZeroLength = new uint256[](0); - IERC20[] memory mismatchedIERCArray = new IERC20[](3); - uint256[] memory mismatchedpairBinSteps = new uint256[](4); - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapExactTokensForTokens(1, 1, pairBinStepsZeroLength, defaultIERCArray, DEV, block.timestamp); - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapExactTokensForTokens(1, 1, mismatchedpairBinSteps, mismatchedIERCArray, DEV, block.timestamp); - - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapExactTokensForAVAX(1, 1, pairBinStepsZeroLength, defaultIERCArray, DEV, block.timestamp); - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapExactTokensForAVAX(1, 1, mismatchedpairBinSteps, mismatchedIERCArray, DEV, block.timestamp); - - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapExactAVAXForTokens(1, pairBinStepsZeroLength, defaultIERCArray, DEV, block.timestamp); - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapExactAVAXForTokens(1, mismatchedpairBinSteps, mismatchedIERCArray, DEV, block.timestamp); - - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapTokensForExactTokens(1, 1, pairBinStepsZeroLength, defaultIERCArray, DEV, block.timestamp); - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapTokensForExactTokens(1, 1, mismatchedpairBinSteps, mismatchedIERCArray, DEV, block.timestamp); - - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapTokensForExactAVAX(1, 1, pairBinStepsZeroLength, defaultIERCArray, DEV, block.timestamp); - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapTokensForExactAVAX(1, 1, mismatchedpairBinSteps, mismatchedIERCArray, DEV, block.timestamp); - - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapAVAXForExactTokens(1, pairBinStepsZeroLength, defaultIERCArray, DEV, block.timestamp); - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapAVAXForExactTokens(1, mismatchedpairBinSteps, mismatchedIERCArray, DEV, block.timestamp); - - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - 1, 1, pairBinStepsZeroLength, defaultIERCArray, DEV, block.timestamp - ); - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapExactTokensForTokensSupportingFeeOnTransferTokens( - 1, 1, mismatchedpairBinSteps, mismatchedIERCArray, DEV, block.timestamp - ); - - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - 1, 1, pairBinStepsZeroLength, defaultIERCArray, DEV, block.timestamp - ); - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - 1, 1, mismatchedpairBinSteps, mismatchedIERCArray, DEV, block.timestamp - ); - - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens( - 1, pairBinStepsZeroLength, defaultIERCArray, DEV, block.timestamp - ); - - vm.expectRevert(LBRouter__LengthsMismatch.selector); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens( - 1, mismatchedpairBinSteps, mismatchedIERCArray, DEV, block.timestamp - ); - } - - function testEnsureModifierLiquidity() public { - uint256 wrongDeadline = block.timestamp - 1; - uint256 _amountYIn = 1e18; - uint24 _numberBins = 11; - uint24 _gap = 2; - uint16 _binStep = DEFAULT_BIN_STEP; - int256[] memory _deltaIds; - uint256[] memory _distributionX; - uint256[] memory _distributionY; - uint256 amountXIn; - factory.setFactoryLockedState(false); - router.createLBPair(usdc, weth, ID_ONE, DEFAULT_BIN_STEP); - router.createLBPair(usdc, wavax, ID_ONE, DEFAULT_BIN_STEP); - - (_deltaIds, _distributionX, _distributionY, amountXIn) = - spreadLiquidityForRouter(_amountYIn, ID_ONE, _numberBins, _gap); - - usdc.mint(DEV, amountXIn); - usdc.approve(address(router), amountXIn); - weth.mint(DEV, _amountYIn); - weth.approve(address(router), _amountYIn); - - ILBRouter.LiquidityParameters memory _liquidityParameters = ILBRouter.LiquidityParameters( - usdc, - weth, - _binStep, - amountXIn, - _amountYIn, - 0, - 0, - ID_ONE, - ID_ONE, - _deltaIds, - _distributionX, - _distributionY, - DEV, - wrongDeadline - ); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__DeadlineExceeded.selector, wrongDeadline, block.timestamp)); - router.addLiquidity(_liquidityParameters); - } - - function testGetPriceFromId() public { - pair = createLBPairDefaultFees(usdc, weth); - uint256 price; - - price = router.getPriceFromId(pair, ID_ONE); - assertEq(price, 340282366920938463463374607431768211456); - - price = router.getPriceFromId(pair, ID_ONE - 10000); - assertEq(price, 4875582648561453899431769403); - - price = router.getPriceFromId(pair, ID_ONE + 10000); - assertEq(price, 23749384962529715407923990466761537977856189636583); - } - - function testGetIdFromPrice() public { - pair = createLBPairDefaultFees(usdc, weth); - uint24 id; - - id = router.getIdFromPrice(pair, 340282366920938463463374607431768211456); - assertEq(id, ID_ONE); - - id = router.getIdFromPrice(pair, 4875582648561453899431769403); - assertEq(id, ID_ONE - 10000); - - id = router.getIdFromPrice(pair, 23749384962529715407923990466761537977856189636583); - assertEq(id, ID_ONE + 10000); - } - - function testGetSwapInWrongAmountsReverts() public { - uint256 _amountYIn = 100e18; - uint24 _startId = ID_ONE; - uint24 _numberBins = 9; - uint24 _gap = 2; - uint256 amountXIn; - int256[] memory _deltaIds; - pair = createLBPairDefaultFees(usdc, weth); - (_deltaIds,,, amountXIn) = - addLiquidityFromRouter(usdc, weth, _amountYIn, _startId, _numberBins, _gap, DEFAULT_BIN_STEP); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__WrongAmounts.selector, 0, _amountYIn)); - router.getSwapIn(pair, 0, true); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__WrongAmounts.selector, _amountYIn + 1, _amountYIn)); - router.getSwapIn(pair, _amountYIn + 1, true); - - uint256[] memory amounts = new uint256[](_numberBins); - uint256[] memory ids = new uint256[](_numberBins); - uint256 totalXbalance; - uint256 totalYBalance; - for (uint256 i; i < _numberBins; i++) { - ids[i] = uint256(int256(uint256(ID_ONE)) + _deltaIds[i]); - uint256 LBTokenAmount = pair.balanceOf(DEV, ids[i]); - amounts[i] = LBTokenAmount; - (uint256 reserveX, uint256 reserveY) = pair.getBin(uint24(ids[i])); - bool hasXBalanceInBin = (LBTokenAmount != 0) && (reserveX != 0); - bool hasYBalanceInBin = (LBTokenAmount != 0) && (reserveY != 0); - totalXbalance += hasXBalanceInBin ? (LBTokenAmount * reserveX - 1) / pair.totalSupply(ids[i]) + 1 : 0; - totalYBalance += hasYBalanceInBin ? (LBTokenAmount * reserveY - 1) / pair.totalSupply(ids[i]) + 1 : 0; - } - assertApproxEqAbs(totalXbalance, amountXIn, 1000); - assertApproxEqAbs(totalYBalance, _amountYIn, 1000); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__WrongAmounts.selector, amountXIn + 1, totalXbalance)); - router.getSwapIn(pair, amountXIn + 1, false); - } - - function testGetSwapInOverflowReverts() public { - uint256 _amountYIn = type(uint112).max - 1; - uint24 _startId = ID_ONE; - uint24 _numberBins = 1; - uint24 _gap = 2; - uint256 amountXIn; - pair = createLBPairDefaultFees(usdc, weth); - (,,, amountXIn) = addLiquidity(_amountYIn, _startId, _numberBins, _gap); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__SwapOverflows.selector, _startId)); - router.getSwapIn(pair, _amountYIn, true); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__SwapOverflows.selector, _startId)); - router.getSwapIn(pair, amountXIn, false); - } - - function testSweep() public { - uint256 amountMinted = 100e6; - usdc.mint(address(router), amountMinted); - router.sweep(usdc, ALICE, amountMinted); - assertEq(usdc.balanceOf(ALICE), amountMinted); - assertEq(usdc.balanceOf(address(router)), 0); - - uint256 amountAvax = 10e18; - vm.deal(address(router), amountAvax); - router.sweep(IERC20(address(0)), ALICE, amountAvax); - assertEq(ALICE.balance, amountAvax); - assertEq(address(router).balance, 0); - } - - function testSweepMax() public { - uint256 amountMinted = 1000e6; - usdc.mint(address(router), amountMinted); - router.sweep(usdc, ALICE, type(uint256).max); - assertEq(usdc.balanceOf(ALICE), amountMinted); - assertEq(usdc.balanceOf(address(router)), 0); - - uint256 amountAvax = 100e18; - vm.deal(address(router), amountAvax); - router.sweep(IERC20(address(0)), ALICE, type(uint256).max); - assertEq(ALICE.balance, amountAvax); - assertEq(address(router).balance, 0); - } - - function testGetSwapInMoreBins() public { - uint256 _amountYIn = 100e18; - uint24 _startId = ID_ONE; - uint24 _numberBins = 9; - uint24 _gap = 2; - uint256 amountXIn; - pair = createLBPairDefaultFees(usdc, weth); - (,,, amountXIn) = addLiquidity(_amountYIn, _startId, _numberBins, _gap); - //getSwapIn goes through all bins with liquidity - (uint256 amountIn,) = router.getSwapIn(pair, amountXIn - 100, false); - (uint256 amountIn2,) = router.getSwapIn(pair, _amountYIn - 100, true); - } - - function testWrongTokenWAVAXSwaps() public { - IERC20[] memory IERCArray = new IERC20[](2); - IERCArray[0] = usdc; - IERCArray[1] = weth; - uint256[] memory pairBinStepsArray = new uint256[](1); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, address(IERCArray[1]))); - router.swapExactTokensForAVAX(1, 1, pairBinStepsArray, IERCArray, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, address(IERCArray[0]))); - router.swapExactAVAXForTokens(1, pairBinStepsArray, IERCArray, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, address(IERCArray[1]))); - router.swapTokensForExactAVAX(1, 1, pairBinStepsArray, IERCArray, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, address(IERCArray[0]))); - router.swapAVAXForExactTokens(1, pairBinStepsArray, IERCArray, DEV, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, address(IERCArray[1]))); - router.swapExactTokensForAVAXSupportingFeeOnTransferTokens( - 1, 1, pairBinStepsArray, IERCArray, DEV, block.timestamp - ); - - vm.expectRevert(abi.encodeWithSelector(LBRouter__InvalidTokenPath.selector, address(IERCArray[0]))); - router.swapExactAVAXForTokensSupportingFeeOnTransferTokens( - 1, pairBinStepsArray, IERCArray, DEV, block.timestamp - ); - } - - function testSweepLBToken() public { - uint256 amountIn = 1e18; - - pair = createLBPairDefaultFees(usdc, weth); - (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, 5, 0); - - uint256[] memory amounts = new uint256[](5); - for (uint256 i; i < 5; i++) { - amounts[i] = pair.balanceOf(DEV, _ids[i]); - } - - assertEq(pair.balanceOf(DEV, ID_ONE - 1), amountIn / 3); - - pair.safeBatchTransferFrom(DEV, address(router), _ids, amounts); - - for (uint256 i; i < 5; i++) { - assertEq(pair.balanceOf(address(router), _ids[i]), amounts[i]); - assertEq(pair.balanceOf(DEV, _ids[i]), 0); - } - vm.prank(ALICE); - vm.expectRevert(LBRouter__NotFactoryOwner.selector); - router.sweepLBToken(pair, DEV, _ids, amounts); - - router.sweepLBToken(pair, DEV, _ids, amounts); - - for (uint256 i; i < 5; i++) { - assertEq(pair.balanceOf(DEV, _ids[i]), amounts[i]); - assertEq(pair.balanceOf(address(router), _ids[i]), 0); - } - } -} diff --git a/test_old/LBToken.t.sol b/test_old/LBToken.t.sol deleted file mode 100644 index db454eb7..00000000 --- a/test_old/LBToken.t.sol +++ /dev/null @@ -1,241 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; - -contract LiquidityBinTokenTest is TestHelper { - event TransferBatch( - address indexed sender, address indexed from, address indexed to, uint256[] ids, uint256[] amounts - ); - event TransferSingle(address indexed sender, address indexed from, address indexed to, uint256 id, uint256 amount); - - function setUp() public override { - usdc = new ERC20Mock(6); - weth = new ERC20Mock(18); - - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - addAllAssetsToQuoteWhitelist(factory); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - - pair = createLBPairDefaultFees(usdc, weth); - } - - function testSafeBatchTransferFrom() public { - uint256 amountIn = 1e18; - - (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, 5, 0); - - uint256[] memory amounts = new uint256[](5); - for (uint256 i; i < 5; i++) { - amounts[i] = pair.balanceOf(DEV, _ids[i]); - } - - assertEq(pair.balanceOf(DEV, ID_ONE - 1), amountIn / 3); - vm.expectEmit(true, true, true, true); - emit TransferBatch(DEV, DEV, ALICE, _ids, amounts); - pair.safeBatchTransferFrom(DEV, ALICE, _ids, amounts); - assertEq(pair.balanceOf(DEV, ID_ONE - 1), 0); - assertEq(pair.balanceOf(ALICE, ID_ONE - 1), amountIn / 3); - - vm.prank(ALICE); - pair.setApprovalForAll(BOB, true); - assertTrue(pair.isApprovedForAll(ALICE, BOB)); - assertFalse(pair.isApprovedForAll(BOB, ALICE)); - - vm.prank(BOB); - vm.expectEmit(true, true, true, true); - emit TransferBatch(BOB, ALICE, BOB, _ids, amounts); - pair.safeBatchTransferFrom(ALICE, BOB, _ids, amounts); - assertEq(pair.balanceOf(DEV, ID_ONE - 1), 0); // DEV - assertEq(pair.balanceOf(ALICE, ID_ONE - 1), 0); - assertEq(pair.balanceOf(BOB, ID_ONE - 1), amountIn / 3); - } - - function testSafeTransferFrom() public { - uint256 amountIn = 1e18; - - (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, 5, 0); - - uint256[] memory amounts = new uint256[](5); - for (uint256 i; i < 5; i++) { - amounts[i] = pair.balanceOf(DEV, _ids[i]); - } - - assertEq(pair.balanceOf(DEV, ID_ONE - 1), amountIn / 3); - vm.expectEmit(true, true, true, true); - emit TransferSingle(DEV, DEV, ALICE, _ids[0], amounts[0]); - pair.safeTransferFrom(DEV, ALICE, _ids[0], amounts[0]); - assertEq(pair.balanceOf(DEV, _ids[0]), 0); - assertEq(pair.balanceOf(ALICE, _ids[0]), amountIn / 3); - - vm.prank(ALICE); - pair.setApprovalForAll(BOB, true); - assertTrue(pair.isApprovedForAll(ALICE, BOB)); - assertFalse(pair.isApprovedForAll(BOB, ALICE)); - - vm.prank(BOB); - vm.expectEmit(true, true, true, true); - emit TransferSingle(BOB, ALICE, BOB, _ids[0], amounts[0]); - pair.safeTransferFrom(ALICE, BOB, _ids[0], amounts[0]); - assertEq(pair.balanceOf(DEV, _ids[0]), 0); - assertEq(pair.balanceOf(ALICE, _ids[0]), 0); - assertEq(pair.balanceOf(BOB, _ids[0]), amountIn / 3); - } - - function testSafeBatchTransferNotApprovedReverts() public { - uint256 amountIn = 1e18; - (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, 5, 0); - - uint256[] memory amounts = new uint256[](5); - for (uint256 i; i < 5; i++) { - amounts[i] = pair.balanceOf(DEV, _ids[i]); - } - - vm.prank(BOB); - vm.expectRevert(abi.encodeWithSelector(LBToken__SpenderNotApproved.selector, DEV, BOB)); - pair.safeBatchTransferFrom(DEV, BOB, _ids, amounts); - } - - function testSafeTransferNotApprovedReverts() public { - uint256 amountIn = 1e18; - (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, 5, 0); - - uint256[] memory amounts = new uint256[](5); - for (uint256 i; i < 5; i++) { - amounts[i] = pair.balanceOf(DEV, _ids[i]); - } - - vm.prank(BOB); - vm.expectRevert(abi.encodeWithSelector(LBToken__SpenderNotApproved.selector, DEV, BOB)); - pair.safeTransferFrom(DEV, BOB, _ids[0], amounts[0]); - } - - function testSafeBatchTransferFromReverts() public { - uint24 binAmount = 11; - uint256 amountIn = 1e18; - (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, binAmount, 0); - - uint256[] memory amounts = new uint256[](binAmount); - for (uint256 i; i < binAmount; i++) { - amounts[i] = pair.balanceOf(DEV, _ids[i]); - } - - vm.prank(BOB); - vm.expectRevert(abi.encodeWithSelector(LBToken__SpenderNotApproved.selector, DEV, BOB)); - pair.safeBatchTransferFrom(DEV, BOB, _ids, amounts); - - vm.prank(address(0)); - vm.expectRevert(LBToken__TransferFromOrToAddress0.selector); - pair.safeBatchTransferFrom(address(0), BOB, _ids, amounts); - - vm.prank(DEV); - vm.expectRevert(LBToken__TransferFromOrToAddress0.selector); - pair.safeBatchTransferFrom(DEV, address(0), _ids, amounts); - - amounts[0] += 1; - vm.expectRevert(abi.encodeWithSelector(LBToken__TransferExceedsBalance.selector, DEV, _ids[0], amounts[0])); - pair.safeBatchTransferFrom(DEV, ALICE, _ids, amounts); - - amounts[0] -= 1; //revert back to proper amount - _ids[1] = ID_ONE + binAmount; - vm.expectRevert(abi.encodeWithSelector(LBToken__TransferExceedsBalance.selector, DEV, _ids[1], amounts[1])); - pair.safeBatchTransferFrom(DEV, ALICE, _ids, amounts); - } - - function testSafeTransferFromReverts() public { - uint24 binAmount = 11; - uint256 amountIn = 1e18; - (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, binAmount, 0); - - uint256[] memory amounts = new uint256[](binAmount); - for (uint256 i; i < binAmount; i++) { - amounts[i] = pair.balanceOf(DEV, _ids[i]); - } - - vm.prank(BOB); - vm.expectRevert(abi.encodeWithSelector(LBToken__SpenderNotApproved.selector, DEV, BOB)); - pair.safeTransferFrom(DEV, BOB, _ids[0], amounts[0]); - - vm.prank(address(0)); - vm.expectRevert(LBToken__TransferFromOrToAddress0.selector); - pair.safeTransferFrom(address(0), BOB, _ids[0], amounts[0]); - - vm.prank(DEV); - vm.expectRevert(LBToken__TransferFromOrToAddress0.selector); - pair.safeTransferFrom(DEV, address(0), _ids[0], amounts[0]); - - amounts[0] += 1; - vm.expectRevert(abi.encodeWithSelector(LBToken__TransferExceedsBalance.selector, DEV, _ids[0], amounts[0])); - pair.safeTransferFrom(DEV, ALICE, _ids[0], amounts[0]); - - _ids[1] = ID_ONE + binAmount; - vm.expectRevert(abi.encodeWithSelector(LBToken__TransferExceedsBalance.selector, DEV, _ids[1], amounts[1])); - pair.safeTransferFrom(DEV, ALICE, _ids[1], amounts[1]); - } - - function testModifierCheckLength() public { - uint24 binAmount = 11; - uint256 amountIn = 1e18; - (uint256[] memory _ids,,,) = addLiquidity(amountIn, ID_ONE, binAmount, 0); - - uint256[] memory amounts = new uint256[](binAmount - 1); - for (uint256 i; i < binAmount - 1; i++) { - amounts[i] = pair.balanceOf(DEV, _ids[i]); - } - - vm.expectRevert(abi.encodeWithSelector(LBToken__LengthMismatch.selector, _ids.length, amounts.length)); - pair.safeBatchTransferFrom(DEV, ALICE, _ids, amounts); - - address[] memory accounts = new address[](binAmount - 1); - for (uint256 i; i < binAmount - 1; i++) { - accounts[i] = DEV; - } - vm.expectRevert(abi.encodeWithSelector(LBToken__LengthMismatch.selector, accounts.length, _ids.length)); - pair.balanceOfBatch(accounts, _ids); - } - - function testSelfApprovalReverts() public { - vm.expectRevert(abi.encodeWithSelector(LBToken__SelfApproval.selector, DEV)); - pair.setApprovalForAll(DEV, true); - } - - function testPrivateViewFunctions() public { - assertEq(pair.name(), "Liquidity Book Token"); - assertEq(pair.symbol(), "LBT"); - } - - function testBalanceOfBatch() public { - uint24 binAmount = 5; - uint256 amountIn = 1e18; - uint24 _startId = ID_ONE; - uint24 _gap = 0; - uint256[] memory batchBalances = new uint256[](binAmount); - - uint256[] memory _ids = new uint256[](binAmount); - for (uint256 i; i < binAmount / 2; i++) { - _ids[i] = _startId - (binAmount / 2) * (1 + _gap) + i * (1 + _gap); - } - - address[] memory accounts = new address[](binAmount); - for (uint256 i; i < binAmount; i++) { - accounts[i] = DEV; - } - batchBalances = pair.balanceOfBatch(accounts, _ids); - for (uint256 i; i < binAmount; i++) { - assertEq(batchBalances[i], 0); - } - - (_ids,,,) = addLiquidity(amountIn, _startId, binAmount, _gap); - uint256[] memory amounts = new uint256[](binAmount); - for (uint256 i; i < binAmount; i++) { - amounts[i] = pair.balanceOf(DEV, _ids[i]); - } - batchBalances = pair.balanceOfBatch(accounts, _ids); - for (uint256 i; i < binAmount; i++) { - assertEq(batchBalances[i], amounts[i]); - } - } -} diff --git a/test_old/LBTokenInternal.t.sol b/test_old/LBTokenInternal.t.sol deleted file mode 100644 index dcb731c7..00000000 --- a/test_old/LBTokenInternal.t.sol +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.10; - -import "test/helpers/TestHelper.sol"; -import "../src/LBToken.sol"; - -contract LiquidityBinTokenTest is TestHelper, LBToken { - function setUp() public override { - usdc = new ERC20Mock(6); - weth = new ERC20Mock(18); - - factory = new LBFactory(DEV, 8e14); - ILBPair _LBPairImplementation = new LBPair(factory); - factory.setLBPairImplementation(address(_LBPairImplementation)); - addAllAssetsToQuoteWhitelist(factory); - setDefaultFactoryPresets(DEFAULT_BIN_STEP); - - pair = createLBPairDefaultFees(usdc, weth); - } - - function testInternalMintTo0AddressReverts() public { - vm.expectRevert(LBToken__MintToAddress0.selector); - _mint(address(0), 2 ** 23, 1000); - } - - function testInternalMint(uint256 mintAmount) public { - uint256 binNumber = 2 ** 23; - uint256 totalSupplyBefore = totalSupply(binNumber); - uint256 balanceBefore = balanceOf(ALICE, binNumber); - vm.expectEmit(true, true, true, true); - // The event we expect - emit TransferSingle(msg.sender, address(0), ALICE, binNumber, mintAmount); - _mint(ALICE, binNumber, mintAmount); - - assertEq(balanceOf(ALICE, binNumber), balanceBefore + mintAmount); - assertEq(totalSupply(binNumber), totalSupplyBefore + mintAmount); - } - - function testInternalBurnFrom0AddressReverts() public { - vm.expectRevert(LBToken__BurnFromAddress0.selector); - _burn(address(0), 2 ** 23, 1000); - } - - function testInternalExcessiveBurnAmountReverts(uint128 mintAmount, uint128 excessiveBurnAmount) public { - vm.assume(excessiveBurnAmount > 0); - uint256 burnAmount = uint256(mintAmount) + uint256(excessiveBurnAmount); - uint256 binNumber = 2 ** 23; - _mint(ALICE, binNumber, mintAmount); - vm.expectRevert(abi.encodeWithSelector(LBToken__BurnExceedsBalance.selector, ALICE, binNumber, burnAmount)); - _burn(ALICE, binNumber, burnAmount); - } - - function testInternalBurn(uint256 mintAmount, uint256 burnAmount) public { - vm.assume(mintAmount > 0 && burnAmount > 0); - vm.assume(mintAmount >= burnAmount); - uint256 binNumber = 2 ** 23; - - _mint(ALICE, binNumber, mintAmount); - - uint256 totalSupplyBefore = totalSupply(binNumber); - uint256 balanceBefore = balanceOf(ALICE, binNumber); - - vm.expectEmit(true, true, true, true); - // The event we expect - emit TransferSingle(msg.sender, ALICE, address(0), binNumber, burnAmount); - _burn(ALICE, binNumber, burnAmount); - - assertEq(balanceOf(ALICE, binNumber), balanceBefore - burnAmount); - assertEq(totalSupply(binNumber), totalSupplyBefore - burnAmount); - } - - function testInternalApproval() public { - vm.expectEmit(true, true, true, true); - emit ApprovalForAll(DEV, ALICE, true); - _setApprovalForAll(DEV, ALICE, true); - assertEq(_isApprovedForAll(DEV, ALICE), true); - - vm.expectEmit(true, true, true, true); - emit ApprovalForAll(DEV, ALICE, false); - _setApprovalForAll(DEV, ALICE, false); - assertEq(_isApprovedForAll(DEV, ALICE), false); - } - - function supportsInterface(bytes4 interfaceId) public view override(TestHelper, LBToken) returns (bool) { - return interfaceId == type(ILBToken).interfaceId; - } -} From 7b3b3c1a9a03589d5acfc4ffe2715d3efd7b3233 Mon Sep 17 00:00:00 2001 From: Louis <107303182+0x0Louis@users.noreply.github.com> Date: Tue, 14 Feb 2023 14:26:31 +0100 Subject: [PATCH 22/47] Rename and clean up (#87) * remove unused libs * rename the approval function --- src/LBToken.sol | 6 +++--- src/interfaces/ILBToken.sol | 2 +- src/libraries/math/PackedUint128Math.sol | 4 ---- src/libraries/math/Uint128x128Math.sol | 2 -- src/libraries/math/Uint256x256Math.sol | 4 ---- test/LBRouter.Liquidity.t.sol | 8 ++++---- test/LBToken.t.sol | 6 +++--- 7 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/LBToken.sol b/src/LBToken.sol index bc7fe862..ede1db05 100644 --- a/src/LBToken.sol +++ b/src/LBToken.sol @@ -129,8 +129,8 @@ contract LBToken is ILBToken { * @param spender The address of the spender. * @param approved The boolean value to grant or revoke permission. */ - function setApprovalForAll(address spender, bool approved) public virtual override { - _setApprovalForAll(msg.sender, spender, approved); + function approveForAll(address spender, bool approved) public virtual override { + _approveForAll(msg.sender, spender, approved); } /** @@ -261,7 +261,7 @@ contract LBToken is ILBToken { * @param spender The address of the spender * @param approved The boolean value to grant or revoke permission */ - function _setApprovalForAll(address owner, address spender, bool approved) internal { + function _approveForAll(address owner, address spender, bool approved) internal { if (owner == spender) revert LBToken__SelfApproval(owner); _spenderApprovals[owner][spender] = approved; diff --git a/src/interfaces/ILBToken.sol b/src/interfaces/ILBToken.sol index e20c67c8..06352194 100644 --- a/src/interfaces/ILBToken.sol +++ b/src/interfaces/ILBToken.sol @@ -36,7 +36,7 @@ interface ILBToken { function isApprovedForAll(address owner, address spender) external view returns (bool); - function setApprovalForAll(address spender, bool approved) external; + function approveForAll(address spender, bool approved) external; function batchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts) external; } diff --git a/src/libraries/math/PackedUint128Math.sol b/src/libraries/math/PackedUint128Math.sol index e68624e1..ed6e79b0 100644 --- a/src/libraries/math/PackedUint128Math.sol +++ b/src/libraries/math/PackedUint128Math.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.10; import {Constants} from "../Constants.sol"; -import {SafeCast} from "./SafeCast.sol"; /** * @title Liquidity Book Packed Uint128 Math Library @@ -12,11 +11,8 @@ import {SafeCast} from "./SafeCast.sol"; * and interact with the encoded bytes32. */ library PackedUint128Math { - using SafeCast for uint256; - error PackedUint128Math__AddOverflow(); error PackedUint128Math__SubUnderflow(); - error PackedUint128Math__AddFirstSubSecondOverflow(); error PackedUint128Math__MultiplierTooLarge(); uint256 private constant OFFSET = 128; diff --git a/src/libraries/math/Uint128x128Math.sol b/src/libraries/math/Uint128x128Math.sol index 510005f6..3ac8712e 100644 --- a/src/libraries/math/Uint128x128Math.sol +++ b/src/libraries/math/Uint128x128Math.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.10; import {Constants} from "../Constants.sol"; import {BitMath} from "./BitMath.sol"; -import {Uint256x256Math} from "./Uint256x256Math.sol"; /** * @title Liquidity Book Uint128x128 Math Library @@ -12,7 +11,6 @@ import {Uint256x256Math} from "./Uint256x256Math.sol"; * @notice Helper contract used for power and log calculations */ library Uint128x128Math { - using Uint256x256Math for uint256; using BitMath for uint256; error Uint128x128Math__LogUnderflow(); diff --git a/src/libraries/math/Uint256x256Math.sol b/src/libraries/math/Uint256x256Math.sol index 7322b8b9..817543df 100644 --- a/src/libraries/math/Uint256x256Math.sol +++ b/src/libraries/math/Uint256x256Math.sol @@ -2,16 +2,12 @@ pragma solidity 0.8.10; -import {BitMath} from "./BitMath.sol"; - /** * @title Liquidity Book Uint256x256 Math Library * @author Trader Joe * @notice Helper contract used for full precision calculations */ library Uint256x256Math { - using BitMath for uint256; - error Uint256x256Math__MulShiftOverflow(); error Uint256x256Math__MulDivOverflow(); diff --git a/test/LBRouter.Liquidity.t.sol b/test/LBRouter.Liquidity.t.sol index 402ef85a..e375fda0 100644 --- a/test/LBRouter.Liquidity.t.sol +++ b/test/LBRouter.Liquidity.t.sol @@ -328,7 +328,7 @@ contract LiquidityBinRouterTest is TestHelper { ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP).LBPair; - pair.setApprovalForAll(address(router), true); + pair.approveForAll(address(router), true); (uint256 amountXOut, uint256 amountYOut) = router.removeLiquidity( usdt, usdc, DEFAULT_BIN_STEP, 0, 0, depositIds, liquidityMinted, address(this), block.timestamp @@ -382,7 +382,7 @@ contract LiquidityBinRouterTest is TestHelper { router.addLiquidity(liquidityParameters); ILBPair pair = factory.getLBPairInformation(usdt, usdc, DEFAULT_BIN_STEP).LBPair; - pair.setApprovalForAll(address(router), true); + pair.approveForAll(address(router), true); // Revert if the deadline is passed vm.expectRevert( @@ -441,7 +441,7 @@ contract LiquidityBinRouterTest is TestHelper { router.addLiquidityNATIVE{value: liquidityParameters.amountX}(liquidityParameters); ILBPair pair = factory.getLBPairInformation(wnative, usdc, DEFAULT_BIN_STEP).LBPair; - pair.setApprovalForAll(address(router), true); + pair.approveForAll(address(router), true); uint256 balanceNATIVEBefore = address(this).balance; uint256 balanceUSDCBefore = usdc.balanceOf(address(this)); @@ -470,7 +470,7 @@ contract LiquidityBinRouterTest is TestHelper { router.addLiquidityNATIVE{value: liquidityParameters.amountX}(liquidityParameters); ILBPair pair = factory.getLBPairInformation(wnative, usdc, DEFAULT_BIN_STEP).LBPair; - pair.setApprovalForAll(address(router), true); + pair.approveForAll(address(router), true); // Revert if the deadline is passed vm.expectRevert( diff --git a/test/LBToken.t.sol b/test/LBToken.t.sol index 96e74178..76e82bdf 100644 --- a/test/LBToken.t.sol +++ b/test/LBToken.t.sol @@ -236,7 +236,7 @@ contract LBTokenTest is Test { vm.stopPrank(); vm.startPrank(from); - lbToken.setApprovalForAll(to, true); + lbToken.approveForAll(to, true); vm.stopPrank(); assertEq(lbToken.isApprovedForAll(from, to), true, "testFuzz_ApprovedForAll::1"); @@ -249,7 +249,7 @@ contract LBTokenTest is Test { assertEq(lbToken.balanceOf(to, id), amount, "testFuzz_ApprovedForAll::3"); vm.startPrank(from); - lbToken.setApprovalForAll(to, false); + lbToken.approveForAll(to, false); vm.stopPrank(); assertEq(lbToken.isApprovedForAll(from, to), false, "testFuzz_ApprovedForAll::4"); @@ -292,7 +292,7 @@ contract LBTokenTest is Test { vm.startPrank(account); vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__SelfApproval.selector, account)); - lbToken.setApprovalForAll(account, true); + lbToken.approveForAll(account, true); vm.stopPrank(); assertEq(lbToken.isApprovedForAll(account, account), true, "testFuzz_SetApprovalOnSelf::1"); From 0023a81118c23dcea4d4c499af1762ad6606dc7d Mon Sep 17 00:00:00 2001 From: Mathieu <85969303+Mathieu-Be@users.noreply.github.com> Date: Wed, 22 Feb 2023 15:44:59 +0100 Subject: [PATCH 23/47] update deploy script (#88) * update deploy script * small fixes --- foundry.toml | 8 ++ script/config/bips-config.sol | 2 +- script/config/deployments.json | 18 +++ script/deploy-core.s.sol | 167 +++++++++++++++++----------- src/interfaces/ILBLegacyFactory.sol | 2 +- 5 files changed, 129 insertions(+), 68 deletions(-) create mode 100644 script/config/deployments.json diff --git a/foundry.toml b/foundry.toml index 4f8acd4d..98570226 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,6 +5,8 @@ libs = ['lib'] optimizer = true optimizer_runs = 800 +fs_permissions = [{ access = "read", path = "./"}] + [fuzz] runs = 1024 @@ -12,4 +14,10 @@ runs = 1024 avalanche = "https://api.avax.network/ext/bc/C/rpc" fuji = "https://api.avax-test.network/ext/bc/C/rpc" +[etherscan] +arbitrum = { key = "${ARBISCAN_API_KEY}", chain = 42161 } +avalanche = { key = "${SNOWTRACE_API_KEY}", chain = 43114 } +arbitrum_goerli = { key = "${ARBISCAN_API_KEY}", chain = 421613 } +fuji = { key = "${SNOWTRACE_API_KEY}", chain = 43113 } + # See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/script/config/bips-config.sol b/script/config/bips-config.sol index be6eb479..8456316f 100644 --- a/script/config/bips-config.sol +++ b/script/config/bips-config.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; library BipsConfig { struct FactoryPreset { - uint16 binStep; + uint8 binStep; uint16 baseFactor; uint16 filterPeriod; uint16 decayPeriod; diff --git a/script/config/deployments.json b/script/config/deployments.json new file mode 100644 index 00000000..9c5d024c --- /dev/null +++ b/script/config/deployments.json @@ -0,0 +1,18 @@ +{ + "avalanche_fuji": { + "factory_v1": "0xF5c7d9733e5f53abCC1695820c4818C59B457C2C", + "factory_v2": "0x6B8E020098cd1B3Ec9f811024bc24e51C660F768", + "multisig": "0xFFC08538077a0455E0F4077823b1A0E3e18Faf0b", + "router_v1": "0xd7f655E3376cE2D7A2b08fF01Eb3B1023191A901", + "router_v2": "0x7b50046cEC8252ca835b148b1eDD997319120a12", + "w_native": "0xd00ae08403B9bbb9124bB305C09058E32C39A48c" + }, + "arbitrum_one_goerli": { + "factory_v1": "0x1886D09C9Ade0c5DB822D85D21678Db67B6c2982", + "factory_v2": "0xC8Af41e49e2C03eA14706C7aa9cEE60454bc5c03", + "multisig": "0xbeE5c10Cf6E4F68f831E11C1D9E59B43560B3642", + "router_v1": "0x454206AD825cAfaE03c9581014AF6b74f7D53713", + "router_v2": "0x6E9603f925FB5A74f7321f51499d9633c1252893", + "w_native": "0xaE4EC9901c3076D0DdBe76A520F9E90a6227aCB7" + } +} diff --git a/script/deploy-core.s.sol b/script/deploy-core.s.sol index 0ba4acf0..4deedac4 100644 --- a/script/deploy-core.s.sol +++ b/script/deploy-core.s.sol @@ -4,73 +4,108 @@ pragma solidity 0.8.10; import "forge-std/Script.sol"; -import "src/LBFactory.sol"; -import "src/LBRouter.sol"; -import "src/LBPair.sol"; -import "src/LBQuoter.sol"; +import {ILBFactory, LBFactory} from "src/LBFactory.sol"; +import {ILBRouter, IJoeFactory, ILBLegacyFactory, ILBLegacyRouter, IWNATIVE, LBRouter} from "src/LBRouter.sol"; +import {IERC20, LBPair} from "src/LBPair.sol"; +import {LBQuoter} from "src/LBQuoter.sol"; -import "./config/bips-config.sol"; +import {BipsConfig} from "./config/bips-config.sol"; contract CoreDeployer is Script { -// address private constant WAVAX_AVALANCHE = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7; -// address private constant WAVAX_FUJI = 0xd00ae08403B9bbb9124bB305C09058E32C39A48c; - -// address private constant FACTORY_V1_AVALANCHE = 0x9Ad6C38BE94206cA50bb0d90783181662f0Cfa10; -// address private constant FACTORY_V1_FUJI = 0xF5c7d9733e5f53abCC1695820c4818C59B457C2C; - -// address private wavax; -// address private factoryV1; - -// uint256 private constant FLASHLOAN_FEE = 5e12; - -// function run() external { -// if (block.chainid == 43114) { -// wavax = WAVAX_AVALANCHE; -// factoryV1 = FACTORY_V1_AVALANCHE; -// } else { -// wavax = WAVAX_FUJI; -// factoryV1 = FACTORY_V1_FUJI; -// } - -// vm.broadcast(); -// LBFactory factory = new LBFactory(msg.sender, FLASHLOAN_FEE); -// console.log("LBFactory deployed -->", address(factory)); - -// vm.broadcast(); -// LBPair pairImplementation = new LBPair(factory); -// console.log("LBPair implementation deployed -->", address(pairImplementation)); - -// vm.broadcast(); -// LBRouter router = new LBRouter(factory, IJoeFactory(factoryV1), IWAVAX(wavax)); -// console.log("LBRouter deployed -->", address(router)); - -// vm.startBroadcast(); -// LBQuoter quoter = new LBQuoter(address(router), address(factoryV1), address(factory)); -// console.log("LBQuoter deployed -->", address(quoter)); - -// factory.setLBPairImplementation(address(pairImplementation)); -// console.log("LBPair implementation set on factory"); - -// factory.addQuoteAsset(IERC20(wavax)); -// console.log("Wavax whitelisted as quote asset"); -// vm.stopBroadcast(); - -// vm.startBroadcast(); -// uint256[] memory presetList = BipsConfig.getPresetList(); -// for (uint256 i; i < presetList.length; i++) { -// BipsConfig.FactoryPreset memory preset = BipsConfig.getPreset(presetList[i]); -// factory.setPreset( -// preset.binStep, -// preset.baseFactor, -// preset.filterPeriod, -// preset.decayPeriod, -// preset.reductionFactor, -// preset.variableFeeControl, -// preset.protocolShare, -// preset.maxVolatilityAccumulated, -// preset.sampleLifetime -// ); -// } -// vm.stopBroadcast(); -// } + using stdJson for string; + + uint256 private constant FLASHLOAN_FEE = 5e12; + + struct Deployment { + address factoryV1; + address factoryV2; + address multisig; + address routerV1; + address routerV2; + address wNative; + } + + string[] chains = ["avalanche_fuji", "arbitrum_one_goerli"]; + + function setUp() public { + _overwriteDefaultArbitrumRPC(); + } + + function run() public { + string memory json = vm.readFile("script/config/deployments.json"); + address deployer = vm.rememberKey(vm.envUint("DEPLOYER_PRIVATE_KEY")); + + console.log("Deployer address: %s", deployer); + + for (uint256 i = 0; i < chains.length; i++) { + bytes memory rawDeploymentData = json.parseRaw(string(abi.encodePacked(".", chains[i]))); + Deployment memory deployment = abi.decode(rawDeploymentData, (Deployment)); + + console.log("\nDeploying V2.1 on %s", chains[i]); + + vm.createSelectFork(StdChains.getChain(chains[i]).rpcUrl); + + vm.broadcast(deployer); + LBFactory factory = new LBFactory(deployer, FLASHLOAN_FEE); + console.log("LBFactory deployed -->", address(factory)); + + vm.broadcast(deployer); + LBPair pairImplementation = new LBPair(factory); + console.log("LBPair implementation deployed -->", address(pairImplementation)); + + vm.broadcast(deployer); + LBRouter router = new LBRouter( + factory, + IJoeFactory(deployment.factoryV1), + ILBLegacyFactory(deployment.factoryV2), + ILBLegacyRouter(deployment.routerV2), + IWNATIVE(deployment.wNative) + ); + console.log("LBRouter deployed -->", address(router)); + + vm.startBroadcast(deployer); + LBQuoter quoter = + new LBQuoter(deployment.factoryV1, deployment.factoryV2, address(factory),deployment.routerV2, address(router)); + console.log("LBQuoter deployed -->", address(quoter)); + + factory.setLBPairImplementation(address(pairImplementation)); + console.log("LBPair implementation set on factory\n"); + + uint256 quoteAssets = ILBLegacyFactory(deployment.factoryV2).getNumberOfQuoteAssets(); + for (uint256 j = 0; j < quoteAssets; j++) { + IERC20 quoteAsset = ILBLegacyFactory(deployment.factoryV2).getQuoteAsset(j); + factory.addQuoteAsset(quoteAsset); + console.log("Quote asset whitelisted -->", address(quoteAsset)); + } + + uint256[] memory presetList = BipsConfig.getPresetList(); + for (uint256 j; j < presetList.length; j++) { + BipsConfig.FactoryPreset memory preset = BipsConfig.getPreset(presetList[j]); + factory.setPreset( + preset.binStep, + preset.baseFactor, + preset.filterPeriod, + preset.decayPeriod, + preset.reductionFactor, + preset.variableFeeControl, + preset.protocolShare, + preset.maxVolatilityAccumulated + ); + } + + factory.setPendingOwner(deployment.multisig); + vm.stopBroadcast(); + } + } + + function _overwriteDefaultArbitrumRPC() private { + StdChains.setChain( + "arbitrum_one_goerli", + StdChains.ChainData({ + name: "Arbitrum One Goerli", + chainId: 421613, + rpcUrl: vm.envString("ARBITRUM_TESTNET_RPC_URL") + }) + ); + } } diff --git a/src/interfaces/ILBLegacyFactory.sol b/src/interfaces/ILBLegacyFactory.sol index a6e12ad5..4642233c 100644 --- a/src/interfaces/ILBLegacyFactory.sol +++ b/src/interfaces/ILBLegacyFactory.sol @@ -80,7 +80,7 @@ interface ILBLegacyFactory is IPendingOwnable { function getNumberOfQuoteAssets() external view returns (uint256); - function getQuoteAssetAtIndex(uint256 index) external view returns (IERC20); + function getQuoteAsset(uint256 index) external view returns (IERC20); function isQuoteAsset(IERC20 token) external view returns (bool); From 974ee4760ac4343dd453a4b491a61fd99d31768b Mon Sep 17 00:00:00 2001 From: Louis <107303182+0x0Louis@users.noreply.github.com> Date: Wed, 22 Feb 2023 15:52:30 +0100 Subject: [PATCH 24/47] More binsteps (#89) * typo * take bin step as uint16 to allow for bigger binsteps * fix tests * typo * leftover todo comment * update config to allow setting the `isOpen` bool --- script/config/bips-config.sol | 10 +- script/deploy-core.s.sol | 3 +- src/LBFactory.sol | 276 +++++++++-------------- src/LBPair.sol | 26 +-- src/LBQuoter.sol | 8 +- src/LBRouter.sol | 6 +- src/interfaces/ILBFactory.sol | 56 +++-- src/interfaces/ILBPair.sol | 2 +- src/interfaces/ILBRouter.sol | 6 +- src/libraries/BinHelper.sol | 4 +- src/libraries/Clone.sol | 13 ++ src/libraries/Constants.sol | 1 - src/libraries/FeeHelper.sol | 6 +- src/libraries/PairParameterHelper.sol | 22 +- src/libraries/PriceHelper.sol | 8 +- src/libraries/math/Encoded.sol | 20 +- src/libraries/math/Uint128x128Math.sol | 4 +- test/LBFactory.t.sol | 171 +++++++------- test/LBPairFees.t.sol | 7 +- test/LBPairImplementation.t.sol | 2 +- test/LBRouter.Liquidity.t.sol | 4 +- test/LBRouter.Swap.t.sol | 2 +- test/helpers/TestHelper.sol | 14 +- test/libraries/BinHelper.t.sol | 2 +- test/libraries/ImmutableClone.t.sol | 6 +- test/libraries/PairParameterHelper.t.sol | 6 +- test/libraries/PriceHelper.t.sol | 27 ++- test/libraries/math/Encoded.t.sol | 6 +- 28 files changed, 348 insertions(+), 370 deletions(-) diff --git a/script/config/bips-config.sol b/script/config/bips-config.sol index 8456316f..153f6d0d 100644 --- a/script/config/bips-config.sol +++ b/script/config/bips-config.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; library BipsConfig { struct FactoryPreset { - uint8 binStep; + uint16 binStep; uint16 baseFactor; uint16 filterPeriod; uint16 decayPeriod; @@ -13,6 +13,7 @@ library BipsConfig { uint16 protocolShare; uint24 maxVolatilityAccumulated; uint16 sampleLifetime; + bool isOpen; } function getPreset(uint256 _bp) internal pure returns (FactoryPreset memory preset) { @@ -26,6 +27,7 @@ library BipsConfig { preset.protocolShare = 0; preset.maxVolatilityAccumulated = 100_000; preset.sampleLifetime = 120; + preset.isOpen = false; } else if (_bp == 2) { preset.binStep = 2; preset.baseFactor = 15_000; @@ -36,6 +38,7 @@ library BipsConfig { preset.protocolShare = 0; preset.maxVolatilityAccumulated = 250_000; preset.sampleLifetime = 120; + preset.isOpen = false; } else if (_bp == 5) { preset.binStep = 5; preset.baseFactor = 8_000; @@ -46,6 +49,7 @@ library BipsConfig { preset.protocolShare = 0; preset.maxVolatilityAccumulated = 300_000; preset.sampleLifetime = 120; + preset.isOpen = false; } else if (_bp == 10) { preset.binStep = 10; preset.baseFactor = 10_000; @@ -56,6 +60,7 @@ library BipsConfig { preset.protocolShare = 0; preset.maxVolatilityAccumulated = 350_000; preset.sampleLifetime = 120; + preset.isOpen = false; } else if (_bp == 15) { preset.binStep = 15; preset.baseFactor = 10_000; @@ -66,6 +71,7 @@ library BipsConfig { preset.protocolShare = 0; preset.maxVolatilityAccumulated = 350_000; preset.sampleLifetime = 120; + preset.isOpen = false; } else if (_bp == 20) { preset.binStep = 20; preset.baseFactor = 10_000; @@ -76,6 +82,7 @@ library BipsConfig { preset.protocolShare = 0; preset.maxVolatilityAccumulated = 350_000; preset.sampleLifetime = 120; + preset.isOpen = false; } else if (_bp == 25) { preset.binStep = 25; preset.baseFactor = 10_000; @@ -86,6 +93,7 @@ library BipsConfig { preset.protocolShare = 0; preset.maxVolatilityAccumulated = 350_000; preset.sampleLifetime = 120; + preset.isOpen = false; } } diff --git a/script/deploy-core.s.sol b/script/deploy-core.s.sol index 4deedac4..9ca46b31 100644 --- a/script/deploy-core.s.sol +++ b/script/deploy-core.s.sol @@ -89,7 +89,8 @@ contract CoreDeployer is Script { preset.reductionFactor, preset.variableFeeControl, preset.protocolShare, - preset.maxVolatilityAccumulated + preset.maxVolatilityAccumulated, + preset.isOpen ); } diff --git a/src/LBFactory.sol b/src/LBFactory.sol index 70c80d45..e32cf4d2 100644 --- a/src/LBFactory.sol +++ b/src/LBFactory.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.10; import {EnumerableSet} from "openzeppelin/utils/structs/EnumerableSet.sol"; +import {EnumerableMap} from "openzeppelin/utils/structs/EnumerableMap.sol"; import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; import {BinHelper, PairParameterHelper} from "./libraries/BinHelper.sol"; @@ -21,18 +22,22 @@ import {ILBPair} from "./interfaces/ILBPair.sol"; * @author Trader Joe * @notice Contract used to deploy and register new LBPairs. * Enables setting fee parameters, flashloan fees and LBPair implementation. - * Unless the `_isPresetOpen` is `true`, only the owner of the factory can create pairs. + * Unless the `isOpen` is `true`, only the owner of the factory can create pairs. */ contract LBFactory is PendingOwnable, ILBFactory { using SafeCast for uint256; using Encoded for bytes32; using PairParameterHelper for bytes32; using EnumerableSet for EnumerableSet.AddressSet; + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableMap for EnumerableMap.UintToUintMap; - uint256 private constant _MIN_BIN_STEP = 1; // 0.01% - uint256 private constant _MAX_BIN_STEP = 200; // 1%, can't be greater than 247 for indexing reasons + uint256 private constant _OFFSET_IS_PRESET_OPEN = 255; + + uint256 private constant _MIN_BIN_STEP = 1; // 0.001% uint256 private constant _MAX_FLASHLOAN_FEE = 0.1e18; // 10% + address private _feeRecipient; uint256 private _flashLoanFee; @@ -40,35 +45,20 @@ contract LBFactory is PendingOwnable, ILBFactory { ILBPair[] private _allLBPairs; - uint256 private constant _TRUE = 1; - uint256 private constant _FALSE = 0; - /** * @dev Mapping from a (tokenA, tokenB, binStep) to a LBPair. The tokens are ordered to save gas, but they can be * in the reverse order in the actual pair. Always query one of the 2 tokens of the pair to assert the order of the 2 tokens */ mapping(IERC20 => mapping(IERC20 => mapping(uint256 => LBPairInformation))) private _lbPairsInfo; - /** - * @dev Whether a preset was set or not, if the bit at `index` is 1, it means that the binStep `index` was set - * The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas - */ - bytes32 private _availablePresets; - - /** - * @dev Whether a preset is open to anyone to create pairs, if the bit at `index` is 1, it means that the binStep `index` was set - */ - bytes32 private _openPresets; - - mapping(uint256 => bytes32) private _presets; - + EnumerableMap.UintToUintMap private _presets; EnumerableSet.AddressSet private _quoteAssetWhitelist; /** * @dev Whether a LBPair was created with a bin step, if the bit at `index` is 1, it means that the LBPair with binStep `index` exists * The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas */ - mapping(IERC20 => mapping(IERC20 => bytes32)) private _availableLBPairBinSteps; + mapping(IERC20 => mapping(IERC20 => EnumerableSet.UintSet)) private _availableLBPairBinSteps; /** * @notice Constructor @@ -89,23 +79,15 @@ contract LBFactory is PendingOwnable, ILBFactory { * @notice Get the minimum bin step a pair can have * @return minBinStep */ - function getMinBinStep() external pure returns (uint256 minBinStep) { + function getMinBinStep() external pure override returns (uint256 minBinStep) { return _MIN_BIN_STEP; } - /** - * @notice Get the maximum bin step a pair can have - * @return maxBinStep - */ - function getMaxBinStep() external pure returns (uint256 maxBinStep) { - return _MAX_BIN_STEP; - } - /** * @notice Get the protocol fee recipient * @return feeRecipient */ - function getFeeRecipient() external view returns (address feeRecipient) { + function getFeeRecipient() external view override returns (address feeRecipient) { return _feeRecipient; } @@ -113,7 +95,7 @@ contract LBFactory is PendingOwnable, ILBFactory { * @notice Get the maximum fee percentage for flashLoans * @return maxFee */ - function getMaxFlashLoanFee() external pure returns (uint256 maxFee) { + function getMaxFlashLoanFee() external pure override returns (uint256 maxFee) { return _MAX_FLASHLOAN_FEE; } @@ -121,7 +103,7 @@ contract LBFactory is PendingOwnable, ILBFactory { * @notice Get the fee for flash loans * @return flashloanFee */ - function getFlashLoanFee() external view returns (uint256 flashloanFee) { + function getFlashLoanFee() external view override returns (uint256 flashloanFee) { return _flashLoanFee; } @@ -129,7 +111,7 @@ contract LBFactory is PendingOwnable, ILBFactory { * @notice Get the address of the LBPair implementation * @return lbPairImplementation */ - function getLBPairImplementation() external view returns (address lbPairImplementation) { + function getLBPairImplementation() external view override returns (address lbPairImplementation) { return _lbPairImplementation; } @@ -146,7 +128,7 @@ contract LBFactory is PendingOwnable, ILBFactory { * @param index The index * @return lbPair The address of the LBPair at index `index` */ - function getLBPairAtIndex(uint256 index) external view returns (ILBPair lbPair) { + function getLBPairAtIndex(uint256 index) external view override returns (ILBPair lbPair) { return _allLBPairs[index]; } @@ -187,6 +169,7 @@ contract LBFactory is PendingOwnable, ILBFactory { function getLBPairInformation(IERC20 tokenA, IERC20 tokenB, uint256 binStep) external view + override returns (LBPairInformation memory lbPairInformation) { return _getLBPairInformation(tokenA, tokenB, binStep); @@ -194,6 +177,7 @@ contract LBFactory is PendingOwnable, ILBFactory { /** * @notice View function to return the different parameters of the preset + * Will revert if the preset doesn't exist * @param binStep The bin step of the preset * @return baseFactor The base factor * @return filterPeriod The filter period of the preset @@ -202,6 +186,7 @@ contract LBFactory is PendingOwnable, ILBFactory { * @return variableFeeControl The variable fee control of the preset * @return protocolShare The protocol share of the preset * @return maxVolatilityAccumulator The max volatility accumulator of the preset + * @return isOpen Whether the preset is open or not */ function getPreset(uint256 binStep) external @@ -214,11 +199,13 @@ contract LBFactory is PendingOwnable, ILBFactory { uint256 reductionFactor, uint256 variableFeeControl, uint256 protocolShare, - uint256 maxVolatilityAccumulator + uint256 maxVolatilityAccumulator, + bool isOpen ) { - bytes32 preset = _presets[binStep]; - if (preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(binStep); + if (!_presets.contains(binStep)) revert LBFactory__BinStepHasNoPreset(binStep); + + bytes32 preset = bytes32(_presets.get(binStep)); baseFactor = preset.getBaseFactor(); filterPeriod = preset.getFilterPeriod(); @@ -227,17 +214,8 @@ contract LBFactory is PendingOwnable, ILBFactory { variableFeeControl = preset.getVariableFeeControl(); protocolShare = preset.getProtocolShare(); maxVolatilityAccumulator = preset.getMaxVolatilityAccumulator(); - } - - /** - * @notice View function to return whether a preset is available to anyone for pair creation (true) or not (false) - * @param binStep The bin step of the preset - * @return isAvailable Whether the preset is available or not - */ - function isPresetOpen(uint8 binStep) external view returns (bool isAvailable) { - bytes32 openPresets = _openPresets; - return _isPresetOpen(openPresets, binStep); + isOpen = preset.decodeBool(_OFFSET_IS_PRESET_OPEN); } /** @@ -245,22 +223,7 @@ contract LBFactory is PendingOwnable, ILBFactory { * @return binStepWithPreset The list of binStep */ function getAllBinSteps() external view override returns (uint256[] memory binStepWithPreset) { - unchecked { - bytes32 avPresets = _availablePresets; - uint256 nbPresets = avPresets.decodeUint8(248); - - if (nbPresets > 0) { - binStepWithPreset = new uint256[](nbPresets); - - uint256 index; - for (uint256 i = _MIN_BIN_STEP; i <= _MAX_BIN_STEP; ++i) { - if (avPresets.decodeUint1(i) == _TRUE) { - binStepWithPreset[index] = i; - if (++index == nbPresets) break; - } - } - } - } + return _presets.keys(); } /** @@ -278,25 +241,24 @@ contract LBFactory is PendingOwnable, ILBFactory { unchecked { (IERC20 tokenA, IERC20 tokenB) = _sortTokens(tokenX, tokenY); - bytes32 avLBPairBinSteps = _availableLBPairBinSteps[tokenA][tokenB]; - uint256 nbAvailable = avLBPairBinSteps.decodeUint8(248); - - if (nbAvailable > 0) { - lbPairsAvailable = new LBPairInformation[](nbAvailable); - - uint256 index; - for (uint256 i = _MIN_BIN_STEP; i <= _MAX_BIN_STEP; ++i) { - if (avLBPairBinSteps.decodeUint1(i) == _TRUE) { - LBPairInformation memory pairInformation = _lbPairsInfo[tokenA][tokenB][i]; - - lbPairsAvailable[index] = LBPairInformation({ - binStep: i.safe8(), - LBPair: pairInformation.LBPair, - createdByOwner: pairInformation.createdByOwner, - ignoredForRouting: pairInformation.ignoredForRouting - }); - if (++index == nbAvailable) break; - } + EnumerableSet.UintSet storage addressSet = _availableLBPairBinSteps[tokenA][tokenB]; + + uint256 length = addressSet.length(); + + if (length > 0) { + lbPairsAvailable = new LBPairInformation[](length); + + mapping(uint256 => LBPairInformation) storage lbPairsInfo = _lbPairsInfo[tokenA][tokenB]; + + for (uint256 i = 0; i < length; ++i) { + uint16 binStep = addressSet.at(i).safe16(); + + lbPairsAvailable[i] = LBPairInformation({ + binStep: binStep, + LBPair: lbPairsInfo[binStep].LBPair, + createdByOwner: lbPairsInfo[binStep].createdByOwner, + ignoredForRouting: lbPairsInfo[binStep].ignoredForRouting + }); } } } @@ -327,15 +289,19 @@ contract LBFactory is PendingOwnable, ILBFactory { * @param tokenX The address of the first token * @param tokenY The address of the second token * @param activeId The active id of the pair - * @param binStep The bin step in basis point, used to calculate log(1 + binStep) + * @param binStep The bin step in basis point, used to calculate log(1 + binStep / 10_000) * @return pair The address of the newly created LBPair */ - function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint8 binStep) + function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) external override returns (ILBPair pair) { - if (!_isPresetOpen(_openPresets, binStep) && msg.sender != owner()) { + if (!_presets.contains(binStep)) revert LBFactory__BinStepHasNoPreset(binStep); + + bytes32 preset = bytes32(_presets.get(binStep)); + + if (!_isPresetOpen(preset) && msg.sender != owner()) { revert LBFactory__FunctionIsLockedForUsers(msg.sender, binStep); } @@ -358,30 +324,24 @@ contract LBFactory is PendingOwnable, ILBFactory { revert LBFactory__LBPairAlreadyExists(tokenX, tokenY, binStep); } - // We remove the bits that are not part of the feeParameters - { - bytes32 salt = keccak256(abi.encode(tokenA, tokenB, binStep)); - pair = ILBPair( - ImmutableClone.cloneDeterministic(implementation, abi.encodePacked(tokenX, tokenY, binStep), salt) - ); - } - - { - bytes32 preset = _presets[binStep]; - - if (preset == bytes32(0)) revert LBFactory__BinStepHasNoPreset(binStep); + pair = ILBPair( + ImmutableClone.cloneDeterministic( + implementation, + abi.encodePacked(tokenX, tokenY, binStep), + keccak256(abi.encode(tokenA, tokenB, binStep)) + ) + ); - pair.initialize( - preset.getBaseFactor(), - preset.getFilterPeriod(), - preset.getDecayPeriod(), - preset.getReductionFactor(), - preset.getVariableFeeControl(), - preset.getProtocolShare(), - preset.getMaxVolatilityAccumulator(), - activeId - ); - } + pair.initialize( + preset.getBaseFactor(), + preset.getFilterPeriod(), + preset.getDecayPeriod(), + preset.getReductionFactor(), + preset.getVariableFeeControl(), + preset.getProtocolShare(), + preset.getMaxVolatilityAccumulator(), + activeId + ); _lbPairsInfo[tokenA][tokenB][binStep] = LBPairInformation({ binStep: binStep, @@ -391,18 +351,7 @@ contract LBFactory is PendingOwnable, ILBFactory { }); _allLBPairs.push(pair); - - { - bytes32 avLBPairBinSteps = _availableLBPairBinSteps[tokenA][tokenB]; - // We add a 1 at bit `binStep` as this binStep is now set - avLBPairBinSteps = avLBPairBinSteps.set(_TRUE, Encoded.MASK_UINT1, binStep); - - // Increase the number of lb pairs by 1 - avLBPairBinSteps = bytes32(uint256(avLBPairBinSteps) + (1 << 248)); - - // Save the changes - _availableLBPairBinSteps[tokenA][tokenB] = avLBPairBinSteps; - } + _availableLBPairBinSteps[tokenA][tokenB].add(binStep); emit LBPairCreated(tokenX, tokenY, binStep, pair, _allLBPairs.length - 1); } @@ -414,15 +363,13 @@ contract LBFactory is PendingOwnable, ILBFactory { * @param binStep The bin step in basis point of the pair * @param ignored Whether to ignore (true) or not (false) the pair for routing */ - function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) - external - override - onlyOwner - { + function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint16 binStep, bool ignored) external override onlyOwner { (IERC20 tokenA, IERC20 tokenB) = _sortTokens(tokenX, tokenY); LBPairInformation memory pairInformation = _lbPairsInfo[tokenA][tokenB][binStep]; - if (address(pairInformation.LBPair) == address(0)) revert LBFactory__AddressZero(); + if (address(pairInformation.LBPair) == address(0)) { + revert LBFactory__LBPairDoesNotExist(tokenX, tokenY, binStep); + } if (pairInformation.ignoredForRouting == ignored) revert LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); @@ -433,7 +380,7 @@ contract LBFactory is PendingOwnable, ILBFactory { /** * @notice Sets the preset parameters of a bin step - * @param binStep The bin step in basis point, used to calculate log(1 + binStep) + * @param binStep The bin step in basis point, used to calculate the price * @param baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep * @param filterPeriod The period where the accumulator value is untouched, prevent spam * @param decayPeriod The period where the accumulator value is halved @@ -443,18 +390,19 @@ contract LBFactory is PendingOwnable, ILBFactory { * @param maxVolatilityAccumulator The max value of the volatility accumulator */ function setPreset( - uint8 binStep, + uint16 binStep, uint16 baseFactor, uint16 filterPeriod, uint16 decayPeriod, uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulator + uint24 maxVolatilityAccumulator, + bool isOpen ) external override onlyOwner { - bytes32 preset; + if (binStep < _MIN_BIN_STEP) revert LBFactory__BinStepTooLow(binStep); - _presets[binStep] = preset.setStaticFeeParameters( + bytes32 preset = bytes32(0).setStaticFeeParameters( baseFactor, filterPeriod, decayPeriod, @@ -464,18 +412,12 @@ contract LBFactory is PendingOwnable, ILBFactory { maxVolatilityAccumulator ); - bytes32 avPresets = _availablePresets; - if (avPresets.decodeUint1(binStep) == 0) { - // We add a 1 at bit `binStep` as this binStep is now set - avPresets = avPresets.set(_TRUE, Encoded.MASK_UINT1, binStep); - - // Increase the number of preset by 1 - avPresets = bytes32(uint256(avPresets) + (1 << 248)); - - // Save the changes - _availablePresets = avPresets; + if (isOpen) { + preset = preset.setBool(true, _OFFSET_IS_PRESET_OPEN); } + _presets.set(binStep, uint256(preset)); + emit PresetSet( binStep, baseFactor, @@ -486,49 +428,35 @@ contract LBFactory is PendingOwnable, ILBFactory { protocolShare, maxVolatilityAccumulator ); + + emit PresetOpenStateChanged(binStep, isOpen); } /** - * @notice Sets the open state of a preset. If true, anyone can create a pair with this preset, - * if false, it is restricted to the owner of the factory - * @param binStep The bin step to open or close - * @param isOpen Whether the preset will be open or not + * @notice Sets if the preset is open or not to be used by users + * @param binStep The bin step in basis point, used to calculate the price + * @param isOpen Whether the preset is open or not */ - function setOpenPreset(uint8 binStep, bool isOpen) external onlyOwner { - bytes32 openPresets = _openPresets; - bool isPresetOpenCurrent = _isPresetOpen(openPresets, binStep); - - if (isOpen) { - if (isPresetOpenCurrent) revert LBFactory__SamePresetOpenState(); + function setPresetOpenState(uint16 binStep, bool isOpen) external override onlyOwner { + if (!_presets.contains(binStep)) revert LBFactory__BinStepHasNoPreset(binStep); - // We add a 1 at bit `binStep` as this binStep is now open - _openPresets = _openPresets.set(_TRUE, Encoded.MASK_UINT1, binStep); - } else { - if (!isPresetOpenCurrent) revert LBFactory__SamePresetOpenState(); + bytes32 preset = bytes32(_presets.get(binStep)); - // We remove a 1 at bit `binStep` as this binStep is now closed - _openPresets = _openPresets.set(_FALSE, Encoded.MASK_UINT1, binStep); + if (preset.decodeBool(_OFFSET_IS_PRESET_OPEN) == isOpen) { + revert LBFactory__PresetOpenStateIsAlreadyInTheSameState(); } - emit OpenPresetChanged(binStep, isOpen); + _presets.set(binStep, uint256(preset.setBool(isOpen, _OFFSET_IS_PRESET_OPEN))); + + emit PresetOpenStateChanged(binStep, isOpen); } /** * @notice Remove the preset linked to a binStep * @param binStep The bin step to remove */ - function removePreset(uint8 binStep) external override onlyOwner { - if (_presets[binStep] == bytes32(0)) revert LBFactory__BinStepHasNoPreset(binStep); - - // Set the bit `binStep` to 0 - bytes32 avPresets = _availablePresets; - - avPresets = avPresets.set(_FALSE, Encoded.MASK_UINT1, binStep); - avPresets = bytes32(uint256(avPresets) - (1 << 248)); - - // Save the changes - _availablePresets = avPresets; - delete _presets[binStep]; + function removePreset(uint16 binStep) external override onlyOwner { + if (!_presets.remove(binStep)) revert LBFactory__BinStepHasNoPreset(binStep); emit PresetRemoved(binStep); } @@ -537,7 +465,7 @@ contract LBFactory is PendingOwnable, ILBFactory { * @notice Function to set the fee parameter of a LBPair * @param tokenX The address of the first token * @param tokenY The address of the second token - * @param binStep The bin step in basis point, used to calculate log(1 + binStep) + * @param binStep The bin step in basis point, used to calculate the price * @param baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep * @param filterPeriod The period where the accumulator value is untouched, prevent spam * @param decayPeriod The period where the accumulator value is halved @@ -549,7 +477,7 @@ contract LBFactory is PendingOwnable, ILBFactory { function setFeesParametersOnPair( IERC20 tokenX, IERC20 tokenY, - uint8 binStep, + uint16 binStep, uint16 baseFactor, uint16 filterPeriod, uint16 decayPeriod, @@ -617,8 +545,8 @@ contract LBFactory is PendingOwnable, ILBFactory { emit QuoteAssetRemoved(quoteAsset); } - function _isPresetOpen(bytes32 openPresets, uint8 binStep) internal pure returns (bool) { - return openPresets.decodeUint1(binStep) == 1; + function _isPresetOpen(bytes32 preset) internal pure returns (bool) { + return preset.decodeBool(_OFFSET_IS_PRESET_OPEN); } /** diff --git a/src/LBPair.sol b/src/LBPair.sol index 1c8f1c20..c5687591 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -143,12 +143,12 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { /** * @notice Returns the bin step of the Liquidity Book Pair - * @dev The bin step is the increase in price between two consecutive bins, in 20_000th. - * For example, a bin step of 1 means that the price of the next bin is 0.005% higher than the price of the previous bin. + * @dev The bin step is the increase in price between two consecutive bins, in basis points. + * For example, a bin step of 1 means that the price of the next bin is 0.01% higher than the price of the previous bin. * The maximum bin step is 200, which means that the price of the next bin is 1% higher than the price of the previous bin. - * @return binStep The bin step of the Liquidity Book Pair, in 20_000th + * @return binStep The bin step of the Liquidity Book Pair, in 10_000th */ - function getBinStep() external pure override returns (uint8) { + function getBinStep() external pure override returns (uint16) { return _binStep(); } @@ -166,7 +166,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { * @notice Returns the active id of the Liquidity Book Pair * @dev The active id is the id of the bin that is currently being used for swaps. * The price of the active bin is the price of the Liquidity Book Pair and can be calculated as follows: - * `price = (1 + binStep / 20_000) ^ (activeId - 2^23)` + * `price = (1 + binStep / 10_000) ^ (activeId - 2^23)` * @return activeId The active id of the Liquidity Book Pair */ function getActiveId() external view override returns (uint24 activeId) { @@ -370,7 +370,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { amountOutLeft = amountOut; bytes32 parameters = _parameters; - uint8 binStep = _binStep(); + uint16 binStep = _binStep(); uint24 id = parameters.getActiveId(); @@ -431,7 +431,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { bytes32 amountsInLeft = amountIn.encode(swapForY); bytes32 parameters = _parameters; - uint8 binStep = _binStep(); + uint16 binStep = _binStep(); uint24 id = parameters.getActiveId(); @@ -491,7 +491,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { if (amountsLeft == 0) revert LBPair__InsufficientAmountIn(); bytes32 parameters = _parameters; - uint8 binStep = _binStep(); + uint16 binStep = _binStep(); uint24 activeId = parameters.getActiveId(); @@ -830,11 +830,11 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { } /** - * @dev Returns the bin step of the pool, in 20_000ths. + * @dev Returns the bin step of the pool, in basis points * @return The bin step of the pool */ - function _binStep() internal pure returns (uint8) { - return _getArgUint8(40); + function _binStep() internal pure returns (uint16) { + return _getArgUint16(40); } /** @@ -904,7 +904,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { ); { - uint8 binStep = _binStep(); + uint16 binStep = _binStep(); bytes32 maxParameters = parameters.setVolatilityAccumulator(maxVolatilityAccumulator); uint256 totalFee = maxParameters.getBaseFee(binStep) + maxParameters.getVariableFee(binStep); if (totalFee > _MAX_TOTAL_FEE) { @@ -941,7 +941,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { returns (uint256 shares, bytes32 amountsIn, bytes32 amountsInToBin) { bytes32 binReserves = _bins[id]; - uint8 binStep = _binStep(); + uint16 binStep = _binStep(); uint256 price = id.getPriceFromId(binStep); uint256 supply = totalSupply(id); diff --git a/src/LBQuoter.sol b/src/LBQuoter.sol index 07290c69..4a4dcbd3 100644 --- a/src/LBQuoter.sol +++ b/src/LBQuoter.sol @@ -208,7 +208,7 @@ contract LBQuoter { if (amountInLeft == 0 && swapAmountOut > quote.amounts[i + 1]) { quote.amounts[i + 1] = swapAmountOut; quote.pairs[i] = address(LBPairsAvailable[j].LBPair); - quote.binSteps[i] = uint8(LBPairsAvailable[j].binStep); + quote.binSteps[i] = uint16(LBPairsAvailable[j].binStep); quote.versions[i] = ILBRouter.Version.V2_1; // Getting current price @@ -326,7 +326,7 @@ contract LBQuoter { ) { quote.amounts[i - 1] = swapAmountIn; quote.pairs[i - 1] = address(LBPairsAvailable[j].LBPair); - quote.binSteps[i - 1] = uint8(LBPairsAvailable[j].binStep); + quote.binSteps[i - 1] = uint16(LBPairsAvailable[j].binStep); quote.versions[i - 1] = ILBRouter.Version.V2_1; // Getting current price @@ -383,11 +383,11 @@ contract LBQuoter { { if (swapForY) { quote = uint128( - PriceHelper.getPriceFromId(activeId, uint8(binStep)).mulShiftRoundDown(amount, Constants.SCALE_OFFSET) + PriceHelper.getPriceFromId(activeId, uint16(binStep)).mulShiftRoundDown(amount, Constants.SCALE_OFFSET) ); } else { quote = uint128( - amount.shiftDivRoundDown(Constants.SCALE_OFFSET, PriceHelper.getPriceFromId(activeId, uint8(binStep))) + amount.shiftDivRoundDown(Constants.SCALE_OFFSET, PriceHelper.getPriceFromId(activeId, uint16(binStep))) ); } } diff --git a/src/LBRouter.sol b/src/LBRouter.sol index 0780bcfe..007efa91 100644 --- a/src/LBRouter.sol +++ b/src/LBRouter.sol @@ -194,7 +194,7 @@ contract LBRouter is ILBRouter { * @param binStep The bin step in basis point, used to calculate log(1 + binStep) * @return pair The address of the newly created LBPair */ - function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint8 binStep) + function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) external override returns (ILBPair pair) @@ -308,7 +308,7 @@ contract LBRouter is ILBRouter { function removeLiquidity( IERC20 tokenX, IERC20 tokenY, - uint8 binStep, + uint16 binStep, uint256 amountXMin, uint256 amountYMin, uint256[] memory ids, @@ -344,7 +344,7 @@ contract LBRouter is ILBRouter { */ function removeLiquidityNATIVE( IERC20 token, - uint8 binStep, + uint16 binStep, uint256 amountTokenMin, uint256 amountNATIVEMin, uint256[] memory ids, diff --git a/src/interfaces/ILBFactory.sol b/src/interfaces/ILBFactory.sol index d0efec64..1168b3c0 100644 --- a/src/interfaces/ILBFactory.sol +++ b/src/interfaces/ILBFactory.sol @@ -18,25 +18,19 @@ interface ILBFactory is IPendingOwnable { error LBFactory__QuoteAssetAlreadyWhitelisted(IERC20 quoteAsset); error LBFactory__AddressZero(); error LBFactory__LBPairAlreadyExists(IERC20 tokenX, IERC20 tokenY, uint256 _binStep); - error LBFactory__LBPairDoesNotExists(IERC20 tokenX, IERC20 tokenY, uint256 _binStep); + error LBFactory__LBPairDoesNotExist(IERC20 tokenX, IERC20 tokenY, uint256 binStep); error LBFactory__LBPairNotCreated(IERC20 tokenX, IERC20 tokenY, uint256 binStep); - error LBFactory__DecreasingPeriods(uint16 filterPeriod, uint16 decayPeriod); - error LBFactory__ReductionFactorOverflows(uint16 reductionFactor, uint256 max); - error LBFactory__VariableFeeControlOverflows(uint16 variableFeeControl, uint256 max); - error LBFactory__BaseFeesBelowMin(uint256 baseFees, uint256 minBaseFees); - error LBFactory__FeesAboveMax(uint256 fees, uint256 maxFees); error LBFactory__FlashLoanFeeAboveMax(uint256 fees, uint256 maxFees); - error LBFactory__BinStepRequirementsBreached(uint256 lowerBound, uint16 binStep, uint256 higherBound); - error LBFactory__ProtocolShareOverflows(uint16 protocolShare, uint256 max); - error LBFactory__FunctionIsLockedForUsers(address user, uint8 binStep); + error LBFactory__BinStepTooLow(uint256 binStep); + error LBFactory__FunctionIsLockedForUsers(address user, uint256 binStep); error LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); error LBFactory__BinStepHasNoPreset(uint256 binStep); + error LBFactory__PresetOpenStateIsAlreadyInTheSameState(); error LBFactory__SameFeeRecipient(address feeRecipient); error LBFactory__SameFlashLoanFee(uint256 flashLoanFee); error LBFactory__LBPairSafetyCheckFailed(address LBPairImplementation); error LBFactory__SameImplementation(address LBPairImplementation); error LBFactory__ImplementationNotSet(); - error LBFactory__SamePresetOpenState(); /** * @dev Structure to store the LBPair information, such as: @@ -46,7 +40,7 @@ interface ILBFactory is IPendingOwnable { * ignoredForRouting: Whether the pair is ignored for routing or not. An ignored pair will not be explored during routes finding */ struct LBPairInformation { - uint8 binStep; + uint16 binStep; ILBPair LBPair; bool createdByOwner; bool ignoredForRouting; @@ -75,36 +69,34 @@ interface ILBFactory is IPendingOwnable { uint256 maxVolatilityAccumulator ); + event PresetOpenStateChanged(uint256 indexed binStep, bool indexed isOpen); + event PresetRemoved(uint256 indexed binStep); event QuoteAssetAdded(IERC20 indexed quoteAsset); event QuoteAssetRemoved(IERC20 indexed quoteAsset); - event OpenPresetChanged(uint8 indexed binStep, bool open); + function getMinBinStep() external pure returns (uint256); - function getMaxFlashLoanFee() external pure returns (uint256); + function getFeeRecipient() external view returns (address); - function getMinBinStep() external pure returns (uint256); + function getMaxFlashLoanFee() external pure returns (uint256); - function getMaxBinStep() external pure returns (uint256); + function getFlashLoanFee() external view returns (uint256); function getLBPairImplementation() external view returns (address); + function getNumberOfLBPairs() external view returns (uint256); + + function getLBPairAtIndex(uint256 id) external returns (ILBPair); + function getNumberOfQuoteAssets() external view returns (uint256); function getQuoteAssetAtIndex(uint256 index) external view returns (IERC20); function isQuoteAsset(IERC20 token) external view returns (bool); - function getFeeRecipient() external view returns (address); - - function getFlashLoanFee() external view returns (uint256); - - function getLBPairAtIndex(uint256 id) external returns (ILBPair); - - function getNumberOfLBPairs() external view returns (uint256); - function getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep) external view @@ -120,7 +112,8 @@ interface ILBFactory is IPendingOwnable { uint256 reductionFactor, uint256 variableFeeControl, uint256 protocolShare, - uint256 maxAccumulator + uint256 maxAccumulator, + bool isOpen ); function getAllBinSteps() external view returns (uint256[] memory presetsBinStep); @@ -132,29 +125,32 @@ interface ILBFactory is IPendingOwnable { function setLBPairImplementation(address lbPairImplementation) external; - function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint8 binStep) + function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) external returns (ILBPair pair); - function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) external; + function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint16 binStep, bool ignored) external; function setPreset( - uint8 binStep, + uint16 binStep, uint16 baseFactor, uint16 filterPeriod, uint16 decayPeriod, uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulator + uint24 maxVolatilityAccumulator, + bool isOpen ) external; - function removePreset(uint8 binStep) external; + function setPresetOpenState(uint16 binStep, bool isOpen) external; + + function removePreset(uint16 binStep) external; function setFeesParametersOnPair( IERC20 tokenX, IERC20 tokenY, - uint8 binStep, + uint16 binStep, uint16 baseFactor, uint16 filterPeriod, uint16 decayPeriod, diff --git a/src/interfaces/ILBPair.sol b/src/interfaces/ILBPair.sol index 25d9993d..550e668a 100644 --- a/src/interfaces/ILBPair.sol +++ b/src/interfaces/ILBPair.sol @@ -88,7 +88,7 @@ interface ILBPair is ILBToken { function getTokenY() external view returns (IERC20 tokenY); - function getBinStep() external view returns (uint8 binStep); + function getBinStep() external view returns (uint16 binStep); function getReserves() external view returns (uint128 reserveX, uint128 reserveY); diff --git a/src/interfaces/ILBRouter.sol b/src/interfaces/ILBRouter.sol index 63463a26..b421dd15 100644 --- a/src/interfaces/ILBRouter.sol +++ b/src/interfaces/ILBRouter.sol @@ -126,7 +126,7 @@ interface ILBRouter { view returns (uint128 amountInLeft, uint128 amountOut, uint128 fee); - function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint8 binStep) + function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) external returns (ILBPair pair); @@ -156,7 +156,7 @@ interface ILBRouter { function removeLiquidity( IERC20 tokenX, IERC20 tokenY, - uint8 binStep, + uint16 binStep, uint256 amountXMin, uint256 amountYMin, uint256[] memory ids, @@ -167,7 +167,7 @@ interface ILBRouter { function removeLiquidityNATIVE( IERC20 token, - uint8 binStep, + uint16 binStep, uint256 amountTokenMin, uint256 amountNATIVEMin, uint256[] memory ids, diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index aa3b4013..b108ca4d 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -163,7 +163,7 @@ library BinHelper { function getCompositionFees( bytes32 binReserves, bytes32 parameters, - uint8 binStep, + uint16 binStep, bytes32 amountsIn, uint256 totalSupply, uint256 shares @@ -211,7 +211,7 @@ library BinHelper { function getAmounts( bytes32 binReserves, bytes32 parameters, - uint8 binStep, + uint16 binStep, bool swapForY, // swap `swapForY` and `activeId` to avoid stack too deep uint24 activeId, bytes32 amountsInLeft diff --git a/src/libraries/Clone.sol b/src/libraries/Clone.sol index 30e1596e..fd766539 100644 --- a/src/libraries/Clone.sol +++ b/src/libraries/Clone.sol @@ -92,6 +92,19 @@ abstract contract Clone { } } + /** + * @dev Reads an immutable arg with type uint16 + * @param argOffset The offset of the arg in the immutable args + * @return arg The immutable uint16 arg + */ + function _getArgUint16(uint256 argOffset) internal pure returns (uint16 arg) { + uint256 offset = _getImmutableArgsOffset(); + /// @solidity memory-safe-assembly + assembly { + arg := shr(0xf0, calldataload(add(offset, argOffset))) + } + } + /** * @dev Reads an immutable arg with type uint8 * @param argOffset The offset of the arg in the immutable args diff --git a/src/libraries/Constants.sol b/src/libraries/Constants.sol index 3ab5e3c0..bab199b6 100644 --- a/src/libraries/Constants.sol +++ b/src/libraries/Constants.sol @@ -18,7 +18,6 @@ library Constants { uint256 internal constant MAX_PROTOCOL_SHARE = 2_500; // 25% of the fee uint256 internal constant BASIS_POINT_MAX = 10_000; - uint256 internal constant TWO_BASIS_POINT_MAX = 2 * BASIS_POINT_MAX; /// @dev The expected return after a successful flash loan bytes32 internal constant CALLBACK_SUCCESS = keccak256("LBPair.onFlashLoan"); diff --git a/src/libraries/FeeHelper.sol b/src/libraries/FeeHelper.sol index 16ef7a97..e624fc28 100644 --- a/src/libraries/FeeHelper.sol +++ b/src/libraries/FeeHelper.sol @@ -36,11 +36,11 @@ library FeeHelper { /** * @dev Calculates the fee amount from the amount with fees, rounding up - * @param amounWithFees The amount with fees + * @param amountWithFees The amount with fees * @param totalFee The total fee * @return feeAmount The fee amount */ - function getFeeAmountFrom(uint128 amounWithFees, uint128 totalFee) + function getFeeAmountFrom(uint128 amountWithFees, uint128 totalFee) internal pure checkFeeOverflow(totalFee) @@ -48,7 +48,7 @@ library FeeHelper { { unchecked { // Can't overflow, max(result) = (type(uint128).max * 0.1e18 + 1e18 - 1) / 1e18 < 2^128 - return uint128((uint256(amounWithFees) * totalFee + Constants.PRECISION - 1) / Constants.PRECISION); + return uint128((uint256(amountWithFees) * totalFee + Constants.PRECISION - 1) / Constants.PRECISION); } } diff --git a/src/libraries/PairParameterHelper.sol b/src/libraries/PairParameterHelper.sol index 114ea017..b1c1e5ef 100644 --- a/src/libraries/PairParameterHelper.sol +++ b/src/libraries/PairParameterHelper.sol @@ -220,31 +220,31 @@ library PairParameterHelper { /** * @dev Calculates the base fee, with 18 decimals * @param params The encoded pair parameters - * @param binStep The bin step (in 20_000th) + * @param binStep The bin step (in basis points) * @return baseFee The base fee */ - function getBaseFee(bytes32 params, uint8 binStep) internal pure returns (uint256) { + function getBaseFee(bytes32 params, uint16 binStep) internal pure returns (uint256) { unchecked { - // Base factor is in basis points, binStep is in 20_000th, so we multiply by 5e9 - return uint256(getBaseFactor(params)) * binStep * 5e9; + // Base factor is in basis points, binStep is in basis points, so we multiply by 1e10 + return uint256(getBaseFactor(params)) * binStep * 1e10; } } /** * @dev Calculates the variable fee * @param params The encoded pair parameters - * @param binStep The bin step (in 20_000th) + * @param binStep The bin step (in basis points) * @return variableFee The variable fee */ - function getVariableFee(bytes32 params, uint8 binStep) internal pure returns (uint256 variableFee) { + function getVariableFee(bytes32 params, uint16 binStep) internal pure returns (uint256 variableFee) { uint256 variableFeeControl = getVariableFeeControl(params); if (variableFeeControl != 0) { unchecked { - // The volatility accumulator is in basis points, binStep is in 20_000th, - // and the variable fee control is in basis points, so the result is in 400e18th + // The volatility accumulator is in basis points, binStep is in basis points, + // and the variable fee control is in basis points, so the result is in 100e18th uint256 prod = uint256(getVolatilityAccumulator(params)) * binStep; - variableFee = (prod * prod * variableFeeControl + 399) / 400; + variableFee = (prod * prod * variableFeeControl + 99) / 100; } } } @@ -252,10 +252,10 @@ library PairParameterHelper { /** * @dev Calculates the total fee, which is the sum of the base fee and the variable fee * @param params The encoded pair parameters - * @param binStep The bin step (in 20_000th) + * @param binStep The bin step (in basis points) * @return totalFee The total fee */ - function getTotalFee(bytes32 params, uint8 binStep) internal pure returns (uint128) { + function getTotalFee(bytes32 params, uint16 binStep) internal pure returns (uint128) { unchecked { return (getBaseFee(params, binStep) + getVariableFee(params, binStep)).safe128(); } diff --git a/src/libraries/PriceHelper.sol b/src/libraries/PriceHelper.sol index f54f695b..e53e3a37 100644 --- a/src/libraries/PriceHelper.sol +++ b/src/libraries/PriceHelper.sol @@ -25,7 +25,7 @@ library PriceHelper { * @param binStep The bin step * @return price The price as a 128.128-binary fixed-point number */ - function getPriceFromId(uint24 id, uint8 binStep) internal pure returns (uint256 price) { + function getPriceFromId(uint24 id, uint16 binStep) internal pure returns (uint256 price) { uint256 base = getBase(binStep); int256 exponent = getExponent(id); @@ -38,7 +38,7 @@ library PriceHelper { * @param binStep The bin step * @return id The id */ - function getIdFromPrice(uint256 price, uint8 binStep) internal pure returns (uint24 id) { + function getIdFromPrice(uint256 price, uint16 binStep) internal pure returns (uint24 id) { uint256 base = getBase(binStep); int256 realId = price.log2() / base.log2(); @@ -52,9 +52,9 @@ library PriceHelper { * @param binStep The bin step * @return base The base */ - function getBase(uint8 binStep) internal pure returns (uint256) { + function getBase(uint16 binStep) internal pure returns (uint256) { unchecked { - return Constants.SCALE + (uint256(binStep) << Constants.SCALE_OFFSET) / Constants.TWO_BASIS_POINT_MAX; + return Constants.SCALE + (uint256(binStep) << Constants.SCALE_OFFSET) / Constants.BASIS_POINT_MAX; } } diff --git a/src/libraries/math/Encoded.sol b/src/libraries/math/Encoded.sol index 937578e8..437197fc 100644 --- a/src/libraries/math/Encoded.sol +++ b/src/libraries/math/Encoded.sol @@ -39,6 +39,18 @@ library Encoded { } } + /** + * @notice Internal function to set a bool in an encoded bytes32 using an offset + * @dev This function can overflow + * @param encoded The previous encoded value + * @param boolean The bool to encode + * @param offset The offset + * @return newEncoded The new encoded value + */ + function setBool(bytes32 encoded, bool boolean, uint256 offset) internal pure returns (bytes32 newEncoded) { + return set(encoded, boolean ? 1 : 0, MASK_UINT1, offset); + } + /** * @notice Internal function to decode a bytes32 sample using a mask and offset * @dev This function can overflow @@ -54,15 +66,15 @@ library Encoded { } /** - * @notice Internal function to decode a bytes32 sample into a uint1 using an offset + * @notice Internal function to decode a bytes32 sample into a bool using an offset * @dev This function can overflow * @param encoded The encoded value * @param offset The offset - * @return value The decoded value as a uint8, since uint1 is not supported + * @return boolean The decoded value as a bool */ - function decodeUint1(bytes32 encoded, uint256 offset) internal pure returns (uint8 value) { + function decodeBool(bytes32 encoded, uint256 offset) internal pure returns (bool boolean) { assembly { - value := and(shr(offset, encoded), MASK_UINT1) + boolean := and(shr(offset, encoded), MASK_UINT1) } } diff --git a/src/libraries/math/Uint128x128Math.sol b/src/libraries/math/Uint128x128Math.sol index 3ac8712e..63e6954f 100644 --- a/src/libraries/math/Uint128x128Math.sol +++ b/src/libraries/math/Uint128x128Math.sol @@ -106,7 +106,7 @@ library Uint128x128Math { } } - if (absY < 0x200000) { + if (absY < 0x100000) { result = Constants.SCALE; assembly { let squared := x @@ -154,8 +154,6 @@ library Uint128x128Math { if and(absY, 0x40000) { result := shr(128, mul(result, squared)) } squared := shr(128, mul(squared, squared)) if and(absY, 0x80000) { result := shr(128, mul(result, squared)) } - squared := shr(128, mul(squared, squared)) - if and(absY, 0x100000) { result := shr(128, mul(result, shr(128, mul(result, squared)))) } } } diff --git a/test/LBFactory.t.sol b/test/LBFactory.t.sol index a59c5840..1c1d2ac5 100644 --- a/test/LBFactory.t.sol +++ b/test/LBFactory.t.sol @@ -54,7 +54,7 @@ contract LiquidityBinFactoryTest is TestHelper { event PresetRemoved(uint256 indexed binStep); event FeeRecipientSet(address oldRecipient, address newRecipient); event FlashLoanFeeSet(uint256 oldFlashLoanFee, uint256 newFlashLoanFee); - event OpenPresetChanged(uint8 indexed binStep, bool open); + event PresetOpenStateChanged(uint256 indexed binStep, bool indexed isOpen); function setUp() public override { super.setUp(); @@ -66,9 +66,8 @@ contract LiquidityBinFactoryTest is TestHelper { assertEq(factory.getLBPairImplementation(), address(pairImplementation), "test_Constructor::3"); assertEq(factory.getMinBinStep(), 1, "test_Constructor::4"); - assertEq(factory.getMaxBinStep(), 200, "test_Constructor::5"); - assertEq(factory.getFeeRecipient(), DEV, "test_Constructor::6"); - assertEq(factory.getMaxFlashLoanFee(), 0.1e18, "test_Constructor::7"); + assertEq(factory.getFeeRecipient(), DEV, "test_Constructor::5"); + assertEq(factory.getMaxFlashLoanFee(), 0.1e18, "test_Constructor::6"); vm.expectEmit(true, true, true, true); emit FlashLoanFeeSet(0, DEFAULT_FLASHLOAN_FEE); @@ -100,9 +99,11 @@ contract LiquidityBinFactoryTest is TestHelper { LBFactory anotherFactory = new LBFactory(DEV, DEFAULT_FLASHLOAN_FEE); + anotherFactory.setPreset(1, 1, 1, 1, 1, 1, 1, 1, false); + // Reverts if there is no implementation set vm.expectRevert(ILBFactory.LBFactory__ImplementationNotSet.selector); - anotherFactory.createLBPair(weth, usdc, ID_ONE, DEFAULT_BIN_STEP); + anotherFactory.createLBPair(weth, usdc, ID_ONE, 1); ILBPair newImplementationForAnotherFactory = new LBPair(anotherFactory); @@ -179,7 +180,7 @@ contract LiquidityBinFactoryTest is TestHelper { ); factory.createLBPair(link, usdc, ID_ONE, DEFAULT_BIN_STEP); - factory.setOpenPreset(DEFAULT_BIN_STEP, true); + factory.setPresetOpenState(DEFAULT_BIN_STEP, true); // Any user should be able to create pairs vm.prank(ALICE); @@ -206,7 +207,7 @@ contract LiquidityBinFactoryTest is TestHelper { ); // Should close pair creations again - factory.setOpenPreset(DEFAULT_BIN_STEP, false); + factory.setPresetOpenState(DEFAULT_BIN_STEP, false); vm.prank(ALICE); vm.expectRevert( @@ -227,6 +228,23 @@ contract LiquidityBinFactoryTest is TestHelper { // Can't create pair if the implementation is not set LBFactory newFactory = new LBFactory(DEV, DEFAULT_FLASHLOAN_FEE); + + // Can't create a pair if the preset is not set + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__BinStepHasNoPreset.selector, DEFAULT_BIN_STEP)); + newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + + newFactory.setPreset( + DEFAULT_BIN_STEP, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + DEFAULT_OPEN_STATE + ); + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__ImplementationNotSet.selector)); newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); @@ -240,29 +258,10 @@ contract LiquidityBinFactoryTest is TestHelper { vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__IdenticalAddresses.selector, usdc)); newFactory.createLBPair(usdc, usdc, ID_ONE, DEFAULT_BIN_STEP); - // TODO Can't create a pair with an invalid bin step - // vm.expectRevert(abi.encodeWithSelector(ILBFactory.BinHelper__BinStepOverflows.selector, type(uint16).max)); - // newFactory.createLBPair(usdt, usdc, ID_ONE, type(uint16).max); - // Can't create a pair with address(0) vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__AddressZero.selector)); newFactory.createLBPair(IERC20(address(0)), usdc, ID_ONE, DEFAULT_BIN_STEP); - // Can't create a pair if the preset is not set - vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__BinStepHasNoPreset.selector, DEFAULT_BIN_STEP)); - newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); - - newFactory.setPreset( - DEFAULT_BIN_STEP, - DEFAULT_BASE_FACTOR, - DEFAULT_FILTER_PERIOD, - DEFAULT_DECAY_PERIOD, - DEFAULT_REDUCTION_FACTOR, - DEFAULT_VARIABLE_FEE_CONTROL, - DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATOR - ); - // Can't create the same pair twice (a revision should be created instead) newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); vm.expectRevert( @@ -302,7 +301,9 @@ contract LiquidityBinFactoryTest is TestHelper { factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, true); // Can't update a non existing pair - vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__AddressZero.selector)); + vm.expectRevert( + abi.encodeWithSelector(ILBFactory.LBFactory__LBPairDoesNotExist.selector, usdt, usdc, DEFAULT_BIN_STEP) + ); factory.setLBPairIgnored(usdt, usdc, DEFAULT_BIN_STEP, true); factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); @@ -318,16 +319,17 @@ contract LiquidityBinFactoryTest is TestHelper { } function testFuzz_SetPreset( - uint8 binStep, + uint16 binStep, uint16 baseFactor, uint16 filterPeriod, uint16 decayPeriod, uint16 reductionFactor, uint24 variableFeeControl, uint16 protocolShare, - uint24 maxVolatilityAccumulator + uint24 maxVolatilityAccumulator, + bool isOpen ) public { - binStep = uint8(bound(binStep, factory.getMinBinStep(), factory.getMaxBinStep())); + binStep = uint16(bound(binStep, factory.getMinBinStep(), type(uint16).max)); filterPeriod = uint16(bound(filterPeriod, 0, Encoded.MASK_UINT12 - 1)); decayPeriod = uint16(bound(decayPeriod, filterPeriod + 1, Encoded.MASK_UINT12)); reductionFactor = uint16(bound(reductionFactor, 0, Constants.BASIS_POINT_MAX)); @@ -346,6 +348,8 @@ contract LiquidityBinFactoryTest is TestHelper { protocolShare, maxVolatilityAccumulator ); + vm.expectEmit(true, true, true, true); + emit PresetOpenStateChanged(binStep, isOpen); factory.setPreset( binStep, @@ -355,25 +359,24 @@ contract LiquidityBinFactoryTest is TestHelper { reductionFactor, variableFeeControl, protocolShare, - maxVolatilityAccumulator + maxVolatilityAccumulator, + isOpen ); // Bin step DEFAULT_BIN_STEP is already there if (binStep != DEFAULT_BIN_STEP) { assertEq(factory.getAllBinSteps().length, 2, "1"); - if (binStep < DEFAULT_BIN_STEP) { - assertEq(factory.getAllBinSteps()[0], binStep, "2"); - } else { - assertEq(factory.getAllBinSteps()[1], binStep, "3"); - } + + assertEq(factory.getAllBinSteps()[0], DEFAULT_BIN_STEP, "2"); + assertEq(factory.getAllBinSteps()[1], binStep, "3"); } else { - assertEq(factory.getAllBinSteps().length, 1, "3"); - assertEq(factory.getAllBinSteps()[0], binStep, "4"); + assertEq(factory.getAllBinSteps().length, 1, "4"); + assertEq(factory.getAllBinSteps()[0], binStep, "5"); } // Check splitted in two to avoid stack too deep errors { - (uint256 baseFactorView, uint256 filterPeriodView, uint256 decayPeriodView, uint256 reductionFactorView,,,) + (uint256 baseFactorView, uint256 filterPeriodView, uint256 decayPeriodView, uint256 reductionFactorView,,,,) = factory.getPreset(binStep); assertEq(baseFactorView, baseFactor); @@ -383,12 +386,21 @@ contract LiquidityBinFactoryTest is TestHelper { } { - (,,,, uint256 variableFeeControlView, uint256 protocolShareView, uint256 maxVolatilityAccumulatorView) = - factory.getPreset(binStep); + ( + , + , + , + , + uint256 variableFeeControlView, + uint256 protocolShareView, + uint256 maxVolatilityAccumulatorView, + bool isOpenView + ) = factory.getPreset(binStep); assertEq(variableFeeControlView, variableFeeControl); assertEq(protocolShareView, protocolShare); assertEq(maxVolatilityAccumulatorView, maxVolatilityAccumulator); + assertEq(isOpenView, isOpen); } } @@ -401,7 +413,8 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATOR + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + DEFAULT_OPEN_STATE ); factory.setPreset( @@ -412,7 +425,8 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATOR + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + DEFAULT_OPEN_STATE ); assertEq(factory.getAllBinSteps().length, 3, "test_RemovePreset::1"); @@ -605,60 +619,55 @@ contract LiquidityBinFactoryTest is TestHelper { factory.setFlashLoanFee(maxFlashLoanFee + 1); } - function testFuzz_OpenPresets(uint8 binStep) public { + function testFuzz_OpenPresets(uint16 binStep) public { uint256 minBinStep = factory.getMinBinStep(); - uint256 maxBinStep = factory.getMaxBinStep(); + uint256 maxBinStep = type(uint16).max; - binStep = uint8(bound(binStep, minBinStep, maxBinStep)); + binStep = uint16(bound(binStep, minBinStep, maxBinStep)); // Preset are not open to the public by default - assertFalse(factory.isPresetOpen(binStep), "testFuzz_OpenPresets::1"); + if (binStep == DEFAULT_BIN_STEP) { + assertFalse(isPresetOpen(binStep), "testFuzz_OpenPresets::1"); + } else { + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__BinStepHasNoPreset.selector, binStep)); + factory.getPreset(binStep); + } // Can be opened vm.expectEmit(true, true, true, true); - emit OpenPresetChanged(binStep, true); - factory.setOpenPreset(binStep, true); - - for (uint256 i = minBinStep; i < maxBinStep; i++) { - if (i == binStep) { - assertTrue(factory.isPresetOpen(uint8(i)), "testFuzz_OpenPresets::2"); - } else { - assertFalse(factory.isPresetOpen(uint8(i)), "testFuzz_OpenPresets::3"); - } - } - - // Setting neighboring presets to true does not change the state of the preset - uint8 nextBinStep = uint8(bound(binStep + 1, minBinStep, maxBinStep)); - uint8 previousBinStep = uint8(bound(binStep + maxBinStep - minBinStep - 1, minBinStep, maxBinStep)); - - factory.setOpenPreset(nextBinStep, true); - factory.setOpenPreset(previousBinStep, true); - - assertTrue(factory.isPresetOpen(binStep), "testFuzz_OpenPresets::4"); - assertTrue(factory.isPresetOpen(nextBinStep), "testFuzz_OpenPresets::5"); - assertTrue(factory.isPresetOpen(previousBinStep), "testFuzz_OpenPresets::6"); + emit PresetOpenStateChanged(binStep, true); + factory.setPreset( + binStep, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + true + ); + assertTrue(isPresetOpen(binStep), "testFuzz_OpenPresets::2"); // Can't set to the same state - vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__SamePresetOpenState.selector)); - factory.setOpenPreset(binStep, true); + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__PresetOpenStateIsAlreadyInTheSameState.selector)); + factory.setPresetOpenState(binStep, true); // Can be closed vm.expectEmit(true, true, true, true); - emit OpenPresetChanged(binStep, false); - factory.setOpenPreset(binStep, false); + emit PresetOpenStateChanged(binStep, false); + factory.setPresetOpenState(binStep, false); - assertFalse(factory.isPresetOpen(binStep), "testFuzz_OpenPresets::7"); - assertTrue(factory.isPresetOpen(nextBinStep), "testFuzz_OpenPresets::8"); - assertTrue(factory.isPresetOpen(previousBinStep), "testFuzz_OpenPresets::9"); + assertFalse(isPresetOpen(binStep), "testFuzz_OpenPresets::3"); // Can't open if not the owner vm.prank(ALICE); vm.expectRevert(abi.encodeWithSelector(IPendingOwnable.PendingOwnable__NotOwner.selector)); - factory.setOpenPreset(binStep, true); + factory.setPresetOpenState(binStep, true); // Can't set to the same state - vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__SamePresetOpenState.selector)); - factory.setOpenPreset(binStep, false); + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__PresetOpenStateIsAlreadyInTheSameState.selector)); + factory.setPresetOpenState(binStep, false); } function test_AddQuoteAsset() public { @@ -736,7 +745,8 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATOR + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + DEFAULT_OPEN_STATE ); factory.setPreset( @@ -747,7 +757,8 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATOR + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + DEFAULT_OPEN_STATE ); ILBPair pair1 = factory.createLBPair(weth, usdc, ID_ONE, 5); diff --git a/test/LBPairFees.t.sol b/test/LBPairFees.t.sol index beda3fea..3b75a45f 100644 --- a/test/LBPairFees.t.sol +++ b/test/LBPairFees.t.sol @@ -481,6 +481,7 @@ contract LBPairFeesTest is TestHelper { vm.prank(BOB); wnative.transfer(address(pairWnative), 1e18); pairWnative.swap(true, BOB); + pairWnative.getBinStep(); (uint128 protocolFeeX, uint128 protocolFeeY) = pairWnative.getProtocolFees(); uint128 previousProtocolFeeX = protocolFeeX; @@ -546,15 +547,15 @@ contract LBPairFeesTest is TestHelper { } function test_revert_TotalFeeExceeded( - uint8 binStep, + uint16 binStep, uint16 baseFactor, uint24 variableFeeControl, uint24 maxVolatilityAccumulator ) external { vm.assume(maxVolatilityAccumulator <= Encoded.MASK_UINT20); - uint256 baseFee = uint256(baseFactor) * binStep * 5e9; - uint256 varFee = ((uint256(binStep) * maxVolatilityAccumulator) ** 2 * variableFeeControl + 399) / 400; + uint256 baseFee = uint256(baseFactor) * binStep * 1e10; + uint256 varFee = ((uint256(binStep) * maxVolatilityAccumulator) ** 2 * variableFeeControl + 99) / 100; vm.assume(baseFee + varFee > 1e17); diff --git a/test/LBPairImplementation.t.sol b/test/LBPairImplementation.t.sol index 6af832f6..54df4a8a 100644 --- a/test/LBPairImplementation.t.sol +++ b/test/LBPairImplementation.t.sol @@ -16,7 +16,7 @@ contract LBPairImplementationTest is Test { implementation = address(new LBPair(ILBFactory(factory))); } - function testFuzz_Getters(address tokenX, address tokenY, uint8 binStep) public { + function testFuzz_Getters(address tokenX, address tokenY, uint16 binStep) public { bytes32 salt = keccak256(abi.encodePacked(tokenX, tokenY, binStep)); bytes memory data = abi.encodePacked(tokenX, tokenY, binStep); diff --git a/test/LBRouter.Liquidity.t.sol b/test/LBRouter.Liquidity.t.sol index e375fda0..808e7947 100644 --- a/test/LBRouter.Liquidity.t.sol +++ b/test/LBRouter.Liquidity.t.sol @@ -21,7 +21,7 @@ contract LiquidityBinRouterTest is TestHelper { function setUp() public override { super.setUp(); - factory.setOpenPreset(DEFAULT_BIN_STEP, true); + factory.setPresetOpenState(DEFAULT_BIN_STEP, true); // Create necessary pairs router.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); @@ -59,7 +59,7 @@ contract LiquidityBinRouterTest is TestHelper { function test_CreatePair() public { router.createLBPair(weth, usdc, ID_ONE, DEFAULT_BIN_STEP); - factory.setOpenPreset(DEFAULT_BIN_STEP, false); + factory.setPresetOpenState(DEFAULT_BIN_STEP, false); vm.expectRevert( abi.encodeWithSelector( diff --git a/test/LBRouter.Swap.t.sol b/test/LBRouter.Swap.t.sol index b2b70579..7926fe60 100644 --- a/test/LBRouter.Swap.t.sol +++ b/test/LBRouter.Swap.t.sol @@ -21,7 +21,7 @@ contract LiquidityBinRouterSwapTest is TestHelper { function setUp() public override { super.setUp(); - factory.setOpenPreset(DEFAULT_BIN_STEP, true); + factory.setPresetOpenState(DEFAULT_BIN_STEP, true); // Create necessary pairs router.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); diff --git a/test/helpers/TestHelper.sol b/test/helpers/TestHelper.sol index 6c0c99b3..7332b284 100644 --- a/test/helpers/TestHelper.sol +++ b/test/helpers/TestHelper.sol @@ -39,7 +39,7 @@ abstract contract TestHelper is Test { uint256 internal constant BASIS_POINT_MAX = 10_000; // Avalanche market config for 10bps - uint8 internal constant DEFAULT_BIN_STEP = 20; + uint16 internal constant DEFAULT_BIN_STEP = 10; uint16 internal constant DEFAULT_BASE_FACTOR = 5_000; uint16 internal constant DEFAULT_FILTER_PERIOD = 30; uint16 internal constant DEFAULT_DECAY_PERIOD = 600; @@ -47,6 +47,7 @@ abstract contract TestHelper is Test { uint24 internal constant DEFAULT_VARIABLE_FEE_CONTROL = 40_000; uint16 internal constant DEFAULT_PROTOCOL_SHARE = 1_000; uint24 internal constant DEFAULT_MAX_VOLATILITY_ACCUMULATOR = 350_000; + bool internal constant DEFAULT_OPEN_STATE = false; uint256 internal constant DEFAULT_FLASHLOAN_FEE = 8e14; address payable immutable DEV = payable(address(this)); @@ -196,7 +197,7 @@ abstract contract TestHelper is Test { if (address(taxToken) != address(0)) factory.addQuoteAsset(taxToken); } - function setDefaultFactoryPresets(uint8 binStep) internal { + function setDefaultFactoryPresets(uint16 binStep) internal { factory.setPreset( binStep, DEFAULT_BASE_FACTOR, @@ -205,7 +206,8 @@ abstract contract TestHelper is Test { DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, DEFAULT_PROTOCOL_SHARE, - DEFAULT_MAX_VOLATILITY_ACCUMULATOR + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + DEFAULT_OPEN_STATE ); } @@ -217,7 +219,7 @@ abstract contract TestHelper is Test { newPair = createLBPairFromStartIdAndBinStep(tokenX, tokenY, startId, DEFAULT_BIN_STEP); } - function createLBPairFromStartIdAndBinStep(IERC20 tokenX, IERC20 tokenY, uint24 startId, uint8 binStep) + function createLBPairFromStartIdAndBinStep(IERC20 tokenX, IERC20 tokenY, uint24 startId, uint16 binStep) internal returns (LBPair newPair) { @@ -362,4 +364,8 @@ abstract contract TestHelper is Test { return id.safe24(); } + + function isPresetOpen(uint16 binStep) public view returns (bool isOpen) { + (,,,,,,, isOpen) = factory.getPreset(binStep); + } } diff --git a/test/libraries/BinHelper.t.sol b/test/libraries/BinHelper.t.sol index fe3d9ac5..07f94268 100644 --- a/test/libraries/BinHelper.t.sol +++ b/test/libraries/BinHelper.t.sol @@ -153,7 +153,7 @@ contract BinHelperTest is TestHelper { function testFuzz_GetCompositionFees( uint128 reserveX, uint128 reserveY, - uint8 binStep, + uint16 binStep, uint128 amountXIn, uint128 amountYIn, uint256 price, diff --git a/test/libraries/ImmutableClone.t.sol b/test/libraries/ImmutableClone.t.sol index 11530ff9..85377e11 100644 --- a/test/libraries/ImmutableClone.t.sol +++ b/test/libraries/ImmutableClone.t.sol @@ -35,7 +35,7 @@ contract TestImmutableClone is Test { assertEq(implementationClone.getBytes(data.length), data, "testFuzz_Implementation::1"); } - function testFuzz_Pair(address tokenX, address tokenY, uint8 binStep) public { + function testFuzz_Pair(address tokenX, address tokenY, uint16 binStep) public { address implementation = address(new Pair()); bytes32 salt = keccak256("salt"); @@ -79,8 +79,8 @@ contract Pair is Clone { return _getArgAddress(20); } - function getBinStep() public pure returns (uint8) { - return _getArgUint8(40); + function getBinStep() public pure returns (uint16) { + return _getArgUint16(40); } } diff --git a/test/libraries/PairParameterHelper.t.sol b/test/libraries/PairParameterHelper.t.sol index ab9d593e..55ab15b9 100644 --- a/test/libraries/PairParameterHelper.t.sol +++ b/test/libraries/PairParameterHelper.t.sol @@ -142,15 +142,15 @@ contract PairParameterHelperTest is Test { ); } - function testFuzz_getBaseAndVariableFees(bytes32 params, uint8 binStep) external { + function testFuzz_getBaseAndVariableFees(bytes32 params, uint16 binStep) external { uint256 baseFee = params.getBaseFee(binStep); uint256 variableFee = params.getVariableFee(binStep); - assertEq(baseFee, uint256(params.getBaseFactor()) * binStep * 5e9, "test_getBaseAndVariableFees::1"); + assertEq(baseFee, uint256(params.getBaseFactor()) * binStep * 1e10, "test_getBaseAndVariableFees::1"); uint256 prod = uint256(params.getVolatilityAccumulator()) * binStep; assertEq( - variableFee, (prod * prod * params.getVariableFeeControl() + 399) / 400, "test_getBaseAndVariableFees::2" + variableFee, (prod * prod * params.getVariableFeeControl() + 99) / 100, "test_getBaseAndVariableFees::2" ); if (baseFee + variableFee < type(uint128).max) { diff --git a/test/libraries/PriceHelper.t.sol b/test/libraries/PriceHelper.t.sol index e1669633..a64ff280 100644 --- a/test/libraries/PriceHelper.t.sol +++ b/test/libraries/PriceHelper.t.sol @@ -11,9 +11,9 @@ contract PriceHelperTest is Test { Math immutable math = new Math(); - function testFuzz_GetBase(uint8 binStep) external { + function testFuzz_GetBase(uint16 binStep) external { uint256 base128x128 = PriceHelper.getBase(binStep); - uint256 expectedBase128x128 = (1 << 128) + (uint256(binStep) << 128) / 20_000; + uint256 expectedBase128x128 = (1 << 128) + (uint256(binStep) << 128) / 10_000; assertEq(base128x128, expectedBase128x128, "test_GetBase::1"); } @@ -49,7 +49,7 @@ contract PriceHelperTest is Test { assertEq(priceDecimal, expectedPriceDecimal, "test_Convert128x128PriceToDecimal::1"); } - function testFuzz_Price(uint256 price, uint8 binStep) external { + function testFuzz_Price(uint256 price, uint16 binStep) external { // test that all prices from 1e64 to 1e192 are valid, includes 1e-18 to 1e18 in decimal. vm.assume(price > 1 << 64 && price < 1 << 192 && binStep > 0 && binStep < 200); @@ -63,12 +63,12 @@ contract PriceHelperTest is Test { // Can't use `assertApproxEqRel` as it overflow when multiplying by 1e18 // Assert that price is at most `binStep`% away from the calculated price assertLe( - price * (Constants.TWO_BASIS_POINT_MAX - binStep) / Constants.TWO_BASIS_POINT_MAX, + price * (Constants.BASIS_POINT_MAX - binStep) / Constants.BASIS_POINT_MAX, calculatedPrice, "test_Price::1" ); assertGe( - price * (Constants.TWO_BASIS_POINT_MAX + binStep) / Constants.TWO_BASIS_POINT_MAX, + price * (Constants.BASIS_POINT_MAX + binStep) / Constants.BASIS_POINT_MAX, calculatedPrice, "test_Price::2" ); @@ -76,29 +76,34 @@ contract PriceHelperTest is Test { } function test_Price() external { - uint24 id = 8761245; // result of `log2(123456789) / log2(1.00005) + 2**23` + uint24 id = 8574931; // result of `log2(123456789) / log2(1.0001) + 2**23` uint256 expectedPrice = PriceHelper.convertDecimalPriceTo128x128(123456789e18); assertLe(PriceHelper.getPriceFromId(id - 1, 1), expectedPrice, "test_Price::1"); assertGe(PriceHelper.getPriceFromId(id + 1, 1), expectedPrice, "test_Price::2"); - id = 8116506; // result of `log2(0.00000123456789) / log2(1.00005) + 2**23` + id = 8252553; // result of `log2(0.00000123456789) / log2(1.0001) + 2**23` expectedPrice = PriceHelper.convertDecimalPriceTo128x128(0.00000123456789e18); assertLe(PriceHelper.getPriceFromId(id - 1, 1), expectedPrice, "test_Price::3"); assertGe(PriceHelper.getPriceFromId(id + 1, 1), expectedPrice, "test_Price::4"); id = 8392773; // result of `log2(10**18)/log2(1.01) + 2**23` expectedPrice = PriceHelper.convertDecimalPriceTo128x128(1e36); - assertLe(PriceHelper.getPriceFromId(id - 1, 200), expectedPrice, "test_Price::5"); - assertGe(PriceHelper.getPriceFromId(id + 1, 200), expectedPrice, "test_Price::6"); + assertLe(PriceHelper.getPriceFromId(id - 1, 100), expectedPrice, "test_Price::5"); + assertGe(PriceHelper.getPriceFromId(id + 1, 100), expectedPrice, "test_Price::6"); + + id = 8389042; // result of `log2(10**18)/log2(1.1) + 2**23` + expectedPrice = PriceHelper.convertDecimalPriceTo128x128(1e36); + assertLe(PriceHelper.getPriceFromId(id - 1, 1000), expectedPrice, "test_Price::7"); + assertGe(PriceHelper.getPriceFromId(id + 1, 1000), expectedPrice, "test_Price::8"); } } contract Math { - function priceFromId(uint24 id, uint8 binStep) external pure returns (uint256) { + function priceFromId(uint24 id, uint16 binStep) external pure returns (uint256) { return PriceHelper.getPriceFromId(id, binStep); } - function idFromPrice(uint256 price, uint8 binStep) external pure returns (uint24) { + function idFromPrice(uint256 price, uint16 binStep) external pure returns (uint24) { return PriceHelper.getIdFromPrice(price, binStep); } } diff --git a/test/libraries/math/Encoded.t.sol b/test/libraries/math/Encoded.t.sol index fead524c..7df80b95 100644 --- a/test/libraries/math/Encoded.t.sol +++ b/test/libraries/math/Encoded.t.sol @@ -31,9 +31,9 @@ contract EncodedTest is Test { assertEq(v2, ((v << offset) >> offset) & mask, "test_SetAndDecode::1"); } - function testFuzz_decodeUint1(bytes32 x, uint256 offset) external { - uint256 v = x.decodeUint1(offset); - assertEq(v, (uint256(x) >> offset) & 1, "test_decodeUint1::1"); + function testFuzz_decodeBool(bytes32 x, uint256 offset) external { + bool v = x.decodeBool(offset); + assertEq(v ? 1 : 0, (uint256(x) >> offset) & 1, "test_decodeUint1::1"); } function testFuzz_decodeUint8(bytes32 x, uint256 offset) external { From bfd3058dbd3183f6d07c5e330cc893da0aaf0a52 Mon Sep 17 00:00:00 2001 From: Louis <107303182+0x0Louis@users.noreply.github.com> Date: Mon, 6 Mar 2023 12:03:09 +0100 Subject: [PATCH 25/47] Make sure to clean dirty bits (#90) --- src/libraries/PairParameterHelper.sol | 26 ++++++------- .../math/LiquidityConfigurations.sol | 9 +++-- src/libraries/math/PackedUint128Math.sol | 4 +- src/libraries/math/SampleMath.sol | 14 +++---- test/libraries/PairParameterHelper.t.sol | 39 +++++++++++++++++++ 5 files changed, 65 insertions(+), 27 deletions(-) diff --git a/src/libraries/PairParameterHelper.sol b/src/libraries/PairParameterHelper.sol index b1c1e5ef..e8635f18 100644 --- a/src/libraries/PairParameterHelper.sol +++ b/src/libraries/PairParameterHelper.sol @@ -31,6 +31,7 @@ library PairParameterHelper { error PairParametersHelper__InvalidParameter(); + uint256 internal constant OFFSET_BASE_FACTOR = 0; uint256 internal constant OFFSET_FILTER_PERIOD = 16; uint256 internal constant OFFSET_DECAY_PERIOD = 28; uint256 internal constant OFFSET_REDUCTION_FACTOR = 40; @@ -56,7 +57,7 @@ library PairParameterHelper { * @return baseFactor The base factor */ function getBaseFactor(bytes32 params) internal pure returns (uint16 baseFactor) { - baseFactor = params.decodeUint16(0); + baseFactor = params.decodeUint16(OFFSET_BASE_FACTOR); } /** @@ -315,7 +316,7 @@ library PairParameterHelper { * @param variableFeeControl The variable fee control * @param protocolShare The protocol share * @param maxVolatilityAccumulator The max volatility accumulator - * @return The updated encoded pair parameters + * @return newParams The updated encoded pair parameters */ function setStaticFeeParameters( bytes32 params, @@ -326,25 +327,22 @@ library PairParameterHelper { uint24 variableFeeControl, uint16 protocolShare, uint24 maxVolatilityAccumulator - ) internal pure returns (bytes32) { + ) internal pure returns (bytes32 newParams) { if ( filterPeriod > decayPeriod || decayPeriod > Encoded.MASK_UINT12 || reductionFactor > Constants.BASIS_POINT_MAX || protocolShare > MAX_PROTOCOL_SHARE || maxVolatilityAccumulator > Encoded.MASK_UINT20 ) revert PairParametersHelper__InvalidParameter(); - uint256 staticParams; - - assembly { - staticParams := or(baseFactor, shl(OFFSET_FILTER_PERIOD, filterPeriod)) - staticParams := or(staticParams, shl(OFFSET_DECAY_PERIOD, decayPeriod)) - staticParams := or(staticParams, shl(OFFSET_REDUCTION_FACTOR, reductionFactor)) - staticParams := or(staticParams, shl(OFFSET_VAR_FEE_CONTROL, variableFeeControl)) - staticParams := or(staticParams, shl(OFFSET_PROTOCOL_SHARE, protocolShare)) - staticParams := or(staticParams, shl(OFFSET_MAX_VOL_ACC, maxVolatilityAccumulator)) - } + newParams = newParams.set(baseFactor, Encoded.MASK_UINT16, OFFSET_BASE_FACTOR); + newParams = newParams.set(filterPeriod, Encoded.MASK_UINT12, OFFSET_FILTER_PERIOD); + newParams = newParams.set(decayPeriod, Encoded.MASK_UINT12, OFFSET_DECAY_PERIOD); + newParams = newParams.set(reductionFactor, Encoded.MASK_UINT16, OFFSET_REDUCTION_FACTOR); + newParams = newParams.set(variableFeeControl, Encoded.MASK_UINT24, OFFSET_VAR_FEE_CONTROL); + newParams = newParams.set(protocolShare, Encoded.MASK_UINT16, OFFSET_PROTOCOL_SHARE); + newParams = newParams.set(maxVolatilityAccumulator, Encoded.MASK_UINT24, OFFSET_MAX_VOL_ACC); - return params.set(staticParams, MASK_STATIC_PARAMETER, 0); + return params.set(uint256(newParams), MASK_STATIC_PARAMETER, 0); } /** diff --git a/src/libraries/math/LiquidityConfigurations.sol b/src/libraries/math/LiquidityConfigurations.sol index 8c5ed592..f57d952c 100644 --- a/src/libraries/math/LiquidityConfigurations.sol +++ b/src/libraries/math/LiquidityConfigurations.sol @@ -17,6 +17,7 @@ library LiquidityConfigurations { error LiquidityConfigurations__InvalidConfig(); + uint256 private constant OFFSET_ID = 0; uint256 private constant OFFSET_DISTRIBUTION_Y = 24; uint256 private constant OFFSET_DISTRIBUTION_X = 88; @@ -38,9 +39,9 @@ library LiquidityConfigurations { pure returns (bytes32 config) { - assembly { - config := or(shl(OFFSET_DISTRIBUTION_X, distributionX), or(shl(OFFSET_DISTRIBUTION_Y, distributionY), id)) - } + config = config.set(distributionX, Encoded.MASK_UINT64, OFFSET_DISTRIBUTION_X); + config = config.set(distributionY, Encoded.MASK_UINT64, OFFSET_DISTRIBUTION_Y); + config = config.set(id, Encoded.MASK_UINT24, OFFSET_ID); } /** @@ -61,7 +62,7 @@ library LiquidityConfigurations { { distributionX = config.decodeUint64(OFFSET_DISTRIBUTION_X); distributionY = config.decodeUint64(OFFSET_DISTRIBUTION_Y); - id = config.decodeUint24(0); + id = config.decodeUint24(OFFSET_ID); if (uint256(config) > type(uint152).max || distributionX > PRECISION || distributionY > PRECISION) { revert LiquidityConfigurations__InvalidConfig(); diff --git a/src/libraries/math/PackedUint128Math.sol b/src/libraries/math/PackedUint128Math.sol index ed6e79b0..f8d35837 100644 --- a/src/libraries/math/PackedUint128Math.sol +++ b/src/libraries/math/PackedUint128Math.sol @@ -29,7 +29,7 @@ library PackedUint128Math { */ function encode(uint128 x1, uint128 x2) internal pure returns (bytes32 z) { assembly { - z := or(x1, shl(OFFSET, x2)) + z := or(and(x1, MASK_128), shl(OFFSET, x2)) } } @@ -42,7 +42,7 @@ library PackedUint128Math { */ function encodeFirst(uint128 x1) internal pure returns (bytes32 z) { assembly { - z := x1 + z := and(x1, MASK_128) } } diff --git a/src/libraries/math/SampleMath.sol b/src/libraries/math/SampleMath.sol index 78bb5780..8736e3b1 100644 --- a/src/libraries/math/SampleMath.sol +++ b/src/libraries/math/SampleMath.sol @@ -20,6 +20,7 @@ import {Encoded} from "./Encoded.sol"; library SampleMath { using Encoded for bytes32; + uint256 internal constant OFFSET_ORACLE_LENGTH = 0; uint256 internal constant OFFSET_CUMULATIVE_ID = 16; uint256 internal constant OFFSET_CUMULATIVE_VOLATILITY = 80; uint256 internal constant OFFSET_CUMULATIVE_BIN_CROSSED = 144; @@ -44,13 +45,12 @@ library SampleMath { uint8 sampleLifetime, uint40 createdAt ) internal pure returns (bytes32 sample) { - assembly { - sample := or(oracleLength, shl(OFFSET_CUMULATIVE_ID, cumulativeId)) - sample := or(sample, shl(OFFSET_CUMULATIVE_VOLATILITY, cumulativeVolatility)) - sample := or(sample, shl(OFFSET_CUMULATIVE_BIN_CROSSED, cumulativeBinCrossed)) - sample := or(sample, shl(OFFSET_SAMPLE_LIFETIME, sampleLifetime)) - sample := or(sample, shl(OFFSET_SAMPLE_CREATION, createdAt)) - } + sample = sample.set(oracleLength, Encoded.MASK_UINT16, OFFSET_ORACLE_LENGTH); + sample = sample.set(cumulativeId, Encoded.MASK_UINT64, OFFSET_CUMULATIVE_ID); + sample = sample.set(cumulativeVolatility, Encoded.MASK_UINT64, OFFSET_CUMULATIVE_VOLATILITY); + sample = sample.set(cumulativeBinCrossed, Encoded.MASK_UINT64, OFFSET_CUMULATIVE_BIN_CROSSED); + sample = sample.set(sampleLifetime, Encoded.MASK_UINT8, OFFSET_SAMPLE_LIFETIME); + sample = sample.set(createdAt, Encoded.MASK_UINT40, OFFSET_SAMPLE_CREATION); } /** diff --git a/test/libraries/PairParameterHelper.t.sol b/test/libraries/PairParameterHelper.t.sol index 55ab15b9..b4feef43 100644 --- a/test/libraries/PairParameterHelper.t.sol +++ b/test/libraries/PairParameterHelper.t.sol @@ -19,6 +19,45 @@ contract PairParameterHelperTest is Test { uint24 maxVolatilityAccumulator; } + function testFuzz_StaticFeeParametersDirtyBits(bytes32 params, StaticFeeParameters memory sfp) external { + vm.assume( + sfp.filterPeriod <= sfp.decayPeriod && sfp.decayPeriod <= Encoded.MASK_UINT12 + && sfp.reductionFactor <= Constants.BASIS_POINT_MAX + && sfp.protocolShare <= PairParameterHelper.MAX_PROTOCOL_SHARE + && sfp.maxVolatilityAccumulator <= Encoded.MASK_UINT20 + ); + + uint256 dirtyBits = type(uint256).max << 24; + + bytes32 newParams = params.setStaticFeeParameters( + uint16(uint256(sfp.baseFactor) | dirtyBits), + uint16(uint256(sfp.filterPeriod) | dirtyBits), + uint16(uint256(sfp.decayPeriod) | dirtyBits), + uint16(uint256(sfp.reductionFactor) | dirtyBits), + uint24(uint256(sfp.variableFeeControl) | dirtyBits), + uint16(uint256(sfp.protocolShare) | dirtyBits), + uint24(uint256(sfp.maxVolatilityAccumulator) | dirtyBits) + ); + + assertEq( + newParams >> PairParameterHelper.OFFSET_VOL_ACC, + params >> PairParameterHelper.OFFSET_VOL_ACC, + "testFuzz_StaticFeeParametersDirtyBits::1" + ); + + assertEq(newParams.getBaseFactor(), sfp.baseFactor, "testFuzz_StaticFeeParametersDirtyBits::2"); + assertEq(newParams.getFilterPeriod(), sfp.filterPeriod, "testFuzz_StaticFeeParametersDirtyBits::3"); + assertEq(newParams.getDecayPeriod(), sfp.decayPeriod, "testFuzz_StaticFeeParametersDirtyBits::4"); + assertEq(newParams.getReductionFactor(), sfp.reductionFactor, "testFuzz_StaticFeeParametersDirtyBits::5"); + assertEq(newParams.getVariableFeeControl(), sfp.variableFeeControl, "testFuzz_StaticFeeParametersDirtyBits::6"); + assertEq(newParams.getProtocolShare(), sfp.protocolShare, "testFuzz_StaticFeeParametersDirtyBits::7"); + assertEq( + newParams.getMaxVolatilityAccumulator(), + sfp.maxVolatilityAccumulator, + "testFuzz_StaticFeeParametersDirtyBits::8" + ); + } + function testFuzz_StaticFeeParameters(bytes32 params, StaticFeeParameters memory sfp) external { vm.assume( sfp.filterPeriod <= sfp.decayPeriod && sfp.decayPeriod <= Encoded.MASK_UINT12 From 60ae8cc9bcc17f35d6a8e651a87a2b088542860d Mon Sep 17 00:00:00 2001 From: Louis <107303182+0x0Louis@users.noreply.github.com> Date: Sat, 11 Mar 2023 19:37:31 +0100 Subject: [PATCH 26/47] Fix oracle and interface (#91) * fix small issue on oracle * add missing functions on ILBLegacyRouter --- src/LBPair.sol | 2 +- src/interfaces/ILBLegacyRouter.sol | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/LBPair.sol b/src/LBPair.sol index c5687591..50272bc5 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -325,7 +325,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { uint40 deltaTime = lookupTimestamp - timeOfLastUpdate; - cumulativeId += uint64(parameters.getIdReference()) * deltaTime; + cumulativeId += uint64(parameters.getActiveId()) * deltaTime; cumulativeVolatility += uint64(parameters.getVolatilityAccumulator()) * deltaTime; } } diff --git a/src/interfaces/ILBLegacyRouter.sol b/src/interfaces/ILBLegacyRouter.sol index 4df2bef2..d8ad6f9d 100644 --- a/src/interfaces/ILBLegacyRouter.sol +++ b/src/interfaces/ILBLegacyRouter.sol @@ -31,6 +31,12 @@ interface ILBLegacyRouter { uint256 deadline; } + function factory() external view returns (address); + + function wavax() external view returns (address); + + function oldFactory() external view returns (address); + function getIdFromPrice(ILBLegacyPair LBPair, uint256 price) external view returns (uint24); function getPriceFromId(ILBLegacyPair LBPair, uint24 id) external view returns (uint256); From acab5923d0af613dfd0e5a5f020a5a444f1245e8 Mon Sep 17 00:00:00 2001 From: Louis <107303182+0x0Louis@users.noreply.github.com> Date: Sun, 12 Mar 2023 09:14:50 +0100 Subject: [PATCH 27/47] add all missing functions in legacy pairs (#92) --- src/interfaces/ILBLegacyPair.sol | 222 +++++++++++++++++++++++++++++- src/interfaces/ILBLegacyToken.sol | 40 ++++++ 2 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 src/interfaces/ILBLegacyToken.sol diff --git a/src/interfaces/ILBLegacyPair.sol b/src/interfaces/ILBLegacyPair.sol index 3293ed9e..e6f6e35f 100644 --- a/src/interfaces/ILBLegacyPair.sol +++ b/src/interfaces/ILBLegacyPair.sol @@ -4,19 +4,237 @@ pragma solidity 0.8.10; import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; +import {ILBLegacyToken} from "./ILBLegacyToken.sol"; + /// @title Liquidity Book Pair V2 Interface /// @author Trader Joe /// @notice Required interface of LBPair contract -interface ILBLegacyPair { +interface ILBLegacyPair is ILBLegacyToken { + /// @dev Structure to store the protocol fees: + /// - binStep: The bin step + /// - baseFactor: The base factor + /// - filterPeriod: The filter period, where the fees stays constant + /// - decayPeriod: The decay period, where the fees are halved + /// - reductionFactor: The reduction factor, used to calculate the reduction of the accumulator + /// - variableFeeControl: The variable fee control, used to control the variable fee, can be 0 to disable them + /// - protocolShare: The share of fees sent to protocol + /// - maxVolatilityAccumulated: The max value of volatility accumulated + /// - volatilityAccumulated: The value of volatility accumulated + /// - volatilityReference: The value of volatility reference + /// - indexRef: The index reference + /// - time: The last time the accumulator was called + struct FeeParameters { + // 144 lowest bits in slot + uint16 binStep; + uint16 baseFactor; + uint16 filterPeriod; + uint16 decayPeriod; + uint16 reductionFactor; + uint24 variableFeeControl; + uint16 protocolShare; + uint24 maxVolatilityAccumulated; + // 112 highest bits in slot + uint24 volatilityAccumulated; + uint24 volatilityReference; + uint24 indexRef; + uint40 time; + } + + /// @dev Structure used during swaps to distributes the fees: + /// - total: The total amount of fees + /// - protocol: The amount of fees reserved for protocol + struct FeesDistribution { + uint128 total; + uint128 protocol; + } + + /// @dev Structure to store the reserves of bins: + /// - reserveX: The current reserve of tokenX of the bin + /// - reserveY: The current reserve of tokenY of the bin + struct Bin { + uint112 reserveX; + uint112 reserveY; + uint256 accTokenXPerShare; + uint256 accTokenYPerShare; + } + + /// @dev Structure to store the information of the pair such as: + /// slot0: + /// - activeId: The current id used for swaps, this is also linked with the price + /// - reserveX: The sum of amounts of tokenX across all bins + /// slot1: + /// - reserveY: The sum of amounts of tokenY across all bins + /// - oracleSampleLifetime: The lifetime of an oracle sample + /// - oracleSize: The current size of the oracle, can be increase by users + /// - oracleActiveSize: The current active size of the oracle, composed only from non empty data sample + /// - oracleLastTimestamp: The current last timestamp at which a sample was added to the circular buffer + /// - oracleId: The current id of the oracle + /// slot2: + /// - feesX: The current amount of fees to distribute in tokenX (total, protocol) + /// slot3: + /// - feesY: The current amount of fees to distribute in tokenY (total, protocol) + struct PairInformation { + uint24 activeId; + uint136 reserveX; + uint136 reserveY; + uint16 oracleSampleLifetime; + uint16 oracleSize; + uint16 oracleActiveSize; + uint40 oracleLastTimestamp; + uint16 oracleId; + FeesDistribution feesX; + FeesDistribution feesY; + } + + /// @dev Structure to store the debts of users + /// - debtX: The tokenX's debt + /// - debtY: The tokenY's debt + struct Debts { + uint256 debtX; + uint256 debtY; + } + + /// @dev Structure to store fees: + /// - tokenX: The amount of fees of token X + /// - tokenY: The amount of fees of token Y + struct Fees { + uint128 tokenX; + uint128 tokenY; + } + + /// @dev Structure to minting informations: + /// - amountXIn: The amount of token X sent + /// - amountYIn: The amount of token Y sent + /// - amountXAddedToPair: The amount of token X that have been actually added to the pair + /// - amountYAddedToPair: The amount of token Y that have been actually added to the pair + /// - activeFeeX: Fees X currently generated + /// - activeFeeY: Fees Y currently generated + /// - totalDistributionX: Total distribution of token X. Should be 1e18 (100%) or 0 (0%) + /// - totalDistributionY: Total distribution of token Y. Should be 1e18 (100%) or 0 (0%) + /// - id: Id of the current working bin when looping on the distribution array + /// - amountX: The amount of token X deposited in the current bin + /// - amountY: The amount of token Y deposited in the current bin + /// - distributionX: Distribution of token X for the current working bin + /// - distributionY: Distribution of token Y for the current working bin + struct MintInfo { + uint256 amountXIn; + uint256 amountYIn; + uint256 amountXAddedToPair; + uint256 amountYAddedToPair; + uint256 activeFeeX; + uint256 activeFeeY; + uint256 totalDistributionX; + uint256 totalDistributionY; + uint256 id; + uint256 amountX; + uint256 amountY; + uint256 distributionX; + uint256 distributionY; + } + + event Swap( + address indexed sender, + address indexed recipient, + uint256 indexed id, + bool swapForY, + uint256 amountIn, + uint256 amountOut, + uint256 volatilityAccumulated, + uint256 fees + ); + + event FlashLoan(address indexed sender, address indexed receiver, IERC20 token, uint256 amount, uint256 fee); + + event CompositionFee( + address indexed sender, address indexed recipient, uint256 indexed id, uint256 feesX, uint256 feesY + ); + + event DepositedToBin( + address indexed sender, address indexed recipient, uint256 indexed id, uint256 amountX, uint256 amountY + ); + + event WithdrawnFromBin( + address indexed sender, address indexed recipient, uint256 indexed id, uint256 amountX, uint256 amountY + ); + + event FeesCollected(address indexed sender, address indexed recipient, uint256 amountX, uint256 amountY); + + event ProtocolFeesCollected(address indexed sender, address indexed recipient, uint256 amountX, uint256 amountY); + + event OracleSizeIncreased(uint256 previousSize, uint256 newSize); + function tokenX() external view returns (IERC20); function tokenY() external view returns (IERC20); + function factory() external view returns (address); + function getReservesAndId() external view returns (uint256 reserveX, uint256 reserveY, uint256 activeId); - function swap(bool sentTokenY, address to) external returns (uint256 amountXOut, uint256 amountYOut); + function getGlobalFees() + external + view + returns (uint128 feesXTotal, uint128 feesYTotal, uint128 feesXProtocol, uint128 feesYProtocol); + + function getOracleParameters() + external + view + returns ( + uint256 oracleSampleLifetime, + uint256 oracleSize, + uint256 oracleActiveSize, + uint256 oracleLastTimestamp, + uint256 oracleId, + uint256 min, + uint256 max + ); + + function getOracleSampleFrom(uint256 timeDelta) + external + view + returns (uint256 cumulativeId, uint256 cumulativeAccumulator, uint256 cumulativeBinCrossed); + + function feeParameters() external view returns (FeeParameters memory); function findFirstNonEmptyBinId(uint24 id_, bool sentTokenY) external view returns (uint24 id); function getBin(uint24 id) external view returns (uint256 reserveX, uint256 reserveY); + + function pendingFees(address account, uint256[] memory ids) + external + view + returns (uint256 amountX, uint256 amountY); + + function swap(bool sentTokenY, address to) external returns (uint256 amountXOut, uint256 amountYOut); + + function flashLoan(address receiver, IERC20 token, uint256 amount, bytes calldata data) external; + + function mint( + uint256[] calldata ids, + uint256[] calldata distributionX, + uint256[] calldata distributionY, + address to + ) external returns (uint256 amountXAddedToPair, uint256 amountYAddedToPair, uint256[] memory liquidityMinted); + + function burn(uint256[] calldata ids, uint256[] calldata amounts, address to) + external + returns (uint256 amountX, uint256 amountY); + + function increaseOracleLength(uint16 newSize) external; + + function collectFees(address account, uint256[] calldata ids) external returns (uint256 amountX, uint256 amountY); + + function collectProtocolFees() external returns (uint128 amountX, uint128 amountY); + + function setFeesParameters(bytes32 packedFeeParameters) external; + + function forceDecay() external; + + function initialize( + IERC20 tokenX, + IERC20 tokenY, + uint24 activeId, + uint16 sampleLifetime, + bytes32 packedFeeParameters + ) external; } diff --git a/src/interfaces/ILBLegacyToken.sol b/src/interfaces/ILBLegacyToken.sol new file mode 100644 index 00000000..7a06d90a --- /dev/null +++ b/src/interfaces/ILBLegacyToken.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +import "openzeppelin/utils/introspection/IERC165.sol"; + +/// @title Liquidity Book V2 Token Interface +/// @author Trader Joe +/// @notice Required interface of LBToken contract +interface ILBLegacyToken is IERC165 { + event TransferSingle(address indexed sender, address indexed from, address indexed to, uint256 id, uint256 amount); + + event TransferBatch( + address indexed sender, address indexed from, address indexed to, uint256[] ids, uint256[] amounts + ); + + event ApprovalForAll(address indexed account, address indexed sender, bool approved); + + function name() external view returns (string memory); + + function symbol() external view returns (string memory); + + function balanceOf(address account, uint256 id) external view returns (uint256); + + function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) + external + view + returns (uint256[] memory batchBalances); + + function totalSupply(uint256 id) external view returns (uint256); + + function isApprovedForAll(address owner, address spender) external view returns (bool); + + function setApprovalForAll(address sender, bool approved) external; + + function safeTransferFrom(address from, address to, uint256 id, uint256 amount) external; + + function safeBatchTransferFrom(address from, address to, uint256[] calldata id, uint256[] calldata amount) + external; +} From 7fb4a705a61e17a83353bb3c828945a01cbd8a82 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 15 Mar 2023 12:41:32 +0000 Subject: [PATCH 28/47] update outdated natspec --- src/libraries/PairParameterHelper.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/PairParameterHelper.sol b/src/libraries/PairParameterHelper.sol index e8635f18..52aab2c5 100644 --- a/src/libraries/PairParameterHelper.sol +++ b/src/libraries/PairParameterHelper.sol @@ -204,11 +204,11 @@ library PairParameterHelper { } /** - * @dev Get the delta between the active index and the reference index + * @dev Get the delta between the current active index and the cached active index * @param params The encoded pair parameters, as follows: * [0 - 232[: other parameters * [232 - 256[: active index (24 bits) - * @param activeId The active index + * @param activeId The current active index * @return The delta */ function getDeltaId(bytes32 params, uint24 activeId) internal pure returns (uint24) { From ba4dcfeb143fad0e1479b81baf18d320d33edc61 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Tue, 21 Mar 2023 19:10:28 +0100 Subject: [PATCH 29/47] Issue 01: missing visibility for open presets --- src/LBFactory.sol | 29 +++++++++++++++++++++++++++++ src/interfaces/ILBFactory.sol | 2 ++ 2 files changed, 31 insertions(+) diff --git a/src/LBFactory.sol b/src/LBFactory.sol index e32cf4d2..84280054 100644 --- a/src/LBFactory.sol +++ b/src/LBFactory.sol @@ -226,6 +226,35 @@ contract LBFactory is PendingOwnable, ILBFactory { return _presets.keys(); } + /** + * @notice View function to return the list of open binSteps + * @return openBinStep The list of open binSteps + */ + function getOpenBinSteps() external view override returns (uint256[] memory openBinStep) { + uint256 length = _presets.length(); + + if (length > 0) { + openBinStep = new uint256[](length); + + uint256 index; + + for (uint256 i; i < length; ++i) { + (uint256 binStep, uint256 preset) = _presets.at(i); + + if (_isPresetOpen(bytes32(preset))) { + openBinStep[index] = binStep; + index++; + } + } + + if (index < length) { + assembly { + mstore(openBinStep, index) + } + } + } + } + /** * @notice View function to return all the LBPair of a pair of tokens * @param tokenX The first token of the pair diff --git a/src/interfaces/ILBFactory.sol b/src/interfaces/ILBFactory.sol index 1168b3c0..44e53259 100644 --- a/src/interfaces/ILBFactory.sol +++ b/src/interfaces/ILBFactory.sol @@ -118,6 +118,8 @@ interface ILBFactory is IPendingOwnable { function getAllBinSteps() external view returns (uint256[] memory presetsBinStep); + function getOpenBinSteps() external view returns (uint256[] memory openBinStep); + function getAllLBPairs(IERC20 tokenX, IERC20 tokenY) external view From 2b9f2924430b03bc78ee50a859f221f02b8be66b Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Tue, 21 Mar 2023 19:11:13 +0100 Subject: [PATCH 30/47] Issue 03: typographical errors --- src/LBFactory.sol | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/LBFactory.sol b/src/LBFactory.sol index 84280054..bb7cb3e8 100644 --- a/src/LBFactory.sol +++ b/src/LBFactory.sol @@ -6,8 +6,7 @@ import {EnumerableSet} from "openzeppelin/utils/structs/EnumerableSet.sol"; import {EnumerableMap} from "openzeppelin/utils/structs/EnumerableMap.sol"; import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; -import {BinHelper, PairParameterHelper} from "./libraries/BinHelper.sol"; -import {Constants} from "./libraries/Constants.sol"; +import {PairParameterHelper} from "./libraries/PairParameterHelper.sol"; import {Encoded} from "./libraries/math/Encoded.sol"; import {ImmutableClone} from "./libraries/ImmutableClone.sol"; import {PendingOwnable} from "./libraries/PendingOwnable.sol"; @@ -47,7 +46,8 @@ contract LBFactory is PendingOwnable, ILBFactory { /** * @dev Mapping from a (tokenA, tokenB, binStep) to a LBPair. The tokens are ordered to save gas, but they can be - * in the reverse order in the actual pair. Always query one of the 2 tokens of the pair to assert the order of the 2 tokens + * in the reverse order in the actual pair. + * Always query one of the 2 tokens of the pair to assert the order of the 2 tokens */ mapping(IERC20 => mapping(IERC20 => mapping(uint256 => LBPairInformation))) private _lbPairsInfo; @@ -55,8 +55,10 @@ contract LBFactory is PendingOwnable, ILBFactory { EnumerableSet.AddressSet private _quoteAssetWhitelist; /** - * @dev Whether a LBPair was created with a bin step, if the bit at `index` is 1, it means that the LBPair with binStep `index` exists - * The max binStep set is 247. We use this method instead of an array to keep it ordered and to reduce gas + * @dev Mapping from a (tokenA, tokenB) to a set of available bin steps, this is used to keep track of the + * bin steps that are already used for a pair. + * The tokens are ordered to save gas, but they can be in the reverse order in the actual pair. + * Always query one of the 2 tokens of the pair to assert the order of the 2 tokens */ mapping(IERC20 => mapping(IERC20 => EnumerableSet.UintSet)) private _availableLBPairBinSteps; @@ -64,7 +66,6 @@ contract LBFactory is PendingOwnable, ILBFactory { * @notice Constructor * @param feeRecipient The address of the fee recipient * @param flashLoanFee The value of the fee for flash loan - * */ constructor(address feeRecipient, uint256 flashLoanFee) { if (flashLoanFee > _MAX_FLASHLOAN_FEE) revert LBFactory__FlashLoanFeeAboveMax(flashLoanFee, _MAX_FLASHLOAN_FEE); @@ -100,10 +101,10 @@ contract LBFactory is PendingOwnable, ILBFactory { } /** - * @notice Get the fee for flash loans - * @return flashloanFee + * @notice Get the fee for flash loans, in 1e18 + * @return flashLoanFee The fee for flash loans, in 1e18 */ - function getFlashLoanFee() external view override returns (uint256 flashloanFee) { + function getFlashLoanFee() external view override returns (uint256 flashLoanFee) { return _flashLoanFee; } @@ -412,7 +413,7 @@ contract LBFactory is PendingOwnable, ILBFactory { * @param binStep The bin step in basis point, used to calculate the price * @param baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep * @param filterPeriod The period where the accumulator value is untouched, prevent spam - * @param decayPeriod The period where the accumulator value is halved + * @param decayPeriod The period where the accumulator value is decayed, by the reduction factor * @param reductionFactor The reduction factor, used to calculate the reduction of the accumulator * @param variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it * @param protocolShare The share of the fees received by the protocol @@ -497,7 +498,7 @@ contract LBFactory is PendingOwnable, ILBFactory { * @param binStep The bin step in basis point, used to calculate the price * @param baseFactor The base factor, used to calculate the base fee, baseFee = baseFactor * binStep * @param filterPeriod The period where the accumulator value is untouched, prevent spam - * @param decayPeriod The period where the accumulator value is halved + * @param decayPeriod The period where the accumulator value is decayed, by the reduction factor * @param reductionFactor The reduction factor, used to calculate the reduction of the accumulator * @param variableFeeControl The variable fee control, used to control the variable fee, can be 0 to disable it * @param protocolShare The share of the fees received by the protocol From 7ab0ce8edca1df4d2691dfcf7cb29206e805bd5b Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Tue, 21 Mar 2023 19:22:58 +0100 Subject: [PATCH 31/47] issue 04: gas optimization --- src/LBFactory.sol | 37 +++++++++++++++++------------------ src/interfaces/ILBFactory.sol | 2 +- test/LBFactory.t.sol | 16 ++++++++------- test/LBRouter.Liquidity.t.sol | 2 +- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/LBFactory.sol b/src/LBFactory.sol index bb7cb3e8..3117539e 100644 --- a/src/LBFactory.sol +++ b/src/LBFactory.sol @@ -330,15 +330,12 @@ contract LBFactory is PendingOwnable, ILBFactory { if (!_presets.contains(binStep)) revert LBFactory__BinStepHasNoPreset(binStep); bytes32 preset = bytes32(_presets.get(binStep)); + bool isOwner = msg.sender == owner(); - if (!_isPresetOpen(preset) && msg.sender != owner()) { - revert LBFactory__FunctionIsLockedForUsers(msg.sender, binStep); + if (!_isPresetOpen(preset) && !isOwner) { + revert LBFactory__PresetIsLockedForUsers(msg.sender, binStep); } - address implementation = _lbPairImplementation; - - if (implementation == address(0)) revert LBFactory__ImplementationNotSet(); - if (!_quoteAssetWhitelist.contains(address(tokenY))) revert LBFactory__QuoteAssetNotWhitelisted(tokenY); if (tokenX == tokenY) revert LBFactory__IdenticalAddresses(tokenX); @@ -354,13 +351,19 @@ contract LBFactory is PendingOwnable, ILBFactory { revert LBFactory__LBPairAlreadyExists(tokenX, tokenY, binStep); } - pair = ILBPair( - ImmutableClone.cloneDeterministic( - implementation, - abi.encodePacked(tokenX, tokenY, binStep), - keccak256(abi.encode(tokenA, tokenB, binStep)) - ) - ); + { + address implementation = _lbPairImplementation; + + if (implementation == address(0)) revert LBFactory__ImplementationNotSet(); + + pair = ILBPair( + ImmutableClone.cloneDeterministic( + implementation, + abi.encodePacked(tokenX, tokenY, binStep), + keccak256(abi.encode(tokenA, tokenB, binStep)) + ) + ); + } pair.initialize( preset.getBaseFactor(), @@ -373,12 +376,8 @@ contract LBFactory is PendingOwnable, ILBFactory { activeId ); - _lbPairsInfo[tokenA][tokenB][binStep] = LBPairInformation({ - binStep: binStep, - LBPair: pair, - createdByOwner: msg.sender == owner(), - ignoredForRouting: false - }); + _lbPairsInfo[tokenA][tokenB][binStep] = + LBPairInformation({binStep: binStep, LBPair: pair, createdByOwner: isOwner, ignoredForRouting: false}); _allLBPairs.push(pair); _availableLBPairBinSteps[tokenA][tokenB].add(binStep); diff --git a/src/interfaces/ILBFactory.sol b/src/interfaces/ILBFactory.sol index 44e53259..a0088147 100644 --- a/src/interfaces/ILBFactory.sol +++ b/src/interfaces/ILBFactory.sol @@ -22,7 +22,7 @@ interface ILBFactory is IPendingOwnable { error LBFactory__LBPairNotCreated(IERC20 tokenX, IERC20 tokenY, uint256 binStep); error LBFactory__FlashLoanFeeAboveMax(uint256 fees, uint256 maxFees); error LBFactory__BinStepTooLow(uint256 binStep); - error LBFactory__FunctionIsLockedForUsers(address user, uint256 binStep); + error LBFactory__PresetIsLockedForUsers(address user, uint256 binStep); error LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); error LBFactory__BinStepHasNoPreset(uint256 binStep); error LBFactory__PresetOpenStateIsAlreadyInTheSameState(); diff --git a/test/LBFactory.t.sol b/test/LBFactory.t.sol index 1c1d2ac5..7360f2f4 100644 --- a/test/LBFactory.t.sol +++ b/test/LBFactory.t.sol @@ -100,6 +100,7 @@ contract LiquidityBinFactoryTest is TestHelper { LBFactory anotherFactory = new LBFactory(DEV, DEFAULT_FLASHLOAN_FEE); anotherFactory.setPreset(1, 1, 1, 1, 1, 1, 1, 1, false); + anotherFactory.addQuoteAsset(usdc); // Reverts if there is no implementation set vm.expectRevert(ILBFactory.LBFactory__ImplementationNotSet.selector); @@ -176,7 +177,7 @@ contract LiquidityBinFactoryTest is TestHelper { // Users should not be able to create pairs by default vm.prank(ALICE); vm.expectRevert( - abi.encodeWithSelector(ILBFactory.LBFactory__FunctionIsLockedForUsers.selector, ALICE, DEFAULT_BIN_STEP) + abi.encodeWithSelector(ILBFactory.LBFactory__PresetIsLockedForUsers.selector, ALICE, DEFAULT_BIN_STEP) ); factory.createLBPair(link, usdc, ID_ONE, DEFAULT_BIN_STEP); @@ -211,7 +212,7 @@ contract LiquidityBinFactoryTest is TestHelper { vm.prank(ALICE); vm.expectRevert( - abi.encodeWithSelector(ILBFactory.LBFactory__FunctionIsLockedForUsers.selector, ALICE, DEFAULT_BIN_STEP) + abi.encodeWithSelector(ILBFactory.LBFactory__PresetIsLockedForUsers.selector, ALICE, DEFAULT_BIN_STEP) ); factory.createLBPair(link, usdc, ID_ONE, DEFAULT_BIN_STEP); @@ -222,7 +223,7 @@ contract LiquidityBinFactoryTest is TestHelper { // Alice can't create a pair if the factory is locked vm.prank(ALICE); vm.expectRevert( - abi.encodeWithSelector(ILBFactory.LBFactory__FunctionIsLockedForUsers.selector, ALICE, DEFAULT_BIN_STEP) + abi.encodeWithSelector(ILBFactory.LBFactory__PresetIsLockedForUsers.selector, ALICE, DEFAULT_BIN_STEP) ); factory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); @@ -245,11 +246,7 @@ contract LiquidityBinFactoryTest is TestHelper { DEFAULT_OPEN_STATE ); - vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__ImplementationNotSet.selector)); - newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); - // Can't create pair if the quote asset is not whitelisted - newFactory.setLBPairImplementation(address(new LBPair(newFactory))); vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__QuoteAssetNotWhitelisted.selector, usdc)); newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); @@ -262,6 +259,11 @@ contract LiquidityBinFactoryTest is TestHelper { vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__AddressZero.selector)); newFactory.createLBPair(IERC20(address(0)), usdc, ID_ONE, DEFAULT_BIN_STEP); + // Can't create a pair if the implementation is not set + vm.expectRevert(abi.encodeWithSelector(ILBFactory.LBFactory__ImplementationNotSet.selector)); + newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); + + newFactory.setLBPairImplementation(address(new LBPair(newFactory))); // Can't create the same pair twice (a revision should be created instead) newFactory.createLBPair(usdt, usdc, ID_ONE, DEFAULT_BIN_STEP); vm.expectRevert( diff --git a/test/LBRouter.Liquidity.t.sol b/test/LBRouter.Liquidity.t.sol index 808e7947..80db6135 100644 --- a/test/LBRouter.Liquidity.t.sol +++ b/test/LBRouter.Liquidity.t.sol @@ -63,7 +63,7 @@ contract LiquidityBinRouterTest is TestHelper { vm.expectRevert( abi.encodeWithSelector( - ILBFactory.LBFactory__FunctionIsLockedForUsers.selector, address(router), DEFAULT_BIN_STEP + ILBFactory.LBFactory__PresetIsLockedForUsers.selector, address(router), DEFAULT_BIN_STEP ) ); router.createLBPair(bnb, usdc, ID_ONE, DEFAULT_BIN_STEP); From d9de76220a469148a0a682582092911b0472e9f5 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 00:32:06 +0100 Subject: [PATCH 32/47] issue 05: dos bins --- src/LBPair.sol | 11 +++++++---- test/LBPairFlashloan.t.sol | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/LBPair.sol b/src/LBPair.sol index 50272bc5..0015a9f3 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -592,8 +592,10 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { totalFees = balancesAfter.sub(reservesBefore); - bytes32 protocolFees = totalFees.scalarMulDivBasisPointRoundDown(parameters.getProtocolShare()); uint24 activeId = parameters.getActiveId(); + bytes32 protocolFees = totalSupply(activeId) == 0 + ? totalFees + : totalFees.scalarMulDivBasisPointRoundDown(parameters.getProtocolShare()); _reserves = balancesAfter; @@ -694,14 +696,15 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { if (amountToBurn == 0) revert LBPair__ZeroAmount(id); bytes32 binReserves = _bins[id]; + uint256 supply = totalSupply(id); - bytes32 amountsOutFromBin = binReserves.getAmountOutOfBin(amountToBurn, totalSupply(id)); + bytes32 amountsOutFromBin = binReserves.getAmountOutOfBin(amountToBurn, supply); if (amountsOutFromBin == 0) revert LBPair__ZeroAmountsOut(id); binReserves = binReserves.sub(amountsOutFromBin); - if (binReserves == 0) _tree.remove(id); + if (supply == amountToBurn) _tree.remove(id); _bins[id] = binReserves; amounts[i] = amountsOutFromBin; @@ -977,7 +980,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { if (shares == 0 || amountsInToBin == 0) revert LBPair__ZeroShares(id); - if (binReserves == 0) _tree.add(id); + if (supply == 0) _tree.add(id); _bins[id] = binReserves.add(amountsInToBin); } diff --git a/test/LBPairFlashloan.t.sol b/test/LBPairFlashloan.t.sol index 2f646f93..f1cf951d 100644 --- a/test/LBPairFlashloan.t.sol +++ b/test/LBPairFlashloan.t.sol @@ -102,4 +102,24 @@ contract LBPairFlashloanTest is TestHelper { vm.expectRevert(ILBPair.LBPair__ZeroBorrowAmount.selector); pairWnative.flashLoan(borrower, 0, ""); } + + function test_EdgeCaseFlashLoanBinDOS() external { + // remove the active liquidity + removeLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 1, 1); + + bytes32 amountsBorrowed = uint128(1e17).encode(uint128(1e17)); + bytes memory data = abi.encode(type(uint128).max, type(uint128).max, Constants.CALLBACK_SUCCESS, 0); + + pairWnative.flashLoan(borrower, amountsBorrowed, data); + + (uint256 x, uint256 y) = pairWnative.getBin(ID_ONE); + + assertEq(x, 0, "test_EdgeCaseFlashLoanBinDOS::1"); + assertEq(y, 0, "test_EdgeCaseFlashLoanBinDOS::2"); + + addLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 1e18, 1, 1); + + assertEq(pairWnative.getNextNonEmptyBin(true, ID_ONE + 1), ID_ONE, "test_EdgeCaseFlashLoanBinDOS::3"); + assertEq(pairWnative.getNextNonEmptyBin(false, ID_ONE - 1), ID_ONE, "test_EdgeCaseFlashLoanBinDOS::4"); + } } From 952af1666e6483cc87ed1a015a0b9762a76e32e9 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 00:45:14 +0100 Subject: [PATCH 33/47] issue 06: mint and burn mint insufficient tokens when using the same id --- src/LBPair.sol | 88 ++++++++++++++++++++++++-------------- src/LBToken.sol | 68 +++++++++-------------------- src/interfaces/ILBPair.sol | 6 +++ test/LBPairLiquidity.t.sol | 2 + test/LBToken.t.sol | 13 +++--- 5 files changed, 93 insertions(+), 84 deletions(-) diff --git a/src/LBPair.sol b/src/LBPair.sol index 0015a9f3..908b3693 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -629,42 +629,25 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { { if (liquidityConfigs.length == 0) revert LBPair__EmptyMarketConfigs(); - liquidityMinted = new uint256[](liquidityConfigs.length); - - uint256[] memory ids = new uint256[](liquidityConfigs.length); - bytes32[] memory amounts = new bytes32[](liquidityConfigs.length); + MintArrays memory arrays = MintArrays({ + ids: new uint256[](liquidityConfigs.length), + amounts: new bytes32[](liquidityConfigs.length), + liquidityMinted: new uint256[](liquidityConfigs.length) + }); bytes32 reserves = _reserves; - bytes32 parameters = _parameters; - uint24 activeId = parameters.getActiveId(); - amountsReceived = reserves.received(_tokenX(), _tokenY()); - amountsLeft = amountsReceived; - - for (uint256 i; i < liquidityConfigs.length;) { - (bytes32 maxAmountsInToBin, uint24 id) = liquidityConfigs[i].getAmountsAndId(amountsReceived); - (uint256 shares, bytes32 amountsIn, bytes32 amountsInToBin) = - _mintBin(activeId, id, maxAmountsInToBin, parameters); - - amountsLeft = amountsLeft.sub(amountsIn); - - ids[i] = id; - amounts[i] = amountsInToBin; - liquidityMinted[i] = shares; - - unchecked { - ++i; - } - } + amountsLeft = _mintBins(liquidityConfigs, amountsReceived, to, arrays); _reserves = reserves.add(amountsReceived.sub(amountsLeft)); - _mintBatch(to, ids, liquidityMinted); - if (amountsLeft > 0) amountsLeft.transfer(_tokenX(), _tokenY(), refundTo); - emit DepositedToBins(msg.sender, to, ids, amounts); + liquidityMinted = arrays.liquidityMinted; + + emit TransferBatch(msg.sender, address(0), to, arrays.ids, liquidityMinted); + emit DepositedToBins(msg.sender, to, arrays.ids, arrays.amounts); } /** @@ -698,6 +681,8 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { bytes32 binReserves = _bins[id]; uint256 supply = totalSupply(id); + _burn(from, id, amountToBurn); + bytes32 amountsOutFromBin = binReserves.getAmountOutOfBin(amountToBurn, supply); if (amountsOutFromBin == 0) revert LBPair__ZeroAmountsOut(id); @@ -717,10 +702,9 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { _reserves = _reserves.sub(amountsOut); - _burnBatch(from, ids, amountsToBurn); - amountsOut.transfer(_tokenX(), _tokenY(), to); + emit TransferBatch(msg.sender, from, address(0), ids, amountsToBurn); emit WithdrawnFromBins(msg.sender, to, ids, amounts); } @@ -930,7 +914,48 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { } /** - * @dev Helper function to mint liquidity in a bin + * @dev Helper function to mint liquidity in each bin in the liquidity configurations + * @param liquidityConfigs The liquidity configurations + * @param amountsReceived The amounts received + * @param to The address to mint the liquidity to + * @param arrays The arrays to store the results + * @return amountsLeft The amounts left + */ + function _mintBins( + bytes32[] calldata liquidityConfigs, + bytes32 amountsReceived, + address to, + MintArrays memory arrays + ) private returns (bytes32 amountsLeft) { + uint16 binStep = _binStep(); + + bytes32 parameters = _parameters; + uint24 activeId = parameters.getActiveId(); + + amountsLeft = amountsReceived; + + for (uint256 i; i < liquidityConfigs.length;) { + (bytes32 maxAmountsInToBin, uint24 id) = liquidityConfigs[i].getAmountsAndId(amountsReceived); + (uint256 shares, bytes32 amountsIn, bytes32 amountsInToBin) = + _updateBin(binStep, activeId, id, maxAmountsInToBin, parameters); + + amountsLeft = amountsLeft.sub(amountsIn); + + arrays.ids[i] = id; + arrays.amounts[i] = amountsInToBin; + arrays.liquidityMinted[i] = shares; + + _mint(to, id, shares); + + unchecked { + ++i; + } + } + } + + /** + * @dev Helper function to update a bin during minting + * @param binStep The bin step of the pair * @param activeId The id of the active bin * @param id The id of the bin * @param maxAmountsInToBin The maximum amounts in to the bin @@ -939,12 +964,11 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { * @return amountsIn The amounts in * @return amountsInToBin The amounts in to the bin */ - function _mintBin(uint24 activeId, uint24 id, bytes32 maxAmountsInToBin, bytes32 parameters) + function _updateBin(uint16 binStep, uint24 activeId, uint24 id, bytes32 maxAmountsInToBin, bytes32 parameters) internal returns (uint256 shares, bytes32 amountsIn, bytes32 amountsInToBin) { bytes32 binReserves = _bins[id]; - uint16 binStep = _binStep(); uint256 price = id.getPriceFromId(binStep); uint256 supply = totalSupply(id); diff --git a/src/LBToken.sol b/src/LBToken.sol index ede1db05..afff607a 100644 --- a/src/LBToken.sol +++ b/src/LBToken.sol @@ -14,7 +14,7 @@ import {ILBToken} from "./interfaces/ILBToken.sol"; * As it's only for ERC20s, the uri function is not implemented. * The contract is made for batch operations. */ -contract LBToken is ILBToken { +abstract contract LBToken is ILBToken { /** * @dev The mapping from account to token id to account balance. */ @@ -160,65 +160,39 @@ contract LBToken is ILBToken { } /** - * @dev Batch mints `amounts` of `ids` to `account`. - * The `account` must not be the zero address and the `ids` and `amounts` must have the same length. + * @dev Mint `amount` of `id` to `account`. + * The `account` must not be the zero address. + * The event should be emitted by the contract that inherits this contract. * @param account The address of the owner. - * @param ids The list of token ids. - * @param amounts The list of amounts to mint for each token id in `ids`. + * @param id The token id. + * @param amount The amount to mint. */ - function _mintBatch(address account, uint256[] memory ids, uint256[] memory amounts) - internal - notAddressZeroOrThis(account) - checkLength(ids.length, amounts.length) - { - mapping(uint256 => uint256) storage accountBalances = _balances[account]; - - for (uint256 i; i < ids.length;) { - uint256 id = ids[i]; - uint256 amount = amounts[i]; - - _totalSupplies[id] += amount; - - unchecked { - accountBalances[id] += amount; + function _mint(address account, uint256 id, uint256 amount) internal notAddressZeroOrThis(account) { + _totalSupplies[id] += amount; - ++i; - } + unchecked { + _balances[account][id] += amount; } - - emit TransferBatch(msg.sender, address(0), account, ids, amounts); } /** - * @dev Batch burns `amounts` of `ids` from `account`. - * The `ids` and `amounts` must have the same length. + * @dev Burn `amount` of `id` from `account`. + * The `account` must not be the zero address. + * The event should be emitted by the contract that inherits this contract. * @param account The address of the owner. - * @param ids The list of token ids. - * @param amounts The list of amounts to burn for each token id in `ids`. + * @param id The token id. + * @param amount The amount to burn. */ - function _burnBatch(address account, uint256[] memory ids, uint256[] memory amounts) - internal - notAddressZeroOrThis(account) - checkLength(ids.length, amounts.length) - { + function _burn(address account, uint256 id, uint256 amount) internal notAddressZeroOrThis(account) { mapping(uint256 => uint256) storage accountBalances = _balances[account]; - for (uint256 i; i < ids.length;) { - uint256 id = ids[i]; - uint256 amount = amounts[i]; - - uint256 balance = accountBalances[id]; - if (balance < amount) revert LBToken__BurnExceedsBalance(account, id, amount); - - unchecked { - _totalSupplies[id] -= amount; - accountBalances[id] -= amount; + uint256 balance = accountBalances[id]; + if (balance < amount) revert LBToken__BurnExceedsBalance(account, id, amount); - ++i; - } + unchecked { + _totalSupplies[id] -= amount; + accountBalances[id] -= amount; } - - emit TransferBatch(msg.sender, account, address(0), ids, amounts); } /** diff --git a/src/interfaces/ILBPair.sol b/src/interfaces/ILBPair.sol index 550e668a..00f89b17 100644 --- a/src/interfaces/ILBPair.sol +++ b/src/interfaces/ILBPair.sol @@ -28,6 +28,12 @@ interface ILBPair is ILBToken { error LBPair__ZeroShares(uint24 id); error LBPair__MaxTotalFeeExceeded(); + struct MintArrays { + uint256[] ids; + bytes32[] amounts; + uint256[] liquidityMinted; + } + event DepositedToBins(address indexed sender, address indexed to, uint256[] ids, bytes32[] amounts); event WithdrawnFromBins(address indexed sender, address indexed to, uint256[] ids, bytes32[] amounts); diff --git a/test/LBPairLiquidity.t.sol b/test/LBPairLiquidity.t.sol index f8c15d52..a35440bc 100644 --- a/test/LBPairLiquidity.t.sol +++ b/test/LBPairLiquidity.t.sol @@ -333,6 +333,8 @@ contract LBPairLiquidityTest is TestHelper { ids[0] = activeId; balances[0] = 1; + addLiquidity(DEV, DEV, pairWnative, activeId, 1e18, 1e18, 1, 1); + vm.expectRevert(abi.encodeWithSelector(ILBPair.LBPair__ZeroAmountsOut.selector, activeId)); pairWnative.burn(DEV, DEV, ids, balances); } diff --git a/test/LBToken.t.sol b/test/LBToken.t.sol index 76e82bdf..59944ab2 100644 --- a/test/LBToken.t.sol +++ b/test/LBToken.t.sol @@ -301,9 +301,6 @@ contract LBTokenTest is Test { function testFuzz_RevertOnInvalidLength(uint256[] memory ids, uint256[] memory amounts) external { vm.assume(ids.length != amounts.length && ids.length != 0); - vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__InvalidLength.selector)); - lbToken.mintBatch(address(1), ids, amounts); - vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__InvalidLength.selector)); vm.prank(address(1)); lbToken.batchTransferFrom(address(1), address(2), ids, amounts); @@ -361,10 +358,16 @@ contract LBTokenTest is Test { contract LBTokenCoverage is LBToken { function mintBatch(address to, uint256[] calldata ids, uint256[] calldata amounts) external { - _mintBatch(to, ids, amounts); + // _mintBatch(to, ids, amounts); + for (uint256 i = 0; i < ids.length; i++) { + _mint(to, ids[i], amounts[i]); + } } function batchBurnFrom(address from, uint256[] calldata ids, uint256[] calldata amounts) external { - _burnBatch(from, ids, amounts); + // _batchBurnFrom(from, ids, amounts); + for (uint256 i = 0; i < ids.length; i++) { + _burn(from, ids[i], amounts[i]); + } } } From 5f0c4a1bf0bed0686af22533dc22498d3b8048ea Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 01:14:28 +0100 Subject: [PATCH 34/47] issue 11: typographical errors --- src/LBPair.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/LBPair.sol b/src/LBPair.sol index 908b3693..73850b91 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -145,7 +145,6 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { * @notice Returns the bin step of the Liquidity Book Pair * @dev The bin step is the increase in price between two consecutive bins, in basis points. * For example, a bin step of 1 means that the price of the next bin is 0.01% higher than the price of the previous bin. - * The maximum bin step is 200, which means that the price of the next bin is 1% higher than the price of the previous bin. * @return binStep The bin step of the Liquidity Book Pair, in 10_000th */ function getBinStep() external pure override returns (uint16) { @@ -615,8 +614,8 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { * @dev Any excess amount of token will be sent to the `to` address. * @param to The address that will receive the LB tokens * @param liquidityConfigs The encoded liquidity configurations, each one containing the id of the bin and the - * @param refundTo The address that will receive the excess amount of tokens * percentage of token X and token Y to add to the bin. + * @param refundTo The address that will receive the excess amount of tokens * @return amountsReceived The amounts of token X and token Y received by the pool * @return amountsLeft The amounts of token X and token Y that were not added to the pool and were sent to `to` * @return liquidityMinted The amounts of LB tokens minted for each bin From 5d9c8a060062326a817a8cdc434b6cadcb952150 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 01:14:55 +0100 Subject: [PATCH 35/47] issue 13: the flashloan callback fail error will often not be thrown --- src/LBPair.sol | 17 +++++++++++++---- test/LBPairFlashloan.t.sol | 4 +--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/LBPair.sol b/src/LBPair.sol index 73850b91..e9e49a3e 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -578,10 +578,19 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { amounts.transfer(_tokenX(), _tokenY(), address(receiver)); - if ( - receiver.LBFlashLoanCallback(msg.sender, _tokenX(), _tokenY(), amounts, totalFees, data) - != Constants.CALLBACK_SUCCESS - ) { + (bool success, bytes memory rData) = address(receiver).call( + abi.encodeWithSelector( + ILBFlashLoanCallback.LBFlashLoanCallback.selector, + msg.sender, + _tokenX(), + _tokenY(), + amounts, + totalFees, + data + ) + ); + + if (!success || rData.length != 32 || abi.decode(rData, (bytes32)) != Constants.CALLBACK_SUCCESS) { revert LBPair__FlashLoanCallbackFailed(); } diff --git a/test/LBPairFlashloan.t.sol b/test/LBPairFlashloan.t.sol index f1cf951d..24645235 100644 --- a/test/LBPairFlashloan.t.sol +++ b/test/LBPairFlashloan.t.sol @@ -89,12 +89,10 @@ contract LBPairFlashloanTest is TestHelper { } function testFuzz_revert_FlashLoanReentrant(bytes32 callback) external { - vm.assume(callback != Constants.CALLBACK_SUCCESS); - bytes32 amountsBorrowed = bytes32(uint256(1)); bytes memory data = abi.encode(0, 0, callback, 1); - vm.expectRevert(ReentrancyGuard.ReentrancyGuard__ReentrantCall.selector); + vm.expectRevert(ILBPair.LBPair__FlashLoanCallbackFailed.selector); pairWnative.flashLoan(borrower, amountsBorrowed, data); } From a3a44ef9ddc4ab211e1e74e7c247fae1b2b24b02 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 01:23:31 +0100 Subject: [PATCH 36/47] issue 14: gas optimizations --- src/LBPair.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LBPair.sol b/src/LBPair.sol index e9e49a3e..8636482f 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -382,7 +382,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { uint128 amountOutOfBin = binReserves > amountOutLeft ? amountOutLeft : binReserves; - parameters = parameters.updateVolatilityParameters(id); + parameters = parameters.updateVolatilityAccumulator(id); uint128 amountInWithoutFee = uint128( swapForY @@ -485,10 +485,10 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { bytes32 protocolFees = _protocolFees; bytes32 amountsLeft = swapForY ? reserves.receivedX(_tokenX()) : reserves.receivedY(_tokenY()); - reserves = reserves.add(amountsLeft); - if (amountsLeft == 0) revert LBPair__InsufficientAmountIn(); + reserves = reserves.add(amountsLeft); + bytes32 parameters = _parameters; uint16 binStep = _binStep(); From bccd65114ccaaf1d88fb683485d23361f6f56022 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 01:23:50 +0100 Subject: [PATCH 37/47] issue 16: gas optimization --- src/LBToken.sol | 6 +++--- test/LBToken.t.sol | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/LBToken.sol b/src/LBToken.sol index afff607a..9c34f799 100644 --- a/src/LBToken.sol +++ b/src/LBToken.sol @@ -97,7 +97,7 @@ abstract contract LBToken is ILBToken { * @param ids The token ids. * @return batchBalances The balance for each (account, id) pair. */ - function balanceOfBatch(address[] memory accounts, uint256[] memory ids) + function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) public view virtual @@ -140,7 +140,7 @@ abstract contract LBToken is ILBToken { * @param ids The list of token ids. * @param amounts The list of amounts to transfer for each token id in `ids`. */ - function batchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory amounts) + function batchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts) public virtual override @@ -203,7 +203,7 @@ abstract contract LBToken is ILBToken { * @param ids The list of token ids. * @param amounts The list of amounts to transfer for each token id in `ids`. */ - function _batchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory amounts) + function _batchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts) internal checkLength(ids.length, amounts.length) notAddressZeroOrThis(to) diff --git a/test/LBToken.t.sol b/test/LBToken.t.sol index 59944ab2..53104e30 100644 --- a/test/LBToken.t.sol +++ b/test/LBToken.t.sol @@ -219,7 +219,8 @@ contract LBTokenTest is Test { function testFuzz_ApprovedForAll(address from, address to, uint256 id, uint256 amount) external { vm.assume( - from != address(0) && to != address(lbToken) && to != address(0) && to != address(lbToken) && from != to + from != address(lbToken) && from != address(0) && to != address(lbToken) && to != address(0) + && to != address(lbToken) && from != to ); uint256[] memory ids = new uint256[](1); From 9a1cd8722c672acc364664b39c049ef6a8fc7aad Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 01:29:53 +0100 Subject: [PATCH 38/47] issue 17: typographical errors --- src/LBToken.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LBToken.sol b/src/LBToken.sol index 9c34f799..82daf13b 100644 --- a/src/LBToken.sol +++ b/src/LBToken.sol @@ -9,7 +9,7 @@ import {ILBToken} from "./interfaces/ILBToken.sol"; * @author Trader Joe * @notice The LBToken is an implementation of a multi-token. * It allows to create multi-ERC20 represented by their ids. - * Its implementation is really similar to the ERC1155 standardn the main difference + * Its implementation is really similar to the ERC1155 standard the main difference * is that it doesn't do any call to the receiver contract to prevent reentrancy. * As it's only for ERC20s, the uri function is not implemented. * The contract is made for batch operations. @@ -115,10 +115,10 @@ abstract contract LBToken is ILBToken { } /** - * @notice Returns true if `spender` is approved to transfer `account`'s tokens. + * @notice Returns true if `spender` is approved to transfer `owner`'s tokens or if `spender` is the `owner`. * @param owner The address of the owner. * @param spender The address of the spender. - * @return True if `spender` is approved to transfer `account`'s tokens. + * @return True if `spender` is approved to transfer `owner`'s tokens. */ function isApprovedForAll(address owner, address spender) public view virtual override returns (bool) { return _isApprovedForAll(owner, spender); @@ -150,7 +150,7 @@ abstract contract LBToken is ILBToken { } /** - * @notice Returns true if `spender` is approved to transfer `owner`'s tokens or if `sender` is the `owner`. + * @notice Returns true if `spender` is approved to transfer `owner`'s tokens or if `spender` is the `owner`. * @param owner The address of the owner. * @param spender The address of the spender. * @return True if `spender` is approved to transfer `owner`'s tokens. From 5087897015f221a6b73e43dbddd64dd553be0e91 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 01:47:42 +0100 Subject: [PATCH 39/47] issue 20: typographical error --- src/libraries/math/PackedUint128Math.sol | 50 +++++---------------- test/libraries/math/PackedUint128Math.t.sol | 24 ---------- 2 files changed, 10 insertions(+), 64 deletions(-) diff --git a/src/libraries/math/PackedUint128Math.sol b/src/libraries/math/PackedUint128Math.sol index f8d35837..cb7da16c 100644 --- a/src/libraries/math/PackedUint128Math.sol +++ b/src/libraries/math/PackedUint128Math.sol @@ -93,13 +93,13 @@ library PackedUint128Math { /** * @dev Decodes a bytes32 into a uint128 as the first uint128 * @param z The encoded bytes32 as follows: - * [0 - 128[: x1 + * [0 - 128[: x * [128 - 256[: any - * @return x1 The first uint128 + * @return x The first uint128 */ - function decodeX(bytes32 z) internal pure returns (uint128 x1) { + function decodeX(bytes32 z) internal pure returns (uint128 x) { assembly { - x1 := and(z, MASK_128) + x := and(z, MASK_128) } } @@ -107,12 +107,12 @@ library PackedUint128Math { * @dev Decodes a bytes32 into a uint128 as the second uint128 * @param z The encoded bytes32 as follows: * [0 - 128[: any - * [128 - 256[: x2 - * @return x2 The second uint128 + * [128 - 256[: y + * @return y The second uint128 */ - function decodeY(bytes32 z) internal pure returns (uint128 x2) { + function decodeY(bytes32 z) internal pure returns (uint128 y) { assembly { - x2 := shr(OFFSET, z) + y := shr(OFFSET, z) } } @@ -207,7 +207,7 @@ library PackedUint128Math { } /** - * @dev Returns whether any of the uint128 of x is greater than the corresponding uint128 of y + * @dev Returns whether any of the uint128 of x is strictly greater than the corresponding uint128 of y * @param x The first bytes32 encoded as follows: * [0 - 128[: x1 * [128 - 256[: x2 @@ -224,7 +224,7 @@ library PackedUint128Math { } /** - * @dev Returns whether any of the uint128 of x is greater than or equal to the corresponding uint128 of y + * @dev Returns whether any of the uint128 of x is strictly greater than the corresponding uint128 of y * @param x The first bytes32 encoded as follows: * [0 - 128[: x1 * [128 - 256[: x2 @@ -240,36 +240,6 @@ library PackedUint128Math { return x1 > y1 || x2 > y2; } - /** - * @dev Multiplies an encoded bytes32 by a uint256 then shifts the result 128 bits to the right, rounding up - * @param x The bytes32 encoded as follows: - * [0 - 128[: x1 - * [128 - 256[: x2 - * @param multiplier The uint128 to multiply by - * @return z The product of x and multiplier encoded as follows: - * [0 - 128[: ceil((x1 * multiplier) / 2**128) - * [128 - 256[: ceil((x2 * multiplier) / 2**128) - */ - function scalarMulShiftRoundUp(bytes32 x, uint256 multiplier) internal pure returns (bytes32 z) { - if (multiplier == 0) return 0; - if (multiplier > MASK_128_PLUS_ONE) revert PackedUint128Math__MultiplierTooLarge(); - - (uint128 x1, uint128 x2) = decode(x); - - // Can't overflow because: - // ``` - // max(x{1,2} * multiplier) = type(uint128).max * type(uint128).max - // = type(uint256).max - (2**129 - 2) - // MASK_128 = 2**128 - 1 < 2**129 - 2 - // ``` - assembly { - x1 := shr(OFFSET, add(mul(x1, multiplier), MASK_128)) - x2 := shr(OFFSET, add(mul(x2, multiplier), MASK_128)) - } - - return encode(x1, x2); - } - /** * @dev Multiplies an encoded bytes32 by a uint128 then divides the result by 10_000, rounding down * The result can't overflow as the multiplier needs to be smaller or equal to 10_000 diff --git a/test/libraries/math/PackedUint128Math.t.sol b/test/libraries/math/PackedUint128Math.t.sol index 61a0baa0..74eb37c5 100644 --- a/test/libraries/math/PackedUint128Math.t.sol +++ b/test/libraries/math/PackedUint128Math.t.sol @@ -148,30 +148,6 @@ contract PackedUint128MathTest is Test { assertEq(x.gt(y), x1 > y1 || x2 > y2, "testFuzz_GreaterThan::1"); } - function testFuzz_ScalarMulShiftRoundUp(bytes32 x, uint128 multiplier) external { - (uint128 x1, uint128 x2) = x.decode(); - - uint256 y1 = uint256(x1) * multiplier; - uint256 y2 = uint256(x2) * multiplier; - - uint256 z1 = y1 == 0 ? 0 : ((y1 - 1) >> 128) + 1; - uint256 z2 = y2 == 0 ? 0 : ((y2 - 1) >> 128) + 1; - - assertLe(z1, type(uint128).max, "testFuzz_ScalarMulShiftRoundUp::1"); - assertLe(z2, type(uint128).max, "testFuzz_ScalarMulShiftRoundUp::2"); - - assertEq( - x.scalarMulShiftRoundUp(multiplier), uint128(z1).encode(uint128(z2)), "testFuzz_ScalarMulShiftRoundUp::3" - ); - } - - function testFuzz_revert_ScalarMulShiftRoundUp(bytes32 x, uint256 multiplier) external { - vm.assume(multiplier > uint256(type(uint128).max) + 1); - - vm.expectRevert(PackedUint128Math.PackedUint128Math__MultiplierTooLarge.selector); - x.scalarMulShiftRoundUp(multiplier); - } - function testFuzz_ScalarMulDivBasisPointRoundDown(bytes32 x, uint128 multipilier) external { (uint128 x1, uint128 x2) = x.decode(); From f76ab2649c970fa12aa1a4ee8e9673f1291c9cbf Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 01:49:51 +0100 Subject: [PATCH 40/47] issue 23: typographical error --- src/libraries/BinHelper.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index b108ca4d..f2e204c7 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -256,7 +256,7 @@ library BinHelper { } /** - * @dev Returns the encoded amounts that was transferred to the contract + * @dev Returns the encoded amounts that were transferred to the contract * @param reserves The reserves * @param tokenX The token X * @param tokenY The token Y @@ -269,7 +269,7 @@ library BinHelper { } /** - * @dev Returns the encoded amounts that was transferred to the contract, only for token X + * @dev Returns the encoded amounts that were transferred to the contract, only for token X * @param reserves The reserves * @param tokenX The token X * @return amounts The amounts, encoded as follows: @@ -282,7 +282,7 @@ library BinHelper { } /** - * @dev Returns the encoded amounts that was transferred to the contract, only for token Y + * @dev Returns the encoded amounts that were transferred to the contract, only for token Y * @param reserves The reserves * @param tokenY The token Y * @return amounts The amounts, encoded as follows: From ce1c5e8e4b25c89fa4ff28f4fe2e1e7729639c3f Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 01:55:12 +0100 Subject: [PATCH 41/47] issue 24: gas optimizations --- src/libraries/BinHelper.sol | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index f2e204c7..a42f3765 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -75,7 +75,9 @@ library BinHelper { pure returns (uint256 shares, bytes32 effectiveAmountsIn) { - uint256 userLiquidity = getLiquidity(amountsIn, price); + (uint256 x, uint256 y) = amountsIn.decode(); + + uint256 userLiquidity = getLiquidity(x, y, price); if (totalSupply == 0 || userLiquidity == 0) return (userLiquidity, amountsIn); uint256 binLiquidity = getLiquidity(binReserves, price); @@ -87,8 +89,6 @@ library BinHelper { if (userLiquidity > effectiveLiquidity) { uint256 deltaLiquidity = userLiquidity - effectiveLiquidity; - (uint256 x, uint256 y) = amountsIn.decode(); - // The other way might be more efficient, but as y is the quote asset, it is more valuable if (deltaLiquidity >= Constants.SCALE) { uint256 deltaY = deltaLiquidity >> Constants.SCALE_OFFSET; @@ -119,10 +119,21 @@ library BinHelper { */ function getLiquidity(bytes32 amounts, uint256 price) internal pure returns (uint256 liquidity) { (uint256 x, uint256 y) = amounts.decode(); + return getLiquidity(x, y, price); + } + + /** + * @dev Returns the amount of liquidity following the constant sum formula `L = price * x + y` + * @param x The amount of the token X + * @param y The amount of the token Y + * @param price The price of the bin + * @return liquidity The amount of liquidity + */ + function getLiquidity(uint256 x, uint256 y, uint256 price) internal pure returns (uint256 liquidity) { if (x > 0) { unchecked { liquidity = price * x; - if (x != 0 && liquidity / x != price) revert BinHelper__LiquidityOverflow(); + if (liquidity / x != price) revert BinHelper__LiquidityOverflow(); } } if (y > 0) { From bb05e9eefd73b25e670ee31ab9bd7d3d7153f9c6 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 01:57:06 +0100 Subject: [PATCH 42/47] issue 25: typographical error --- src/libraries/Clone.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Clone.sol b/src/libraries/Clone.sol index fd766539..446bd973 100644 --- a/src/libraries/Clone.sol +++ b/src/libraries/Clone.sol @@ -27,7 +27,7 @@ abstract contract Clone { mstore(arg, length) // Copy the array. calldatacopy(add(arg, 0x20), add(offset, argOffset), length) - // Allocate the memory, rounded up to the next 32 byte boudnary. + // Allocate the memory, rounded up to the next 32 byte boundary. mstore(0x40, and(add(add(arg, 0x3f), length), not(0x1f))) } } From 0e215bf3fb53edc137a03bb02ea4b13222753377 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 02:01:14 +0100 Subject: [PATCH 43/47] issue 26: typographical errors --- src/libraries/FeeHelper.sol | 32 ++++++++++++-------------------- test/libraries/FeeHelper.t.sol | 8 ++++---- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/libraries/FeeHelper.sol b/src/libraries/FeeHelper.sol index e624fc28..22a15a07 100644 --- a/src/libraries/FeeHelper.sol +++ b/src/libraries/FeeHelper.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.10; import {Constants} from "./Constants.sol"; -import {SafeCast} from "./math/SafeCast.sol"; /** * @title Liquidity Book Fee Helper Library @@ -11,26 +10,24 @@ import {SafeCast} from "./math/SafeCast.sol"; * @notice This library contains functions to calculate fees */ library FeeHelper { - using SafeCast for uint256; - - error FeeHelper__FeeOverflow(); - error FeeHelper__ProtocolShareOverflow(); + error FeeHelper__FeeTooLarge(); + error FeeHelper__ProtocolShareTooLarge(); /** - * @dev Modifier to check that the fee does not overflow + * @dev Modifier to check that the fee is not too large * @param fee The fee */ - modifier checkFeeOverflow(uint128 fee) { - if (fee > Constants.MAX_FEE) revert FeeHelper__FeeOverflow(); + modifier verifyFee(uint128 fee) { + if (fee > Constants.MAX_FEE) revert FeeHelper__FeeTooLarge(); _; } /** - * @dev Modifier to check that the protocol share does not overflow + * @dev Modifier to check that the protocol share is not too large * @param protocolShare The protocol share */ - modifier checkProtocolShareOverflow(uint128 protocolShare) { - if (protocolShare > Constants.MAX_PROTOCOL_SHARE) revert FeeHelper__ProtocolShareOverflow(); + modifier verifyProtocolShare(uint128 protocolShare) { + if (protocolShare > Constants.MAX_PROTOCOL_SHARE) revert FeeHelper__ProtocolShareTooLarge(); _; } @@ -43,7 +40,7 @@ library FeeHelper { function getFeeAmountFrom(uint128 amountWithFees, uint128 totalFee) internal pure - checkFeeOverflow(totalFee) + verifyFee(totalFee) returns (uint128) { unchecked { @@ -58,12 +55,7 @@ library FeeHelper { * @param totalFee The total fee * @return feeAmount The fee amount */ - function getFeeAmount(uint128 amount, uint128 totalFee) - internal - pure - checkFeeOverflow(totalFee) - returns (uint128) - { + function getFeeAmount(uint128 amount, uint128 totalFee) internal pure verifyFee(totalFee) returns (uint128) { unchecked { uint256 denominator = Constants.PRECISION - totalFee; // Can't overflow, max(result) = (type(uint128).max * 0.1e18 + (1e18 - 1)) / 0.9e18 < 2^128 @@ -80,7 +72,7 @@ library FeeHelper { function getCompositionFee(uint128 amountWithFees, uint128 totalFee) internal pure - checkFeeOverflow(totalFee) + verifyFee(totalFee) returns (uint128) { unchecked { @@ -99,7 +91,7 @@ library FeeHelper { function getProtocolFeeAmount(uint128 feeAmount, uint128 protocolShare) internal pure - checkProtocolShareOverflow(protocolShare) + verifyProtocolShare(protocolShare) returns (uint128) { unchecked { diff --git a/test/libraries/FeeHelper.t.sol b/test/libraries/FeeHelper.t.sol index 44682064..36473c9e 100644 --- a/test/libraries/FeeHelper.t.sol +++ b/test/libraries/FeeHelper.t.sol @@ -13,7 +13,7 @@ contract FeeHelperTest is Test { function testFuzz_GetFeeAmountFrom(uint128 amountWithFee, uint128 fee) external { if (fee > Constants.MAX_FEE) { - vm.expectRevert(FeeHelper.FeeHelper__FeeOverflow.selector); + vm.expectRevert(FeeHelper.FeeHelper__FeeTooLarge.selector); amountWithFee.getFeeAmountFrom(fee); } else { uint256 expectedFeeAmount = (uint256(amountWithFee) * fee + 1e18 - 1) / 1e18; @@ -25,7 +25,7 @@ contract FeeHelperTest is Test { function testFuzz_GetFeeAmount(uint128 amount, uint128 fee) external { if (fee > Constants.MAX_FEE) { - vm.expectRevert(FeeHelper.FeeHelper__FeeOverflow.selector); + vm.expectRevert(FeeHelper.FeeHelper__FeeTooLarge.selector); amount.getFeeAmount(fee); } else { uint128 denominator = 1e18 - fee; @@ -39,7 +39,7 @@ contract FeeHelperTest is Test { function testFuzz_GetCompositionFee(uint128 amountWithFee, uint128 fee) external { if (fee > Constants.MAX_FEE) { - vm.expectRevert(FeeHelper.FeeHelper__FeeOverflow.selector); + vm.expectRevert(FeeHelper.FeeHelper__FeeTooLarge.selector); amountWithFee.getCompositionFee(fee); } @@ -54,7 +54,7 @@ contract FeeHelperTest is Test { function testFuzz_GetProtocolFeeAmount(uint128 amount, uint128 fee) external { if (fee > Constants.MAX_PROTOCOL_SHARE) { - vm.expectRevert(FeeHelper.FeeHelper__ProtocolShareOverflow.selector); + vm.expectRevert(FeeHelper.FeeHelper__ProtocolShareTooLarge.selector); amount.getProtocolFeeAmount(fee); } else { uint256 expectedProtocolFeeAmount = (uint256(amount) * fee) / 1e4; From 72e293b403867061bcf3f308b1302061a51a198b Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 02:08:35 +0100 Subject: [PATCH 44/47] issue 32: typographical error --- src/libraries/PairParameterHelper.sol | 4 +--- test/libraries/PairParameterHelper.t.sol | 15 +++++---------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/libraries/PairParameterHelper.sol b/src/libraries/PairParameterHelper.sol index 52aab2c5..a4eec51e 100644 --- a/src/libraries/PairParameterHelper.sol +++ b/src/libraries/PairParameterHelper.sol @@ -47,8 +47,6 @@ library PairParameterHelper { uint256 internal constant MASK_STATIC_PARAMETER = 0xffffffffffffffffffffffffffff; - uint256 internal constant MAX_PROTOCOL_SHARE = 2_500; - /** * @dev Get the base factor from the encoded pair parameters * @param params The encoded pair parameters, as follows: @@ -330,7 +328,7 @@ library PairParameterHelper { ) internal pure returns (bytes32 newParams) { if ( filterPeriod > decayPeriod || decayPeriod > Encoded.MASK_UINT12 - || reductionFactor > Constants.BASIS_POINT_MAX || protocolShare > MAX_PROTOCOL_SHARE + || reductionFactor > Constants.BASIS_POINT_MAX || protocolShare > Constants.MAX_PROTOCOL_SHARE || maxVolatilityAccumulator > Encoded.MASK_UINT20 ) revert PairParametersHelper__InvalidParameter(); diff --git a/test/libraries/PairParameterHelper.t.sol b/test/libraries/PairParameterHelper.t.sol index b4feef43..ec067189 100644 --- a/test/libraries/PairParameterHelper.t.sol +++ b/test/libraries/PairParameterHelper.t.sol @@ -22,8 +22,7 @@ contract PairParameterHelperTest is Test { function testFuzz_StaticFeeParametersDirtyBits(bytes32 params, StaticFeeParameters memory sfp) external { vm.assume( sfp.filterPeriod <= sfp.decayPeriod && sfp.decayPeriod <= Encoded.MASK_UINT12 - && sfp.reductionFactor <= Constants.BASIS_POINT_MAX - && sfp.protocolShare <= PairParameterHelper.MAX_PROTOCOL_SHARE + && sfp.reductionFactor <= Constants.BASIS_POINT_MAX && sfp.protocolShare <= Constants.MAX_PROTOCOL_SHARE && sfp.maxVolatilityAccumulator <= Encoded.MASK_UINT20 ); @@ -61,8 +60,7 @@ contract PairParameterHelperTest is Test { function testFuzz_StaticFeeParameters(bytes32 params, StaticFeeParameters memory sfp) external { vm.assume( sfp.filterPeriod <= sfp.decayPeriod && sfp.decayPeriod <= Encoded.MASK_UINT12 - && sfp.reductionFactor <= Constants.BASIS_POINT_MAX - && sfp.protocolShare <= PairParameterHelper.MAX_PROTOCOL_SHARE + && sfp.reductionFactor <= Constants.BASIS_POINT_MAX && sfp.protocolShare <= Constants.MAX_PROTOCOL_SHARE && sfp.maxVolatilityAccumulator <= Encoded.MASK_UINT20 ); @@ -96,8 +94,7 @@ contract PairParameterHelperTest is Test { function testFuzz_revert_StaticFeeParameters(bytes32 params, StaticFeeParameters memory sfp) external { vm.assume( sfp.filterPeriod > sfp.decayPeriod || sfp.decayPeriod > Encoded.MASK_UINT12 - || sfp.reductionFactor > Constants.BASIS_POINT_MAX - || sfp.protocolShare > PairParameterHelper.MAX_PROTOCOL_SHARE + || sfp.reductionFactor > Constants.BASIS_POINT_MAX || sfp.protocolShare > Constants.MAX_PROTOCOL_SHARE || sfp.maxVolatilityAccumulator > Encoded.MASK_UINT20 ); @@ -269,8 +266,7 @@ contract PairParameterHelperTest is Test { { vm.assume( previousTime <= time && sfp.filterPeriod <= sfp.decayPeriod && sfp.decayPeriod <= Encoded.MASK_UINT12 - && sfp.reductionFactor <= Constants.BASIS_POINT_MAX - && sfp.protocolShare <= PairParameterHelper.MAX_PROTOCOL_SHARE + && sfp.reductionFactor <= Constants.BASIS_POINT_MAX && sfp.protocolShare <= Constants.MAX_PROTOCOL_SHARE && sfp.maxVolatilityAccumulator <= Encoded.MASK_UINT20 ); @@ -333,8 +329,7 @@ contract PairParameterHelperTest is Test { ) external { vm.assume( previousTime <= time && sfp.filterPeriod <= sfp.decayPeriod && sfp.decayPeriod <= Encoded.MASK_UINT12 - && sfp.reductionFactor <= Constants.BASIS_POINT_MAX - && sfp.protocolShare <= PairParameterHelper.MAX_PROTOCOL_SHARE + && sfp.reductionFactor <= Constants.BASIS_POINT_MAX && sfp.protocolShare <= Constants.MAX_PROTOCOL_SHARE && sfp.maxVolatilityAccumulator <= Encoded.MASK_UINT20 ); From 5867797f91056f02a6cb7557a62c49c1d5b5c00f Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 02:10:30 +0100 Subject: [PATCH 45/47] issue 33: gas optimizations --- src/libraries/PairParameterHelper.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libraries/PairParameterHelper.sol b/src/libraries/PairParameterHelper.sol index a4eec51e..9a5ae3d2 100644 --- a/src/libraries/PairParameterHelper.sol +++ b/src/libraries/PairParameterHelper.sol @@ -388,10 +388,12 @@ library PairParameterHelper { */ function updateVolatilityAccumulator(bytes32 params, uint24 activeId) internal pure returns (bytes32) { uint256 idReference = getIdReference(params); - uint256 deltaId = activeId > idReference ? activeId - idReference : idReference - activeId; + uint256 deltaId; uint256 volAcc; + unchecked { + deltaId = activeId > idReference ? activeId - idReference : idReference - activeId; volAcc = (uint256(getVolatilityReference(params)) + deltaId * Constants.BASIS_POINT_MAX); } From 8dcc5e01fb5a95b8855c89c1c83e4de4885f1bd7 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 22 Mar 2023 02:12:19 +0100 Subject: [PATCH 46/47] issue 34: typographical error --- src/libraries/PendingOwnable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/PendingOwnable.sol b/src/libraries/PendingOwnable.sol index 30e8ff43..3f4fead9 100644 --- a/src/libraries/PendingOwnable.sol +++ b/src/libraries/PendingOwnable.sol @@ -10,7 +10,7 @@ import {IPendingOwnable} from "../interfaces/IPendingOwnable.sol"; * @notice Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. The ownership of this contract is transferred using the - * push and pull pattern, the current owner set a `pendingOwner` using + * push and pull pattern, the current owner sets a `pendingOwner` using * {setPendingOwner} and that address can then call {becomeOwner} to become the * owner of that contract. The main logic and comments comes from OpenZeppelin's * Ownable contract. From adbe21c04726b3bb4b510ffca15c95d25348f886 Mon Sep 17 00:00:00 2001 From: Louis <107303182+0x0Louis@users.noreply.github.com> Date: Mon, 3 Apr 2023 00:25:26 +0200 Subject: [PATCH 47/47] Last audit fixes (#94) * fix invalid masks * gas optimisation * Missing natspec --- src/LBPair.sol | 1 + src/LBToken.sol | 8 ++++---- src/interfaces/ILBRouter.sol | 3 ++- src/libraries/PairParameterHelper.sol | 6 +++--- test/LBToken.t.sol | 14 -------------- 5 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/LBPair.sol b/src/LBPair.sol index 8636482f..517b334e 100644 --- a/src/LBPair.sol +++ b/src/LBPair.sol @@ -633,6 +633,7 @@ contract LBPair is LBToken, ReentrancyGuard, Clone, ILBPair { external override nonReentrant + notAddressZeroOrThis(to) returns (bytes32 amountsReceived, bytes32 amountsLeft, uint256[] memory liquidityMinted) { if (liquidityConfigs.length == 0) revert LBPair__EmptyMarketConfigs(); diff --git a/src/LBToken.sol b/src/LBToken.sol index 82daf13b..f56b78c8 100644 --- a/src/LBToken.sol +++ b/src/LBToken.sol @@ -167,7 +167,7 @@ abstract contract LBToken is ILBToken { * @param id The token id. * @param amount The amount to mint. */ - function _mint(address account, uint256 id, uint256 amount) internal notAddressZeroOrThis(account) { + function _mint(address account, uint256 id, uint256 amount) internal { _totalSupplies[id] += amount; unchecked { @@ -183,7 +183,7 @@ abstract contract LBToken is ILBToken { * @param id The token id. * @param amount The amount to burn. */ - function _burn(address account, uint256 id, uint256 amount) internal notAddressZeroOrThis(account) { + function _burn(address account, uint256 id, uint256 amount) internal { mapping(uint256 => uint256) storage accountBalances = _balances[account]; uint256 balance = accountBalances[id]; @@ -191,7 +191,7 @@ abstract contract LBToken is ILBToken { unchecked { _totalSupplies[id] -= amount; - accountBalances[id] -= amount; + accountBalances[id] = balance - amount; } } @@ -235,7 +235,7 @@ abstract contract LBToken is ILBToken { * @param spender The address of the spender * @param approved The boolean value to grant or revoke permission */ - function _approveForAll(address owner, address spender, bool approved) internal { + function _approveForAll(address owner, address spender, bool approved) internal notAddressZeroOrThis(owner) { if (owner == spender) revert LBToken__SelfApproval(owner); _spenderApprovals[owner][spender] = approved; diff --git a/src/interfaces/ILBRouter.sol b/src/interfaces/ILBRouter.sol index b421dd15..9497589c 100644 --- a/src/interfaces/ILBRouter.sol +++ b/src/interfaces/ILBRouter.sol @@ -70,7 +70,8 @@ interface ILBRouter { * - distributionX: The distribution of tokenX with sum(distributionX) = 100e18 (100%) or 0 (0%) * - distributionY: The distribution of tokenY with sum(distributionY) = 100e18 (100%) or 0 (0%) * - to: The address of the recipient - * - deadline: The deadline of the tx + * - refundTo: The address of the recipient of the refunded tokens if too much tokens are sent + * - deadline: The deadline of the transaction */ struct LiquidityParameters { IERC20 tokenX; diff --git a/src/libraries/PairParameterHelper.sol b/src/libraries/PairParameterHelper.sol index 9a5ae3d2..83436ce9 100644 --- a/src/libraries/PairParameterHelper.sol +++ b/src/libraries/PairParameterHelper.sol @@ -335,10 +335,10 @@ library PairParameterHelper { newParams = newParams.set(baseFactor, Encoded.MASK_UINT16, OFFSET_BASE_FACTOR); newParams = newParams.set(filterPeriod, Encoded.MASK_UINT12, OFFSET_FILTER_PERIOD); newParams = newParams.set(decayPeriod, Encoded.MASK_UINT12, OFFSET_DECAY_PERIOD); - newParams = newParams.set(reductionFactor, Encoded.MASK_UINT16, OFFSET_REDUCTION_FACTOR); + newParams = newParams.set(reductionFactor, Encoded.MASK_UINT14, OFFSET_REDUCTION_FACTOR); newParams = newParams.set(variableFeeControl, Encoded.MASK_UINT24, OFFSET_VAR_FEE_CONTROL); - newParams = newParams.set(protocolShare, Encoded.MASK_UINT16, OFFSET_PROTOCOL_SHARE); - newParams = newParams.set(maxVolatilityAccumulator, Encoded.MASK_UINT24, OFFSET_MAX_VOL_ACC); + newParams = newParams.set(protocolShare, Encoded.MASK_UINT14, OFFSET_PROTOCOL_SHARE); + newParams = newParams.set(maxVolatilityAccumulator, Encoded.MASK_UINT20, OFFSET_MAX_VOL_ACC); return params.set(uint256(newParams), MASK_STATIC_PARAMETER, 0); } diff --git a/test/LBToken.t.sol b/test/LBToken.t.sol index 53104e30..94b70104 100644 --- a/test/LBToken.t.sol +++ b/test/LBToken.t.sol @@ -265,27 +265,13 @@ contract LBTokenTest is Test { uint256[] memory ids = new uint256[](1); uint256[] memory amounts = new uint256[](1); - vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__AddressThisOrZero.selector)); - lbToken.mintBatch(address(0), ids, amounts); - vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__AddressThisOrZero.selector)); vm.prank(address(1)); lbToken.batchTransferFrom(address(1), address(0), ids, amounts); - vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__AddressThisOrZero.selector)); - vm.prank(address(1)); - lbToken.batchBurnFrom(address(0), ids, amounts); - - vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__AddressThisOrZero.selector)); - lbToken.mintBatch(address(lbToken), ids, amounts); - vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__AddressThisOrZero.selector)); vm.prank(address(1)); lbToken.batchTransferFrom(address(1), address(lbToken), ids, amounts); - - vm.expectRevert(abi.encodeWithSelector(ILBToken.LBToken__AddressThisOrZero.selector)); - vm.prank(address(1)); - lbToken.batchBurnFrom(address(lbToken), ids, amounts); } function testFuzz_SetApprovalOnSelf(address account) external {