├── .env.example ├── .eslintignore ├── .gitignore ├── .gitmodules ├── .prettierignore ├── .prettierrc ├── .solhint.json ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── foundry.toml ├── remappings.txt ├── script ├── config │ ├── bips-config.sol │ └── deployments.json └── deploy-core.s.sol ├── slither.config.json ├── src ├── LBBaseHooks.sol ├── LBFactory.sol ├── LBPair.sol ├── LBQuoter.sol ├── LBRouter.sol ├── LBToken.sol ├── interfaces │ ├── IJoeFactory.sol │ ├── IJoePair.sol │ ├── IJoeRouter01.sol │ ├── IJoeRouter02.sol │ ├── ILBFactory.sol │ ├── ILBFlashLoanCallback.sol │ ├── ILBHooks.sol │ ├── ILBLegacyFactory.sol │ ├── ILBLegacyPair.sol │ ├── ILBLegacyRouter.sol │ ├── ILBLegacyToken.sol │ ├── ILBPair.sol │ ├── ILBRouter.sol │ ├── ILBToken.sol │ └── IWNATIVE.sol └── libraries │ ├── BinHelper.sol │ ├── Clone.sol │ ├── Constants.sol │ ├── FeeHelper.sol │ ├── Hooks.sol │ ├── ImmutableClone.sol │ ├── JoeLibrary.sol │ ├── OracleHelper.sol │ ├── PairParameterHelper.sol │ ├── PriceHelper.sol │ ├── ReentrancyGuardUpgradeable.sol │ ├── TokenHelper.sol │ └── math │ ├── BitMath.sol │ ├── Encoded.sol │ ├── LiquidityConfigurations.sol │ ├── PackedUint128Math.sol │ ├── SafeCast.sol │ ├── SampleMath.sol │ ├── TreeMath.sol │ ├── Uint128x128Math.sol │ └── Uint256x256Math.sol └── test ├── Faucet.t.sol ├── GetOracleLength.t.sol ├── Hooks.t.sol ├── LBFactory.t.sol ├── LBPairFees.t.sol ├── LBPairFlashloan.t.sol ├── LBPairHooks.t.sol ├── LBPairImplementation.t.sol ├── LBPairInitialState.t.sol ├── LBPairLiquidity.t.sol ├── LBPairOracle.t.sol ├── LBPairSwap.t.sol ├── LBRouter.Liquidity.t.sol ├── LBRouter.Swap.t.sol ├── LBToken.t.sol ├── helpers ├── TestHelper.sol └── Utils.sol ├── integration ├── Addresses.sol ├── LBQuoter.t.sol ├── LBQuoterPriority.t.sol └── LBRouter.t.sol ├── libraries ├── BinHelper.t.sol ├── FeeHelper.t.sol ├── ImmutableClone.t.sol ├── OracleHelper.t.sol ├── PairParameterHelper.t.sol ├── PriceHelper.t.sol ├── ReentrancyGuardUpgradeable.t.sol ├── TokenHelper.t.sol └── math │ ├── BitMath.t.sol │ ├── Encoded.t.sol │ ├── LiquidityConfigurations.t.sol │ ├── PackedUint128Math.t.sol │ ├── SafeCast.t.sol │ ├── SampleMath.t.sol │ ├── TreeMath.t.sol │ ├── Uint128x128Math.t.sol │ └── Uint256x256Math.t.sol └── mocks ├── ERC20.sol ├── ERC20TransferTax.sol ├── Faucet.sol ├── FlashBorrower.sol ├── MockHooks.sol └── WNATIVE.sol /.env.example: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY="ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 2 | SNOWTRACE_API_KEY="" 3 | RPC_MAINNET_URL="https://api.avax.network/ext/bc/C/rpc" 4 | RPC_TESTNET_URL="https://api.avax-test.network/ext/bc/C/rpc" 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | cache 2 | lib 3 | out -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | cache 3 | out 4 | broadcast 5 | slither-results.json 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/openzeppelin-contracts"] 5 | path = lib/openzeppelin-contracts 6 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 7 | [submodule "lib/openzeppelin-contracts-upgradeable"] 8 | path = lib/openzeppelin-contracts-upgradeable 9 | url = https://github.com/openzeppelin/openzeppelin-contracts-upgradeable 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | cache 2 | lib 3 | out -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.sol", 5 | "options": { 6 | "printWidth":120, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "singleQuote": false, 10 | "bracketSpacing": false, 11 | "explicitTypes": "preserve" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": ["error", "^0.8.10"], 5 | "func-visibility": ["warn", { "ignoreConstructors": true }] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "solidity.formatter": "forge", 3 | "slither.solcPath": "", 4 | "slither.hiddenDetectors": [], 5 | "coverage-gutters.showLineCoverage": true 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Joemart Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Joe V2: Liquidity Book](https://github.com/traderjoe-xyz/joe-v2) 2 | 3 | This repository contains the Liquidity Book contracts, as well as tests and deploy scripts. 4 | 5 | - The [LBPair](./src/LBPair.sol) is the contract that contains all the logic of the actual pair for swaps, adds, removals of liquidity and fee claiming. This contract should never be deployed directly, and the factory should always be used for that matter. 6 | 7 | - The [LBToken](./src/LBToken.sol) is the contract that is used to calculate the shares of a user. The LBToken is a new token standard that is similar to ERC-1155, but without any callbacks (for safety reasons) and any functions or variables relating to ERC-721. 8 | 9 | - The [LBFactory](./src/LBFactory.sol) is the contract used to deploy the different pairs and acts as a registry for all the pairs already created. There are also privileged functions such as setting the parameters of the fees, the flashloan fee, setting the pair implementation, set if a pair should be ignored by the quoter and add new presets. Unless the `creationUnlocked` is `true`, only the owner of the factory can create pairs. 10 | 11 | - The [LBRouter](./src/LBRouter.sol) is the main contract that user will interact with as it adds security checks. Most users shouldn't interact directly with the pair. 12 | 13 | - The [LBQuoter](./src/LBQuoter.sol) is a contract that is used to return the best route of all those given. This should be used before a swap to get the best return on a swap. 14 | 15 | For more information, go to the [documentation](https://docs.traderjoexyz.com/) and the [whitepaper](https://github.com/traderjoe-xyz/LB-Whitepaper/blob/main/Joe%20v2%20Liquidity%20Book%20Whitepaper.pdf). 16 | 17 | ## Install foundry 18 | 19 | Foundry documentation can be found [here](https://book.getfoundry.sh/forge/index.html). 20 | 21 | ### On Linux and macOS 22 | 23 | Open your terminal and type in the following command: 24 | 25 | ``` 26 | curl -L https://foundry.paradigm.xyz | bash 27 | ``` 28 | 29 | This will download foundryup. Then install Foundry by running: 30 | 31 | ``` 32 | foundryup 33 | ``` 34 | 35 | To update foundry after installation, simply run `foundryup` again, and it will update to the latest Foundry release. 36 | You can also revert to a specific version of Foundry with `foundryup -v $VERSION`. 37 | 38 | ### On Windows 39 | 40 | If you use Windows, you need to build from source to get Foundry. 41 | 42 | Download and run `rustup-init` from [rustup.rs](https://rustup.rs/). It will start the installation in a console. 43 | 44 | After this, run the following to build Foundry from source: 45 | 46 | ``` 47 | cargo install --git https://github.com/foundry-rs/foundry foundry-cli anvil --bins --locked 48 | ``` 49 | 50 | To update from source, run the same command again. 51 | 52 | ## Install dependencies 53 | 54 | To install dependencies, run the following to install dependencies: 55 | 56 | ``` 57 | forge install 58 | ``` 59 | 60 | ___ 61 | 62 | ## Tests 63 | 64 | To run tests, run the following command: 65 | 66 | ``` 67 | forge test 68 | ``` 69 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | solc_version = "0.8.20" 6 | evm_version = 'paris' 7 | optimizer = true 8 | optimizer_runs = 800 9 | 10 | fs_permissions = [{ access = "read", path = "./"}] 11 | 12 | [fuzz] 13 | runs = 1024 14 | 15 | [rpc_endpoints] 16 | avalanche = "https://api.avax.network/ext/bc/C/rpc" 17 | fuji = "https://api.avax-test.network/ext/bc/C/rpc" 18 | 19 | [etherscan] 20 | arbitrum = { key = "${ARBISCAN_API_KEY}", chain = 42161 } 21 | avalanche = { key = "${SNOWTRACE_API_KEY}", chain = 43114 } 22 | arbitrum_goerli = { key = "${ARBISCAN_API_KEY}", chain = 421613 } 23 | fuji = { key = "${SNOWTRACE_API_KEY}", chain = 43113 } 24 | 25 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ 4 | @openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ -------------------------------------------------------------------------------- /script/config/bips-config.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | library BipsConfig { 6 | struct FactoryPreset { 7 | uint16 binStep; 8 | uint16 baseFactor; 9 | uint16 filterPeriod; 10 | uint16 decayPeriod; 11 | uint16 reductionFactor; 12 | uint24 variableFeeControl; 13 | uint16 protocolShare; 14 | uint24 maxVolatilityAccumulated; 15 | uint16 sampleLifetime; 16 | bool isOpen; 17 | } 18 | 19 | function getPreset(uint256 _bp) internal pure returns (FactoryPreset memory preset) { 20 | if (_bp == 1) { 21 | preset.binStep = 1; 22 | preset.baseFactor = 20_000; 23 | preset.filterPeriod = 10; 24 | preset.decayPeriod = 120; 25 | preset.reductionFactor = 5_000; 26 | preset.variableFeeControl = 2_000_000; 27 | preset.protocolShare = 0; 28 | preset.maxVolatilityAccumulated = 100_000; 29 | preset.sampleLifetime = 120; 30 | preset.isOpen = false; 31 | } else if (_bp == 2) { 32 | preset.binStep = 2; 33 | preset.baseFactor = 15_000; 34 | preset.filterPeriod = 10; 35 | preset.decayPeriod = 120; 36 | preset.reductionFactor = 5_000; 37 | preset.variableFeeControl = 500_000; 38 | preset.protocolShare = 0; 39 | preset.maxVolatilityAccumulated = 250_000; 40 | preset.sampleLifetime = 120; 41 | preset.isOpen = false; 42 | } else if (_bp == 5) { 43 | preset.binStep = 5; 44 | preset.baseFactor = 8_000; 45 | preset.filterPeriod = 30; 46 | preset.decayPeriod = 600; 47 | preset.reductionFactor = 5_000; 48 | preset.variableFeeControl = 120_000; 49 | preset.protocolShare = 0; 50 | preset.maxVolatilityAccumulated = 300_000; 51 | preset.sampleLifetime = 120; 52 | preset.isOpen = false; 53 | } else if (_bp == 10) { 54 | preset.binStep = 10; 55 | preset.baseFactor = 10_000; 56 | preset.filterPeriod = 30; 57 | preset.decayPeriod = 600; 58 | preset.reductionFactor = 5_000; 59 | preset.variableFeeControl = 40_000; 60 | preset.protocolShare = 0; 61 | preset.maxVolatilityAccumulated = 350_000; 62 | preset.sampleLifetime = 120; 63 | preset.isOpen = false; 64 | } else if (_bp == 15) { 65 | preset.binStep = 15; 66 | preset.baseFactor = 10_000; 67 | preset.filterPeriod = 30; 68 | preset.decayPeriod = 600; 69 | preset.reductionFactor = 5_000; 70 | preset.variableFeeControl = 30_000; 71 | preset.protocolShare = 0; 72 | preset.maxVolatilityAccumulated = 350_000; 73 | preset.sampleLifetime = 120; 74 | preset.isOpen = false; 75 | } else if (_bp == 20) { 76 | preset.binStep = 20; 77 | preset.baseFactor = 10_000; 78 | preset.filterPeriod = 30; 79 | preset.decayPeriod = 600; 80 | preset.reductionFactor = 5_000; 81 | preset.variableFeeControl = 20_000; 82 | preset.protocolShare = 0; 83 | preset.maxVolatilityAccumulated = 350_000; 84 | preset.sampleLifetime = 120; 85 | preset.isOpen = false; 86 | } else if (_bp == 25) { 87 | preset.binStep = 25; 88 | preset.baseFactor = 10_000; 89 | preset.filterPeriod = 30; 90 | preset.decayPeriod = 600; 91 | preset.reductionFactor = 5_000; 92 | preset.variableFeeControl = 15_000; 93 | preset.protocolShare = 0; 94 | preset.maxVolatilityAccumulated = 350_000; 95 | preset.sampleLifetime = 120; 96 | preset.isOpen = false; 97 | } 98 | } 99 | 100 | function getPresetList() internal pure returns (uint256[] memory presetList) { 101 | presetList = new uint256[](7); 102 | presetList[0] = 1; 103 | presetList[1] = 2; 104 | presetList[2] = 5; 105 | presetList[3] = 10; 106 | presetList[4] = 15; 107 | presetList[5] = 20; 108 | presetList[6] = 25; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /script/config/deployments.json: -------------------------------------------------------------------------------- 1 | { 2 | "avalanche_fuji": { 3 | "factory_v1": "0xF5c7d9733e5f53abCC1695820c4818C59B457C2C", 4 | "factory_v2": "0x6B8E020098cd1B3Ec9f811024bc24e51C660F768", 5 | "factory_v2_1": "0x8e42f2F4101563bF679975178e880FD87d3eFd4e", 6 | "multisig": "0xFFC08538077a0455E0F4077823b1A0E3e18Faf0b", 7 | "router_v1": "0xd7f655E3376cE2D7A2b08fF01Eb3B1023191A901", 8 | "router_v2": "0x7b50046cEC8252ca835b148b1eDD997319120a12", 9 | "router_v2_1": "0xb4315e873dBcf96Ffd0acd8EA43f689D8c20fB30", 10 | "w_native": "0xd00ae08403B9bbb9124bB305C09058E32C39A48c" 11 | }, 12 | "arbitrum_one_goerli": { 13 | "factory_v1": "0x1886D09C9Ade0c5DB822D85D21678Db67B6c2982", 14 | "factory_v2": "0xC8Af41e49e2C03eA14706C7aa9cEE60454bc5c03", 15 | "factory_v2_1": "0x0000000000000000000000000000000000000000", 16 | "multisig": "0xbeE5c10Cf6E4F68f831E11C1D9E59B43560B3642", 17 | "router_v1": "0x454206AD825cAfaE03c9581014AF6b74f7D53713", 18 | "router_v2": "0x6E9603f925FB5A74f7321f51499d9633c1252893", 19 | "router_v2_1": "0x0000000000000000000000000000000000000000", 20 | "w_native": "0xaE4EC9901c3076D0DdBe76A520F9E90a6227aCB7" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /script/deploy-core.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "forge-std/Script.sol"; 6 | 7 | import {ILBFactory, LBFactory} from "src/LBFactory.sol"; 8 | import {ILBRouter, IJoeFactory, ILBLegacyFactory, ILBLegacyRouter, IWNATIVE, LBRouter} from "src/LBRouter.sol"; 9 | import {IERC20, LBPair} from "src/LBPair.sol"; 10 | import {LBQuoter} from "src/LBQuoter.sol"; 11 | 12 | import {BipsConfig} from "./config/bips-config.sol"; 13 | 14 | contract CoreDeployer is Script { 15 | using stdJson for string; 16 | 17 | uint256 private constant FLASHLOAN_FEE = 5e12; 18 | 19 | struct Deployment { 20 | address factoryV1; 21 | address factoryV2; 22 | address factoryV2_1; 23 | address multisig; 24 | address routerV1; 25 | address routerV2; 26 | address routerV2_1; 27 | address wNative; 28 | } 29 | 30 | string[] chains = ["avalanche_fuji", "arbitrum_one_goerli"]; 31 | 32 | function setUp() public { 33 | _overwriteDefaultArbitrumRPC(); 34 | } 35 | 36 | function run() public { 37 | string memory json = vm.readFile("script/config/deployments.json"); 38 | address deployer = vm.rememberKey(vm.envUint("DEPLOYER_PRIVATE_KEY")); 39 | 40 | console.log("Deployer address: %s", deployer); 41 | 42 | for (uint256 i = 0; i < chains.length; i++) { 43 | bytes memory rawDeploymentData = json.parseRaw(string(abi.encodePacked(".", chains[i]))); 44 | Deployment memory deployment = abi.decode(rawDeploymentData, (Deployment)); 45 | 46 | console.log("\nDeploying V2.1 on %s", chains[i]); 47 | 48 | vm.createSelectFork(StdChains.getChain(chains[i]).rpcUrl); 49 | 50 | vm.broadcast(deployer); 51 | LBFactory factoryV2_2 = new LBFactory(deployer, deployer, FLASHLOAN_FEE); 52 | console.log("LBFactory deployed -->", address(factoryV2_2)); 53 | 54 | vm.broadcast(deployer); 55 | LBPair pairImplementation = new LBPair(factoryV2_2); 56 | console.log("LBPair implementation deployed -->", address(pairImplementation)); 57 | 58 | vm.broadcast(deployer); 59 | LBRouter routerV2_2 = new LBRouter( 60 | factoryV2_2, 61 | IJoeFactory(deployment.factoryV1), 62 | ILBLegacyFactory(deployment.factoryV2), 63 | ILBLegacyRouter(deployment.routerV2), 64 | ILBFactory(deployment.factoryV2_1), 65 | IWNATIVE(deployment.wNative) 66 | ); 67 | console.log("LBRouter deployed -->", address(routerV2_2)); 68 | 69 | vm.startBroadcast(deployer); 70 | LBQuoter quoter = new LBQuoter( 71 | deployment.factoryV1, 72 | deployment.factoryV2, 73 | deployment.factoryV2_1, 74 | address(factoryV2_2), 75 | deployment.routerV2, 76 | deployment.routerV2_1, 77 | address(routerV2_2) 78 | ); 79 | console.log("LBQuoter deployed -->", address(quoter)); 80 | 81 | factoryV2_2.setLBPairImplementation(address(pairImplementation)); 82 | console.log("LBPair implementation set on factoryV2_2\n"); 83 | 84 | uint256 quoteAssets = ILBLegacyFactory(deployment.factoryV2).getNumberOfQuoteAssets(); 85 | for (uint256 j = 0; j < quoteAssets; j++) { 86 | IERC20 quoteAsset = ILBLegacyFactory(deployment.factoryV2).getQuoteAsset(j); 87 | factoryV2_2.addQuoteAsset(quoteAsset); 88 | console.log("Quote asset whitelisted -->", address(quoteAsset)); 89 | } 90 | 91 | uint256[] memory presetList = BipsConfig.getPresetList(); 92 | for (uint256 j; j < presetList.length; j++) { 93 | BipsConfig.FactoryPreset memory preset = BipsConfig.getPreset(presetList[j]); 94 | factoryV2_2.setPreset( 95 | preset.binStep, 96 | preset.baseFactor, 97 | preset.filterPeriod, 98 | preset.decayPeriod, 99 | preset.reductionFactor, 100 | preset.variableFeeControl, 101 | preset.protocolShare, 102 | preset.maxVolatilityAccumulated, 103 | preset.isOpen 104 | ); 105 | } 106 | 107 | factoryV2_2.transferOwnership(deployment.multisig); 108 | vm.stopBroadcast(); 109 | } 110 | } 111 | 112 | function _overwriteDefaultArbitrumRPC() private { 113 | StdChains.setChain( 114 | "arbitrum_one_goerli", 115 | StdChains.ChainData({ 116 | name: "Arbitrum One Goerli", 117 | chainId: 421613, 118 | rpcUrl: vm.envString("ARBITRUM_TESTNET_RPC_URL") 119 | }) 120 | ); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /slither.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "filter_paths": "(lib/|test/|script/)", 3 | "solc_remaps": [ 4 | "ds-test/=lib/ds-test/src/", 5 | "forge-std/=lib/forge-std/src/", 6 | "openzeppelin/=lib/openzeppelin-contracts/contracts/" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/interfaces/IJoeFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | /// @title Joe V1 Factory Interface 6 | /// @notice Interface to interact with Joe V1 Factory 7 | interface IJoeFactory { 8 | event PairCreated(address indexed token0, address indexed token1, address pair, uint256); 9 | 10 | function feeTo() external view returns (address); 11 | 12 | function feeToSetter() external view returns (address); 13 | 14 | function migrator() external view returns (address); 15 | 16 | function getPair(address tokenA, address tokenB) external view returns (address pair); 17 | 18 | function allPairs(uint256) external view returns (address pair); 19 | 20 | function allPairsLength() external view returns (uint256); 21 | 22 | function createPair(address tokenA, address tokenB) external returns (address pair); 23 | 24 | function setFeeTo(address) external; 25 | 26 | function setFeeToSetter(address) external; 27 | 28 | function setMigrator(address) external; 29 | } 30 | -------------------------------------------------------------------------------- /src/interfaces/IJoePair.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | /// @title Joe V1 Pair Interface 6 | /// @notice Interface to interact with Joe V1 Pairs 7 | interface IJoePair { 8 | event Approval(address indexed owner, address indexed spender, uint256 value); 9 | event Transfer(address indexed from, address indexed to, uint256 value); 10 | 11 | function name() external pure returns (string memory); 12 | 13 | function symbol() external pure returns (string memory); 14 | 15 | function decimals() external pure returns (uint8); 16 | 17 | function totalSupply() external view returns (uint256); 18 | 19 | function balanceOf(address owner) external view returns (uint256); 20 | 21 | function allowance(address owner, address spender) external view returns (uint256); 22 | 23 | function approve(address spender, uint256 value) external returns (bool); 24 | 25 | function transfer(address to, uint256 value) external returns (bool); 26 | 27 | function transferFrom(address from, address to, uint256 value) external returns (bool); 28 | 29 | function DOMAIN_SEPARATOR() external view returns (bytes32); 30 | 31 | function PERMIT_TYPEHASH() external pure returns (bytes32); 32 | 33 | function nonces(address owner) external view returns (uint256); 34 | 35 | function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) 36 | external; 37 | 38 | event Mint(address indexed sender, uint256 amount0, uint256 amount1); 39 | event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); 40 | event Swap( 41 | address indexed sender, 42 | uint256 amount0In, 43 | uint256 amount1In, 44 | uint256 amount0Out, 45 | uint256 amount1Out, 46 | address indexed to 47 | ); 48 | event Sync(uint112 reserve0, uint112 reserve1); 49 | 50 | function MINIMUM_LIQUIDITY() external pure returns (uint256); 51 | 52 | function factory() external view returns (address); 53 | 54 | function token0() external view returns (address); 55 | 56 | function token1() external view returns (address); 57 | 58 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 59 | 60 | function price0CumulativeLast() external view returns (uint256); 61 | 62 | function price1CumulativeLast() external view returns (uint256); 63 | 64 | function kLast() external view returns (uint256); 65 | 66 | function mint(address to) external returns (uint256 liquidity); 67 | 68 | function burn(address to) external returns (uint256 amount0, uint256 amount1); 69 | 70 | function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external; 71 | 72 | function skim(address to) external; 73 | 74 | function sync() external; 75 | 76 | function initialize(address, address) external; 77 | } 78 | -------------------------------------------------------------------------------- /src/interfaces/IJoeRouter01.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | /// @title Joe V1 Router01 Interface 6 | /// @notice Interface to interact with Joe V1 Router 7 | interface IJoeRouter01 { 8 | function factory() external pure returns (address); 9 | 10 | function WAVAX() external pure returns (address); 11 | 12 | function addLiquidity( 13 | address tokenA, 14 | address tokenB, 15 | uint256 amountADesired, 16 | uint256 amountBDesired, 17 | uint256 amountAMin, 18 | uint256 amountBMin, 19 | address to, 20 | uint256 deadline 21 | ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity); 22 | 23 | function addLiquidityAVAX( 24 | address token, 25 | uint256 amountTokenDesired, 26 | uint256 amountTokenMin, 27 | uint256 amountAVAXMin, 28 | address to, 29 | uint256 deadline 30 | ) external payable returns (uint256 amountToken, uint256 amountAVAX, uint256 liquidity); 31 | 32 | function removeLiquidity( 33 | address tokenA, 34 | address tokenB, 35 | uint256 liquidity, 36 | uint256 amountAMin, 37 | uint256 amountBMin, 38 | address to, 39 | uint256 deadline 40 | ) external returns (uint256 amountA, uint256 amountB); 41 | 42 | function removeLiquidityAVAX( 43 | address token, 44 | uint256 liquidity, 45 | uint256 amountTokenMin, 46 | uint256 amountAVAXMin, 47 | address to, 48 | uint256 deadline 49 | ) external returns (uint256 amountToken, uint256 amountAVAX); 50 | 51 | function removeLiquidityWithPermit( 52 | address tokenA, 53 | address tokenB, 54 | uint256 liquidity, 55 | uint256 amountAMin, 56 | uint256 amountBMin, 57 | address to, 58 | uint256 deadline, 59 | bool approveMax, 60 | uint8 v, 61 | bytes32 r, 62 | bytes32 s 63 | ) external returns (uint256 amountA, uint256 amountB); 64 | 65 | function removeLiquidityAVAXWithPermit( 66 | address token, 67 | uint256 liquidity, 68 | uint256 amountTokenMin, 69 | uint256 amountAVAXMin, 70 | address to, 71 | uint256 deadline, 72 | bool approveMax, 73 | uint8 v, 74 | bytes32 r, 75 | bytes32 s 76 | ) external returns (uint256 amountToken, uint256 amountAVAX); 77 | 78 | function swapExactTokensForTokens( 79 | uint256 amountIn, 80 | uint256 amountOutMin, 81 | address[] calldata path, 82 | address to, 83 | uint256 deadline 84 | ) external returns (uint256[] memory amounts); 85 | 86 | function swapTokensForExactTokens( 87 | uint256 amountOut, 88 | uint256 amountInMax, 89 | address[] calldata path, 90 | address to, 91 | uint256 deadline 92 | ) external returns (uint256[] memory amounts); 93 | 94 | function swapExactAVAXForTokens(uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) 95 | external 96 | payable 97 | returns (uint256[] memory amounts); 98 | 99 | function swapTokensForExactAVAX( 100 | uint256 amountOut, 101 | uint256 amountInMax, 102 | address[] calldata path, 103 | address to, 104 | uint256 deadline 105 | ) external returns (uint256[] memory amounts); 106 | 107 | function swapExactTokensForAVAX( 108 | uint256 amountIn, 109 | uint256 amountOutMin, 110 | address[] calldata path, 111 | address to, 112 | uint256 deadline 113 | ) external returns (uint256[] memory amounts); 114 | 115 | function swapAVAXForExactTokens(uint256 amountOut, address[] calldata path, address to, uint256 deadline) 116 | external 117 | payable 118 | returns (uint256[] memory amounts); 119 | 120 | function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) external pure returns (uint256 amountB); 121 | 122 | function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) 123 | external 124 | pure 125 | returns (uint256 amountOut); 126 | 127 | function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) 128 | external 129 | pure 130 | returns (uint256 amountIn); 131 | 132 | function getAmountsOut(uint256 amountIn, address[] calldata path) 133 | external 134 | view 135 | returns (uint256[] memory amounts); 136 | 137 | function getAmountsIn(uint256 amountOut, address[] calldata path) 138 | external 139 | view 140 | returns (uint256[] memory amounts); 141 | } 142 | -------------------------------------------------------------------------------- /src/interfaces/IJoeRouter02.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import {IJoeRouter01} from "./IJoeRouter01.sol"; 6 | 7 | /// @title Joe V1 Router Interface 8 | /// @notice Interface to interact with Joe V1 Router 9 | interface IJoeRouter02 is IJoeRouter01 { 10 | function removeLiquidityAVAXSupportingFeeOnTransferTokens( 11 | address token, 12 | uint256 liquidity, 13 | uint256 amountTokenMin, 14 | uint256 amountAVAXMin, 15 | address to, 16 | uint256 deadline 17 | ) external returns (uint256 amountAVAX); 18 | 19 | function removeLiquidityAVAXWithPermitSupportingFeeOnTransferTokens( 20 | address token, 21 | uint256 liquidity, 22 | uint256 amountTokenMin, 23 | uint256 amountAVAXMin, 24 | address to, 25 | uint256 deadline, 26 | bool approveMax, 27 | uint8 v, 28 | bytes32 r, 29 | bytes32 s 30 | ) external returns (uint256 amountAVAX); 31 | 32 | function swapExactTokensForTokensSupportingFeeOnTransferTokens( 33 | uint256 amountIn, 34 | uint256 amountOutMin, 35 | address[] calldata path, 36 | address to, 37 | uint256 deadline 38 | ) external; 39 | 40 | function swapExactAVAXForTokensSupportingFeeOnTransferTokens( 41 | uint256 amountOutMin, 42 | address[] calldata path, 43 | address to, 44 | uint256 deadline 45 | ) external payable; 46 | 47 | function swapExactTokensForAVAXSupportingFeeOnTransferTokens( 48 | uint256 amountIn, 49 | uint256 amountOutMin, 50 | address[] calldata path, 51 | address to, 52 | uint256 deadline 53 | ) external; 54 | } 55 | -------------------------------------------------------------------------------- /src/interfaces/ILBFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | import {ILBHooks} from "./ILBHooks.sol"; 8 | import {ILBPair} from "./ILBPair.sol"; 9 | 10 | /** 11 | * @title Liquidity Book Factory Interface 12 | * @author Trader Joe 13 | * @notice Required interface of LBFactory contract 14 | */ 15 | interface ILBFactory { 16 | error LBFactory__IdenticalAddresses(IERC20 token); 17 | error LBFactory__QuoteAssetNotWhitelisted(IERC20 quoteAsset); 18 | error LBFactory__QuoteAssetAlreadyWhitelisted(IERC20 quoteAsset); 19 | error LBFactory__AddressZero(); 20 | error LBFactory__LBPairAlreadyExists(IERC20 tokenX, IERC20 tokenY, uint256 _binStep); 21 | error LBFactory__LBPairDoesNotExist(IERC20 tokenX, IERC20 tokenY, uint256 binStep); 22 | error LBFactory__LBPairNotCreated(IERC20 tokenX, IERC20 tokenY, uint256 binStep); 23 | error LBFactory__FlashLoanFeeAboveMax(uint256 fees, uint256 maxFees); 24 | error LBFactory__BinStepTooLow(uint256 binStep); 25 | error LBFactory__PresetIsLockedForUsers(address user, uint256 binStep); 26 | error LBFactory__LBPairIgnoredIsAlreadyInTheSameState(); 27 | error LBFactory__BinStepHasNoPreset(uint256 binStep); 28 | error LBFactory__PresetOpenStateIsAlreadyInTheSameState(); 29 | error LBFactory__SameFeeRecipient(address feeRecipient); 30 | error LBFactory__SameFlashLoanFee(uint256 flashLoanFee); 31 | error LBFactory__LBPairSafetyCheckFailed(address LBPairImplementation); 32 | error LBFactory__SameImplementation(address LBPairImplementation); 33 | error LBFactory__ImplementationNotSet(); 34 | error LBFactory__SameHooksImplementation(address hooksImplementation); 35 | error LBFactory__SameHooksParameters(bytes32 hooksParameters); 36 | error LBFactory__InvalidHooksParameters(); 37 | error LBFactory__CannotGrantDefaultAdminRole(); 38 | 39 | /** 40 | * @dev Structure to store the LBPair information, such as: 41 | * binStep: The bin step of the LBPair 42 | * LBPair: The address of the LBPair 43 | * createdByOwner: Whether the pair was created by the owner of the factory 44 | * ignoredForRouting: Whether the pair is ignored for routing or not. An ignored pair will not be explored during routes finding 45 | */ 46 | struct LBPairInformation { 47 | uint16 binStep; 48 | ILBPair LBPair; 49 | bool createdByOwner; 50 | bool ignoredForRouting; 51 | } 52 | 53 | event LBPairCreated( 54 | IERC20 indexed tokenX, IERC20 indexed tokenY, uint256 indexed binStep, ILBPair LBPair, uint256 pid 55 | ); 56 | 57 | event FeeRecipientSet(address oldRecipient, address newRecipient); 58 | 59 | event FlashLoanFeeSet(uint256 oldFlashLoanFee, uint256 newFlashLoanFee); 60 | 61 | event LBPairImplementationSet(address oldLBPairImplementation, address LBPairImplementation); 62 | 63 | event LBPairIgnoredStateChanged(ILBPair indexed LBPair, bool ignored); 64 | 65 | event PresetSet( 66 | uint256 indexed binStep, 67 | uint256 baseFactor, 68 | uint256 filterPeriod, 69 | uint256 decayPeriod, 70 | uint256 reductionFactor, 71 | uint256 variableFeeControl, 72 | uint256 protocolShare, 73 | uint256 maxVolatilityAccumulator 74 | ); 75 | 76 | event PresetOpenStateChanged(uint256 indexed binStep, bool indexed isOpen); 77 | 78 | event PresetRemoved(uint256 indexed binStep); 79 | 80 | event QuoteAssetAdded(IERC20 indexed quoteAsset); 81 | 82 | event QuoteAssetRemoved(IERC20 indexed quoteAsset); 83 | 84 | function getMinBinStep() external pure returns (uint256); 85 | 86 | function getFeeRecipient() external view returns (address); 87 | 88 | function getMaxFlashLoanFee() external pure returns (uint256); 89 | 90 | function getFlashLoanFee() external view returns (uint256); 91 | 92 | function getLBPairImplementation() external view returns (address); 93 | 94 | function getNumberOfLBPairs() external view returns (uint256); 95 | 96 | function getLBPairAtIndex(uint256 id) external returns (ILBPair); 97 | 98 | function getNumberOfQuoteAssets() external view returns (uint256); 99 | 100 | function getQuoteAssetAtIndex(uint256 index) external view returns (IERC20); 101 | 102 | function isQuoteAsset(IERC20 token) external view returns (bool); 103 | 104 | function getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep) 105 | external 106 | view 107 | returns (LBPairInformation memory); 108 | 109 | function getPreset(uint256 binStep) 110 | external 111 | view 112 | returns ( 113 | uint256 baseFactor, 114 | uint256 filterPeriod, 115 | uint256 decayPeriod, 116 | uint256 reductionFactor, 117 | uint256 variableFeeControl, 118 | uint256 protocolShare, 119 | uint256 maxAccumulator, 120 | bool isOpen 121 | ); 122 | 123 | function getAllBinSteps() external view returns (uint256[] memory presetsBinStep); 124 | 125 | function getOpenBinSteps() external view returns (uint256[] memory openBinStep); 126 | 127 | function getAllLBPairs(IERC20 tokenX, IERC20 tokenY) 128 | external 129 | view 130 | returns (LBPairInformation[] memory LBPairsBinStep); 131 | 132 | function setLBPairImplementation(address lbPairImplementation) external; 133 | 134 | function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) 135 | external 136 | returns (ILBPair pair); 137 | 138 | function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint16 binStep, bool ignored) external; 139 | 140 | function setPreset( 141 | uint16 binStep, 142 | uint16 baseFactor, 143 | uint16 filterPeriod, 144 | uint16 decayPeriod, 145 | uint16 reductionFactor, 146 | uint24 variableFeeControl, 147 | uint16 protocolShare, 148 | uint24 maxVolatilityAccumulator, 149 | bool isOpen 150 | ) external; 151 | 152 | function setPresetOpenState(uint16 binStep, bool isOpen) external; 153 | 154 | function removePreset(uint16 binStep) external; 155 | 156 | function setFeesParametersOnPair( 157 | IERC20 tokenX, 158 | IERC20 tokenY, 159 | uint16 binStep, 160 | uint16 baseFactor, 161 | uint16 filterPeriod, 162 | uint16 decayPeriod, 163 | uint16 reductionFactor, 164 | uint24 variableFeeControl, 165 | uint16 protocolShare, 166 | uint24 maxVolatilityAccumulator 167 | ) external; 168 | 169 | function setLBHooksParametersOnPair( 170 | IERC20 tokenX, 171 | IERC20 tokenY, 172 | uint16 binStep, 173 | bytes32 hooksParameters, 174 | bytes memory onHooksSetData 175 | ) external; 176 | 177 | function removeLBHooksOnPair(IERC20 tokenX, IERC20 tokenY, uint16 binStep) external; 178 | 179 | function setFeeRecipient(address feeRecipient) external; 180 | 181 | function setFlashLoanFee(uint256 flashLoanFee) external; 182 | 183 | function addQuoteAsset(IERC20 quoteAsset) external; 184 | 185 | function removeQuoteAsset(IERC20 quoteAsset) external; 186 | 187 | function forceDecay(ILBPair lbPair) external; 188 | } 189 | -------------------------------------------------------------------------------- /src/interfaces/ILBFlashLoanCallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | /// @title Liquidity Book Flashloan Callback Interface 8 | /// @author Trader Joe 9 | /// @notice Required interface to interact with LB flash loans 10 | interface ILBFlashLoanCallback { 11 | function LBFlashLoanCallback( 12 | address sender, 13 | IERC20 tokenX, 14 | IERC20 tokenY, 15 | bytes32 amounts, 16 | bytes32 totalFees, 17 | bytes calldata data 18 | ) external returns (bytes32); 19 | } 20 | -------------------------------------------------------------------------------- /src/interfaces/ILBHooks.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.10; 3 | 4 | import {ILBPair} from "./ILBPair.sol"; 5 | 6 | import {Hooks} from "../libraries/Hooks.sol"; 7 | 8 | interface ILBHooks { 9 | function getLBPair() external view returns (ILBPair); 10 | 11 | function isLinked() external view returns (bool); 12 | 13 | function onHooksSet(bytes32 hooksParameters, bytes calldata onHooksSetData) external returns (bytes4); 14 | 15 | function beforeSwap(address sender, address to, bool swapForY, bytes32 amountsIn) external returns (bytes4); 16 | 17 | function afterSwap(address sender, address to, bool swapForY, bytes32 amountsOut) external returns (bytes4); 18 | 19 | function beforeFlashLoan(address sender, address to, bytes32 amounts) external returns (bytes4); 20 | 21 | function afterFlashLoan(address sender, address to, bytes32 fees, bytes32 feesReceived) external returns (bytes4); 22 | 23 | function beforeMint(address sender, address to, bytes32[] calldata liquidityConfigs, bytes32 amountsReceived) 24 | external 25 | returns (bytes4); 26 | 27 | function afterMint(address sender, address to, bytes32[] calldata liquidityConfigs, bytes32 amountsIn) 28 | external 29 | returns (bytes4); 30 | 31 | function beforeBurn( 32 | address sender, 33 | address from, 34 | address to, 35 | uint256[] calldata ids, 36 | uint256[] calldata amountsToBurn 37 | ) external returns (bytes4); 38 | 39 | function afterBurn( 40 | address sender, 41 | address from, 42 | address to, 43 | uint256[] calldata ids, 44 | uint256[] calldata amountsToBurn 45 | ) external returns (bytes4); 46 | 47 | function beforeBatchTransferFrom( 48 | address sender, 49 | address from, 50 | address to, 51 | uint256[] calldata ids, 52 | uint256[] calldata amounts 53 | ) external returns (bytes4); 54 | 55 | function afterBatchTransferFrom( 56 | address sender, 57 | address from, 58 | address to, 59 | uint256[] calldata ids, 60 | uint256[] calldata amounts 61 | ) external returns (bytes4); 62 | } 63 | -------------------------------------------------------------------------------- /src/interfaces/ILBLegacyFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | import {ILBLegacyPair} from "./ILBLegacyPair.sol"; 8 | 9 | /// @title Liquidity Book Factory Interface 10 | /// @author Trader Joe 11 | /// @notice Required interface of LBFactory contract 12 | interface ILBLegacyFactory { 13 | /// @dev Structure to store the LBPair information, such as: 14 | /// - binStep: The bin step of the LBPair 15 | /// - LBPair: The address of the LBPair 16 | /// - createdByOwner: Whether the pair was created by the owner of the factory 17 | /// - ignoredForRouting: Whether the pair is ignored for routing or not. An ignored pair will not be explored during routes finding 18 | struct LBPairInformation { 19 | uint16 binStep; 20 | ILBLegacyPair LBPair; 21 | bool createdByOwner; 22 | bool ignoredForRouting; 23 | } 24 | 25 | event LBPairCreated( 26 | IERC20 indexed tokenX, IERC20 indexed tokenY, uint256 indexed binStep, ILBLegacyPair LBPair, uint256 pid 27 | ); 28 | 29 | event FeeRecipientSet(address oldRecipient, address newRecipient); 30 | 31 | event FlashLoanFeeSet(uint256 oldFlashLoanFee, uint256 newFlashLoanFee); 32 | 33 | event FeeParametersSet( 34 | address indexed sender, 35 | ILBLegacyPair indexed LBPair, 36 | uint256 binStep, 37 | uint256 baseFactor, 38 | uint256 filterPeriod, 39 | uint256 decayPeriod, 40 | uint256 reductionFactor, 41 | uint256 variableFeeControl, 42 | uint256 protocolShare, 43 | uint256 maxVolatilityAccumulator 44 | ); 45 | 46 | event FactoryLockedStatusUpdated(bool unlocked); 47 | 48 | event LBPairImplementationSet(address oldLBPairImplementation, address LBPairImplementation); 49 | 50 | event LBPairIgnoredStateChanged(ILBLegacyPair indexed LBPair, bool ignored); 51 | 52 | event PresetSet( 53 | uint256 indexed binStep, 54 | uint256 baseFactor, 55 | uint256 filterPeriod, 56 | uint256 decayPeriod, 57 | uint256 reductionFactor, 58 | uint256 variableFeeControl, 59 | uint256 protocolShare, 60 | uint256 maxVolatilityAccumulator, 61 | uint256 sampleLifetime 62 | ); 63 | 64 | event PresetRemoved(uint256 indexed binStep); 65 | 66 | event QuoteAssetAdded(IERC20 indexed quoteAsset); 67 | 68 | event QuoteAssetRemoved(IERC20 indexed quoteAsset); 69 | 70 | function MAX_FEE() external pure returns (uint256); 71 | 72 | function MIN_BIN_STEP() external pure returns (uint256); 73 | 74 | function MAX_BIN_STEP() external pure returns (uint256); 75 | 76 | function MAX_PROTOCOL_SHARE() external pure returns (uint256); 77 | 78 | function LBPairImplementation() external view returns (address); 79 | 80 | function getNumberOfQuoteAssets() external view returns (uint256); 81 | 82 | function getQuoteAsset(uint256 index) external view returns (IERC20); 83 | 84 | function isQuoteAsset(IERC20 token) external view returns (bool); 85 | 86 | function feeRecipient() external view returns (address); 87 | 88 | function flashLoanFee() external view returns (uint256); 89 | 90 | function creationUnlocked() external view returns (bool); 91 | 92 | function allLBPairs(uint256 id) external returns (ILBLegacyPair); 93 | 94 | function getNumberOfLBPairs() external view returns (uint256); 95 | 96 | function getLBPairInformation(IERC20 tokenX, IERC20 tokenY, uint256 binStep) 97 | external 98 | view 99 | returns (LBPairInformation memory); 100 | 101 | function getPreset(uint16 binStep) 102 | external 103 | view 104 | returns ( 105 | uint256 baseFactor, 106 | uint256 filterPeriod, 107 | uint256 decayPeriod, 108 | uint256 reductionFactor, 109 | uint256 variableFeeControl, 110 | uint256 protocolShare, 111 | uint256 maxAccumulator, 112 | uint256 sampleLifetime 113 | ); 114 | 115 | function getAllBinSteps() external view returns (uint256[] memory presetsBinStep); 116 | 117 | function getAllLBPairs(IERC20 tokenX, IERC20 tokenY) 118 | external 119 | view 120 | returns (LBPairInformation[] memory LBPairsBinStep); 121 | 122 | function setLBPairImplementation(address LBPairImplementation) external; 123 | 124 | function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) 125 | external 126 | returns (ILBLegacyPair pair); 127 | 128 | function setLBPairIgnored(IERC20 tokenX, IERC20 tokenY, uint256 binStep, bool ignored) external; 129 | 130 | function setPreset( 131 | uint16 binStep, 132 | uint16 baseFactor, 133 | uint16 filterPeriod, 134 | uint16 decayPeriod, 135 | uint16 reductionFactor, 136 | uint24 variableFeeControl, 137 | uint16 protocolShare, 138 | uint24 maxVolatilityAccumulator, 139 | uint16 sampleLifetime 140 | ) external; 141 | 142 | function removePreset(uint16 binStep) external; 143 | 144 | function setFeesParametersOnPair( 145 | IERC20 tokenX, 146 | IERC20 tokenY, 147 | uint16 binStep, 148 | uint16 baseFactor, 149 | uint16 filterPeriod, 150 | uint16 decayPeriod, 151 | uint16 reductionFactor, 152 | uint24 variableFeeControl, 153 | uint16 protocolShare, 154 | uint24 maxVolatilityAccumulator 155 | ) external; 156 | 157 | function setFeeRecipient(address feeRecipient) external; 158 | 159 | function setFlashLoanFee(uint256 flashLoanFee) external; 160 | 161 | function setFactoryLockedState(bool locked) external; 162 | 163 | function addQuoteAsset(IERC20 quoteAsset) external; 164 | 165 | function removeQuoteAsset(IERC20 quoteAsset) external; 166 | 167 | function forceDecay(ILBLegacyPair LBPair) external; 168 | } 169 | -------------------------------------------------------------------------------- /src/interfaces/ILBLegacyRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | import {ILBFactory} from "./ILBFactory.sol"; 8 | import {IJoeFactory} from "./IJoeFactory.sol"; 9 | import {ILBLegacyPair} from "./ILBLegacyPair.sol"; 10 | import {ILBToken} from "./ILBToken.sol"; 11 | import {IWNATIVE} from "./IWNATIVE.sol"; 12 | 13 | /// @title Liquidity Book Router Interface 14 | /// @author Trader Joe 15 | /// @notice Required interface of LBRouter contract 16 | interface ILBLegacyRouter { 17 | struct LiquidityParameters { 18 | IERC20 tokenX; 19 | IERC20 tokenY; 20 | uint256 binStep; 21 | uint256 amountX; 22 | uint256 amountY; 23 | uint256 amountXMin; 24 | uint256 amountYMin; 25 | uint256 activeIdDesired; 26 | uint256 idSlippage; 27 | int256[] deltaIds; 28 | uint256[] distributionX; 29 | uint256[] distributionY; 30 | address to; 31 | uint256 deadline; 32 | } 33 | 34 | function factory() external view returns (address); 35 | 36 | function wavax() external view returns (address); 37 | 38 | function oldFactory() external view returns (address); 39 | 40 | function getIdFromPrice(ILBLegacyPair LBPair, uint256 price) external view returns (uint24); 41 | 42 | function getPriceFromId(ILBLegacyPair LBPair, uint24 id) external view returns (uint256); 43 | 44 | function getSwapIn(ILBLegacyPair lbPair, uint256 amountOut, bool swapForY) 45 | external 46 | view 47 | returns (uint256 amountIn, uint256 feesIn); 48 | 49 | function getSwapOut(ILBLegacyPair lbPair, uint256 amountIn, bool swapForY) 50 | external 51 | view 52 | returns (uint256 amountOut, uint256 feesIn); 53 | 54 | function createLBPair(IERC20 tokenX, IERC20 tokenY, uint24 activeId, uint16 binStep) 55 | external 56 | returns (ILBLegacyPair pair); 57 | 58 | function addLiquidity(LiquidityParameters calldata liquidityParameters) 59 | external 60 | returns (uint256[] memory depositIds, uint256[] memory liquidityMinted); 61 | 62 | function addLiquidityAVAX(LiquidityParameters calldata liquidityParameters) 63 | external 64 | payable 65 | returns (uint256[] memory depositIds, uint256[] memory liquidityMinted); 66 | 67 | function removeLiquidity( 68 | IERC20 tokenX, 69 | IERC20 tokenY, 70 | uint16 binStep, 71 | uint256 amountXMin, 72 | uint256 amountYMin, 73 | uint256[] memory ids, 74 | uint256[] memory amounts, 75 | address to, 76 | uint256 deadline 77 | ) external returns (uint256 amountX, uint256 amountY); 78 | 79 | function removeLiquidityAVAX( 80 | IERC20 token, 81 | uint16 binStep, 82 | uint256 amountTokenMin, 83 | uint256 amountAVAXMin, 84 | uint256[] memory ids, 85 | uint256[] memory amounts, 86 | address payable to, 87 | uint256 deadline 88 | ) external returns (uint256 amountToken, uint256 amountAVAX); 89 | 90 | function swapExactTokensForTokens( 91 | uint256 amountIn, 92 | uint256 amountOutMin, 93 | uint256[] memory pairBinSteps, 94 | IERC20[] memory tokenPath, 95 | address to, 96 | uint256 deadline 97 | ) external returns (uint256 amountOut); 98 | 99 | function swapExactTokensForAVAX( 100 | uint256 amountIn, 101 | uint256 amountOutMinAVAX, 102 | uint256[] memory pairBinSteps, 103 | IERC20[] memory tokenPath, 104 | address payable to, 105 | uint256 deadline 106 | ) external returns (uint256 amountOut); 107 | 108 | function swapExactAVAXForTokens( 109 | uint256 amountOutMin, 110 | uint256[] memory pairBinSteps, 111 | IERC20[] memory tokenPath, 112 | address to, 113 | uint256 deadline 114 | ) external payable returns (uint256 amountOut); 115 | 116 | function swapTokensForExactTokens( 117 | uint256 amountOut, 118 | uint256 amountInMax, 119 | uint256[] memory pairBinSteps, 120 | IERC20[] memory tokenPath, 121 | address to, 122 | uint256 deadline 123 | ) external returns (uint256[] memory amountsIn); 124 | 125 | function swapTokensForExactAVAX( 126 | uint256 amountOut, 127 | uint256 amountInMax, 128 | uint256[] memory pairBinSteps, 129 | IERC20[] memory tokenPath, 130 | address payable to, 131 | uint256 deadline 132 | ) external returns (uint256[] memory amountsIn); 133 | 134 | function swapAVAXForExactTokens( 135 | uint256 amountOut, 136 | uint256[] memory pairBinSteps, 137 | IERC20[] memory tokenPath, 138 | address to, 139 | uint256 deadline 140 | ) external payable returns (uint256[] memory amountsIn); 141 | 142 | function swapExactTokensForTokensSupportingFeeOnTransferTokens( 143 | uint256 amountIn, 144 | uint256 amountOutMin, 145 | uint256[] memory pairBinSteps, 146 | IERC20[] memory tokenPath, 147 | address to, 148 | uint256 deadline 149 | ) external returns (uint256 amountOut); 150 | 151 | function swapExactTokensForAVAXSupportingFeeOnTransferTokens( 152 | uint256 amountIn, 153 | uint256 amountOutMinAVAX, 154 | uint256[] memory pairBinSteps, 155 | IERC20[] memory tokenPath, 156 | address payable to, 157 | uint256 deadline 158 | ) external returns (uint256 amountOut); 159 | 160 | function swapExactAVAXForTokensSupportingFeeOnTransferTokens( 161 | uint256 amountOutMin, 162 | uint256[] memory pairBinSteps, 163 | IERC20[] memory tokenPath, 164 | address to, 165 | uint256 deadline 166 | ) external payable returns (uint256 amountOut); 167 | 168 | function sweep(IERC20 token, address to, uint256 amount) external; 169 | 170 | function sweepLBToken(ILBToken _lbToken, address _to, uint256[] calldata _ids, uint256[] calldata _amounts) 171 | external; 172 | } 173 | -------------------------------------------------------------------------------- /src/interfaces/ILBLegacyToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 6 | 7 | /// @title Liquidity Book V2 Token Interface 8 | /// @author Trader Joe 9 | /// @notice Required interface of LBToken contract 10 | interface ILBLegacyToken is IERC165 { 11 | event TransferSingle(address indexed sender, address indexed from, address indexed to, uint256 id, uint256 amount); 12 | 13 | event TransferBatch( 14 | address indexed sender, address indexed from, address indexed to, uint256[] ids, uint256[] amounts 15 | ); 16 | 17 | event ApprovalForAll(address indexed account, address indexed sender, bool approved); 18 | 19 | function name() external view returns (string memory); 20 | 21 | function symbol() external view returns (string memory); 22 | 23 | function balanceOf(address account, uint256 id) external view returns (uint256); 24 | 25 | function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) 26 | external 27 | view 28 | returns (uint256[] memory batchBalances); 29 | 30 | function totalSupply(uint256 id) external view returns (uint256); 31 | 32 | function isApprovedForAll(address owner, address spender) external view returns (bool); 33 | 34 | function setApprovalForAll(address sender, bool approved) external; 35 | 36 | function safeTransferFrom(address from, address to, uint256 id, uint256 amount) external; 37 | 38 | function safeBatchTransferFrom(address from, address to, uint256[] calldata id, uint256[] calldata amount) 39 | external; 40 | } 41 | -------------------------------------------------------------------------------- /src/interfaces/ILBPair.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | import {Hooks} from "../libraries/Hooks.sol"; 8 | import {ILBFactory} from "./ILBFactory.sol"; 9 | import {ILBFlashLoanCallback} from "./ILBFlashLoanCallback.sol"; 10 | import {ILBToken} from "./ILBToken.sol"; 11 | 12 | interface ILBPair is ILBToken { 13 | error LBPair__ZeroBorrowAmount(); 14 | error LBPair__AddressZero(); 15 | error LBPair__EmptyMarketConfigs(); 16 | error LBPair__FlashLoanCallbackFailed(); 17 | error LBPair__FlashLoanInsufficientAmount(); 18 | error LBPair__InsufficientAmountIn(); 19 | error LBPair__InsufficientAmountOut(); 20 | error LBPair__InvalidInput(); 21 | error LBPair__InvalidStaticFeeParameters(); 22 | error LBPair__OnlyFactory(); 23 | error LBPair__OnlyProtocolFeeRecipient(); 24 | error LBPair__OutOfLiquidity(); 25 | error LBPair__TokenNotSupported(); 26 | error LBPair__ZeroAmount(uint24 id); 27 | error LBPair__ZeroAmountsOut(uint24 id); 28 | error LBPair__ZeroShares(uint24 id); 29 | error LBPair__MaxTotalFeeExceeded(); 30 | error LBPair__InvalidHooks(); 31 | 32 | struct MintArrays { 33 | uint256[] ids; 34 | bytes32[] amounts; 35 | uint256[] liquidityMinted; 36 | } 37 | 38 | event DepositedToBins(address indexed sender, address indexed to, uint256[] ids, bytes32[] amounts); 39 | 40 | event WithdrawnFromBins(address indexed sender, address indexed to, uint256[] ids, bytes32[] amounts); 41 | 42 | event CompositionFees(address indexed sender, uint24 id, bytes32 totalFees, bytes32 protocolFees); 43 | 44 | event CollectedProtocolFees(address indexed feeRecipient, bytes32 protocolFees); 45 | 46 | event Swap( 47 | address indexed sender, 48 | address indexed to, 49 | uint24 id, 50 | bytes32 amountsIn, 51 | bytes32 amountsOut, 52 | uint24 volatilityAccumulator, 53 | bytes32 totalFees, 54 | bytes32 protocolFees 55 | ); 56 | 57 | event StaticFeeParametersSet( 58 | address indexed sender, 59 | uint16 baseFactor, 60 | uint16 filterPeriod, 61 | uint16 decayPeriod, 62 | uint16 reductionFactor, 63 | uint24 variableFeeControl, 64 | uint16 protocolShare, 65 | uint24 maxVolatilityAccumulator 66 | ); 67 | 68 | event HooksParametersSet(address indexed sender, bytes32 hooksParameters); 69 | 70 | event FlashLoan( 71 | address indexed sender, 72 | ILBFlashLoanCallback indexed receiver, 73 | uint24 activeId, 74 | bytes32 amounts, 75 | bytes32 totalFees, 76 | bytes32 protocolFees 77 | ); 78 | 79 | event OracleLengthIncreased(address indexed sender, uint16 oracleLength); 80 | 81 | event ForcedDecay(address indexed sender, uint24 idReference, uint24 volatilityReference); 82 | 83 | function initialize( 84 | uint16 baseFactor, 85 | uint16 filterPeriod, 86 | uint16 decayPeriod, 87 | uint16 reductionFactor, 88 | uint24 variableFeeControl, 89 | uint16 protocolShare, 90 | uint24 maxVolatilityAccumulator, 91 | uint24 activeId 92 | ) external; 93 | 94 | function implementation() external view returns (address); 95 | 96 | function getFactory() external view returns (ILBFactory factory); 97 | 98 | function getTokenX() external view returns (IERC20 tokenX); 99 | 100 | function getTokenY() external view returns (IERC20 tokenY); 101 | 102 | function getBinStep() external view returns (uint16 binStep); 103 | 104 | function getReserves() external view returns (uint128 reserveX, uint128 reserveY); 105 | 106 | function getActiveId() external view returns (uint24 activeId); 107 | 108 | function getBin(uint24 id) external view returns (uint128 binReserveX, uint128 binReserveY); 109 | 110 | function getNextNonEmptyBin(bool swapForY, uint24 id) external view returns (uint24 nextId); 111 | 112 | function getProtocolFees() external view returns (uint128 protocolFeeX, uint128 protocolFeeY); 113 | 114 | function getStaticFeeParameters() 115 | external 116 | view 117 | returns ( 118 | uint16 baseFactor, 119 | uint16 filterPeriod, 120 | uint16 decayPeriod, 121 | uint16 reductionFactor, 122 | uint24 variableFeeControl, 123 | uint16 protocolShare, 124 | uint24 maxVolatilityAccumulator 125 | ); 126 | 127 | function getLBHooksParameters() external view returns (bytes32 hooksParameters); 128 | 129 | function getVariableFeeParameters() 130 | external 131 | view 132 | returns (uint24 volatilityAccumulator, uint24 volatilityReference, uint24 idReference, uint40 timeOfLastUpdate); 133 | 134 | function getOracleParameters() 135 | external 136 | view 137 | returns (uint8 sampleLifetime, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp); 138 | 139 | function getOracleSampleAt(uint40 lookupTimestamp) 140 | external 141 | view 142 | returns (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed); 143 | 144 | function getPriceFromId(uint24 id) external view returns (uint256 price); 145 | 146 | function getIdFromPrice(uint256 price) external view returns (uint24 id); 147 | 148 | function getSwapIn(uint128 amountOut, bool swapForY) 149 | external 150 | view 151 | returns (uint128 amountIn, uint128 amountOutLeft, uint128 fee); 152 | 153 | function getSwapOut(uint128 amountIn, bool swapForY) 154 | external 155 | view 156 | returns (uint128 amountInLeft, uint128 amountOut, uint128 fee); 157 | 158 | function swap(bool swapForY, address to) external returns (bytes32 amountsOut); 159 | 160 | function flashLoan(ILBFlashLoanCallback receiver, bytes32 amounts, bytes calldata data) external; 161 | 162 | function mint(address to, bytes32[] calldata liquidityConfigs, address refundTo) 163 | external 164 | returns (bytes32 amountsReceived, bytes32 amountsLeft, uint256[] memory liquidityMinted); 165 | 166 | function burn(address from, address to, uint256[] calldata ids, uint256[] calldata amountsToBurn) 167 | external 168 | returns (bytes32[] memory amounts); 169 | 170 | function collectProtocolFees() external returns (bytes32 collectedProtocolFees); 171 | 172 | function increaseOracleLength(uint16 newLength) external; 173 | 174 | function setStaticFeeParameters( 175 | uint16 baseFactor, 176 | uint16 filterPeriod, 177 | uint16 decayPeriod, 178 | uint16 reductionFactor, 179 | uint24 variableFeeControl, 180 | uint16 protocolShare, 181 | uint24 maxVolatilityAccumulator 182 | ) external; 183 | 184 | function setHooksParameters(bytes32 hooksParameters, bytes calldata onHooksSetData) external; 185 | 186 | function forceDecay() external; 187 | } 188 | -------------------------------------------------------------------------------- /src/interfaces/ILBToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | /** 6 | * @title Liquidity Book Token Interface 7 | * @author Trader Joe 8 | * @notice Interface to interact with the LBToken. 9 | */ 10 | interface ILBToken { 11 | error LBToken__AddressThisOrZero(); 12 | error LBToken__InvalidLength(); 13 | error LBToken__SelfApproval(address owner); 14 | error LBToken__SpenderNotApproved(address from, address spender); 15 | error LBToken__TransferExceedsBalance(address from, uint256 id, uint256 amount); 16 | error LBToken__BurnExceedsBalance(address from, uint256 id, uint256 amount); 17 | 18 | event TransferBatch( 19 | address indexed sender, address indexed from, address indexed to, uint256[] ids, uint256[] amounts 20 | ); 21 | 22 | event ApprovalForAll(address indexed account, address indexed sender, bool approved); 23 | 24 | function name() external view returns (string memory); 25 | 26 | function symbol() external view returns (string memory); 27 | 28 | function totalSupply(uint256 id) external view returns (uint256); 29 | 30 | function balanceOf(address account, uint256 id) external view returns (uint256); 31 | 32 | function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) 33 | external 34 | view 35 | returns (uint256[] memory); 36 | 37 | function isApprovedForAll(address owner, address spender) external view returns (bool); 38 | 39 | function approveForAll(address spender, bool approved) external; 40 | 41 | function batchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts) external; 42 | } 43 | -------------------------------------------------------------------------------- /src/interfaces/IWNATIVE.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | /** 8 | * @title WNATIVE Interface 9 | * @notice Required interface of Wrapped NATIVE contract 10 | */ 11 | interface IWNATIVE is IERC20 { 12 | function deposit() external payable; 13 | 14 | function withdraw(uint256) external; 15 | } 16 | -------------------------------------------------------------------------------- /src/libraries/Clone.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | /** 6 | * @title Clone 7 | * @notice Class with helper read functions for clone with immutable args. 8 | * @author Trader Joe 9 | * @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Clone.sol) 10 | * @author Adapted from clones with immutable args by zefram.eth, Saw-mon & Natalie 11 | * (https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args) 12 | */ 13 | abstract contract Clone { 14 | /** 15 | * @dev Reads an immutable arg with type bytes 16 | * @param argOffset The offset of the arg in the immutable args 17 | * @param length The length of the arg 18 | * @return arg The immutable bytes arg 19 | */ 20 | function _getArgBytes(uint256 argOffset, uint256 length) internal pure returns (bytes memory arg) { 21 | uint256 offset = _getImmutableArgsOffset(); 22 | /// @solidity memory-safe-assembly 23 | assembly { 24 | // Grab the free memory pointer. 25 | arg := mload(0x40) 26 | // Store the array length. 27 | mstore(arg, length) 28 | // Copy the array. 29 | calldatacopy(add(arg, 0x20), add(offset, argOffset), length) 30 | // Allocate the memory, rounded up to the next 32 byte boundary. 31 | mstore(0x40, and(add(add(arg, 0x3f), length), not(0x1f))) 32 | } 33 | } 34 | 35 | /** 36 | * @dev Reads an immutable arg with type address 37 | * @param argOffset The offset of the arg in the immutable args 38 | * @return arg The immutable address arg 39 | */ 40 | function _getArgAddress(uint256 argOffset) internal pure returns (address arg) { 41 | uint256 offset = _getImmutableArgsOffset(); 42 | /// @solidity memory-safe-assembly 43 | assembly { 44 | arg := shr(0x60, calldataload(add(offset, argOffset))) 45 | } 46 | } 47 | 48 | /** 49 | * @dev Reads an immutable arg with type uint256 50 | * @param argOffset The offset of the arg in the immutable args 51 | * @return arg The immutable uint256 arg 52 | */ 53 | function _getArgUint256(uint256 argOffset) internal pure returns (uint256 arg) { 54 | uint256 offset = _getImmutableArgsOffset(); 55 | /// @solidity memory-safe-assembly 56 | assembly { 57 | arg := calldataload(add(offset, argOffset)) 58 | } 59 | } 60 | 61 | /** 62 | * @dev Reads a uint256 array stored in the immutable args. 63 | * @param argOffset The offset of the arg in the immutable args 64 | * @param length The length of the arg 65 | * @return arg The immutable uint256 array arg 66 | */ 67 | function _getArgUint256Array(uint256 argOffset, uint256 length) internal pure returns (uint256[] memory arg) { 68 | uint256 offset = _getImmutableArgsOffset(); 69 | /// @solidity memory-safe-assembly 70 | assembly { 71 | // Grab the free memory pointer. 72 | arg := mload(0x40) 73 | // Store the array length. 74 | mstore(arg, length) 75 | // Copy the array. 76 | calldatacopy(add(arg, 0x20), add(offset, argOffset), shl(5, length)) 77 | // Allocate the memory. 78 | mstore(0x40, add(add(arg, 0x20), shl(5, length))) 79 | } 80 | } 81 | 82 | /** 83 | * @dev Reads an immutable arg with type uint64 84 | * @param argOffset The offset of the arg in the immutable args 85 | * @return arg The immutable uint64 arg 86 | */ 87 | function _getArgUint64(uint256 argOffset) internal pure returns (uint64 arg) { 88 | uint256 offset = _getImmutableArgsOffset(); 89 | /// @solidity memory-safe-assembly 90 | assembly { 91 | arg := shr(0xc0, calldataload(add(offset, argOffset))) 92 | } 93 | } 94 | 95 | /** 96 | * @dev Reads an immutable arg with type uint16 97 | * @param argOffset The offset of the arg in the immutable args 98 | * @return arg The immutable uint16 arg 99 | */ 100 | function _getArgUint16(uint256 argOffset) internal pure returns (uint16 arg) { 101 | uint256 offset = _getImmutableArgsOffset(); 102 | /// @solidity memory-safe-assembly 103 | assembly { 104 | arg := shr(0xf0, calldataload(add(offset, argOffset))) 105 | } 106 | } 107 | 108 | /** 109 | * @dev Reads an immutable arg with type uint8 110 | * @param argOffset The offset of the arg in the immutable args 111 | * @return arg The immutable uint8 arg 112 | */ 113 | function _getArgUint8(uint256 argOffset) internal pure returns (uint8 arg) { 114 | uint256 offset = _getImmutableArgsOffset(); 115 | /// @solidity memory-safe-assembly 116 | assembly { 117 | arg := shr(0xf8, calldataload(add(offset, argOffset))) 118 | } 119 | } 120 | 121 | /** 122 | * @dev Reads the offset of the packed immutable args in calldata. 123 | * @return offset The offset of the packed immutable args in calldata. 124 | */ 125 | function _getImmutableArgsOffset() internal pure returns (uint256 offset) { 126 | /// @solidity memory-safe-assembly 127 | assembly { 128 | offset := sub(calldatasize(), shr(0xf0, calldataload(sub(calldatasize(), 2)))) 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/libraries/Constants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | /** 6 | * @title Liquidity Book Constants Library 7 | * @author Trader Joe 8 | * @notice Set of constants for Liquidity Book contracts 9 | */ 10 | library Constants { 11 | uint8 internal constant SCALE_OFFSET = 128; 12 | uint256 internal constant SCALE = 1 << SCALE_OFFSET; 13 | 14 | uint256 internal constant PRECISION = 1e18; 15 | uint256 internal constant SQUARED_PRECISION = PRECISION * PRECISION; 16 | 17 | uint256 internal constant MAX_FEE = 0.1e18; // 10% 18 | uint256 internal constant MAX_PROTOCOL_SHARE = 2_500; // 25% of the fee 19 | 20 | uint256 internal constant BASIS_POINT_MAX = 10_000; 21 | 22 | // (2^256 - 1) / (2 * log(2**128) / log(1.0001)) 23 | uint256 internal constant MAX_LIQUIDITY_PER_BIN = 24 | 65251743116719673010965625540244653191619923014385985379600384103134737; 25 | 26 | /// @dev The expected return after a successful flash loan 27 | bytes32 internal constant CALLBACK_SUCCESS = keccak256("LBPair.onFlashLoan"); 28 | } 29 | -------------------------------------------------------------------------------- /src/libraries/FeeHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import {Constants} from "./Constants.sol"; 6 | 7 | /** 8 | * @title Liquidity Book Fee Helper Library 9 | * @author Trader Joe 10 | * @notice This library contains functions to calculate fees 11 | */ 12 | library FeeHelper { 13 | error FeeHelper__FeeTooLarge(); 14 | error FeeHelper__ProtocolShareTooLarge(); 15 | 16 | /** 17 | * @dev Modifier to check that the fee is not too large 18 | * @param fee The fee 19 | */ 20 | modifier verifyFee(uint128 fee) { 21 | _verifyFee(fee); 22 | _; 23 | } 24 | 25 | /** 26 | * @dev Modifier to check that the protocol share is not too large 27 | * @param protocolShare The protocol share 28 | */ 29 | modifier verifyProtocolShare(uint128 protocolShare) { 30 | if (protocolShare > Constants.MAX_PROTOCOL_SHARE) revert FeeHelper__ProtocolShareTooLarge(); 31 | _; 32 | } 33 | 34 | /** 35 | * @dev Calculates the fee amount from the amount with fees, rounding up 36 | * @param amountWithFees The amount with fees 37 | * @param totalFee The total fee 38 | * @return feeAmount The fee amount 39 | */ 40 | function getFeeAmountFrom(uint128 amountWithFees, uint128 totalFee) 41 | internal 42 | pure 43 | verifyFee(totalFee) 44 | returns (uint128) 45 | { 46 | unchecked { 47 | // Can't overflow, max(result) = (type(uint128).max * 0.1e18 + 1e18 - 1) / 1e18 < 2^128 48 | return uint128((uint256(amountWithFees) * totalFee + Constants.PRECISION - 1) / Constants.PRECISION); 49 | } 50 | } 51 | 52 | /** 53 | * @dev Calculates the fee amount that will be charged, rounding up 54 | * @param amount The amount 55 | * @param totalFee The total fee 56 | * @return feeAmount The fee amount 57 | */ 58 | function getFeeAmount(uint128 amount, uint128 totalFee) internal pure verifyFee(totalFee) returns (uint128) { 59 | unchecked { 60 | uint256 denominator = Constants.PRECISION - totalFee; 61 | // Can't overflow, max(result) = (type(uint128).max * 0.1e18 + (1e18 - 1)) / 0.9e18 < 2^128 62 | return uint128((uint256(amount) * totalFee + denominator - 1) / denominator); 63 | } 64 | } 65 | 66 | /** 67 | * @dev Calculates the composition fee amount from the amount with fees, rounding down 68 | * @param amountWithFees The amount with fees 69 | * @param totalFee The total fee 70 | * @return The amount with fees 71 | */ 72 | function getCompositionFee(uint128 amountWithFees, uint128 totalFee) 73 | internal 74 | pure 75 | verifyFee(totalFee) 76 | returns (uint128) 77 | { 78 | unchecked { 79 | uint256 denominator = Constants.SQUARED_PRECISION; 80 | // Can't overflow, max(result) = type(uint128).max * 0.1e18 * 1.1e18 / 1e36 <= 2^128 * 0.11e36 / 1e36 < 2^128 81 | return uint128(uint256(amountWithFees) * totalFee * (uint256(totalFee) + Constants.PRECISION) / denominator); 82 | } 83 | } 84 | 85 | /** 86 | * @dev Calculates the protocol fee amount from the fee amount and the protocol share, rounding down 87 | * @param feeAmount The fee amount 88 | * @param protocolShare The protocol share 89 | * @return protocolFeeAmount The protocol fee amount 90 | */ 91 | function getProtocolFeeAmount(uint128 feeAmount, uint128 protocolShare) 92 | internal 93 | pure 94 | verifyProtocolShare(protocolShare) 95 | returns (uint128) 96 | { 97 | unchecked { 98 | return uint128(uint256(feeAmount) * protocolShare / Constants.BASIS_POINT_MAX); 99 | } 100 | } 101 | 102 | /** 103 | * @dev Internal function to check that the fee is not too large 104 | * @param fee The fee 105 | */ 106 | function _verifyFee(uint128 fee) private pure { 107 | if (fee > Constants.MAX_FEE) revert FeeHelper__FeeTooLarge(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/libraries/JoeLibrary.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | /** 6 | * @title Liquidity Book Joe Library Helper Library 7 | * @author Trader Joe 8 | * @notice Helper contract used for Joe V1 related calculations 9 | */ 10 | library JoeLibrary { 11 | error JoeLibrary__AddressZero(); 12 | error JoeLibrary__IdenticalAddresses(); 13 | error JoeLibrary__InsufficientAmount(); 14 | error JoeLibrary__InsufficientLiquidity(); 15 | 16 | // returns sorted token addresses, used to handle return values from pairs sorted in this order 17 | function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { 18 | if (tokenA == tokenB) revert JoeLibrary__IdenticalAddresses(); 19 | (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); 20 | if (token0 == address(0)) revert JoeLibrary__AddressZero(); 21 | } 22 | 23 | // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset 24 | function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) internal pure returns (uint256 amountB) { 25 | if (amountA == 0) revert JoeLibrary__InsufficientAmount(); 26 | if (reserveA == 0 || reserveB == 0) revert JoeLibrary__InsufficientLiquidity(); 27 | amountB = (amountA * reserveB) / reserveA; 28 | } 29 | 30 | // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset 31 | function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) 32 | internal 33 | pure 34 | returns (uint256 amountOut) 35 | { 36 | if (amountIn == 0) revert JoeLibrary__InsufficientAmount(); 37 | if (reserveIn == 0 || reserveOut == 0) revert JoeLibrary__InsufficientLiquidity(); 38 | uint256 amountInWithFee = amountIn * 997; 39 | uint256 numerator = amountInWithFee * reserveOut; 40 | uint256 denominator = reserveIn * 1000 + amountInWithFee; 41 | amountOut = numerator / denominator; 42 | } 43 | 44 | // given an output amount of an asset and pair reserves, returns a required input amount of the other asset 45 | function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) 46 | internal 47 | pure 48 | returns (uint256 amountIn) 49 | { 50 | if (amountOut == 0) revert JoeLibrary__InsufficientAmount(); 51 | if (reserveIn == 0 || reserveOut == 0) revert JoeLibrary__InsufficientLiquidity(); 52 | uint256 numerator = reserveIn * amountOut * 1000; 53 | uint256 denominator = (reserveOut - amountOut) * 997; 54 | amountIn = numerator / denominator + 1; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/libraries/PriceHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import {Uint128x128Math} from "./math/Uint128x128Math.sol"; 6 | import {Uint256x256Math} from "./math/Uint256x256Math.sol"; 7 | import {SafeCast} from "./math/SafeCast.sol"; 8 | import {Constants} from "./Constants.sol"; 9 | 10 | /** 11 | * @title Liquidity Book Price Helper Library 12 | * @author Trader Joe 13 | * @notice This library contains functions to calculate prices 14 | */ 15 | library PriceHelper { 16 | using Uint128x128Math for uint256; 17 | using Uint256x256Math for uint256; 18 | using SafeCast for uint256; 19 | 20 | int256 private constant REAL_ID_SHIFT = 1 << 23; 21 | 22 | /** 23 | * @dev Calculates the price from the id and the bin step 24 | * @param id The id 25 | * @param binStep The bin step 26 | * @return price The price as a 128.128-binary fixed-point number 27 | */ 28 | function getPriceFromId(uint24 id, uint16 binStep) internal pure returns (uint256 price) { 29 | uint256 base = getBase(binStep); 30 | int256 exponent = getExponent(id); 31 | 32 | price = base.pow(exponent); 33 | } 34 | 35 | /** 36 | * @dev Calculates the id from the price and the bin step 37 | * @param price The price as a 128.128-binary fixed-point number 38 | * @param binStep The bin step 39 | * @return id The id 40 | */ 41 | function getIdFromPrice(uint256 price, uint16 binStep) internal pure returns (uint24 id) { 42 | uint256 base = getBase(binStep); 43 | int256 realId = price.log2() / base.log2(); 44 | 45 | unchecked { 46 | id = uint256(REAL_ID_SHIFT + realId).safe24(); 47 | } 48 | } 49 | 50 | /** 51 | * @dev Calculates the base from the bin step, which is `1 + binStep / BASIS_POINT_MAX` 52 | * @param binStep The bin step 53 | * @return base The base 54 | */ 55 | function getBase(uint16 binStep) internal pure returns (uint256) { 56 | unchecked { 57 | return Constants.SCALE + (uint256(binStep) << Constants.SCALE_OFFSET) / Constants.BASIS_POINT_MAX; 58 | } 59 | } 60 | 61 | /** 62 | * @dev Calculates the exponent from the id, which is `id - REAL_ID_SHIFT` 63 | * @param id The id 64 | * @return exponent The exponent 65 | */ 66 | function getExponent(uint24 id) internal pure returns (int256) { 67 | unchecked { 68 | return int256(uint256(id)) - REAL_ID_SHIFT; 69 | } 70 | } 71 | 72 | /** 73 | * @dev Converts a price with 18 decimals to a 128.128-binary fixed-point number 74 | * @param price The price with 18 decimals 75 | * @return price128x128 The 128.128-binary fixed-point number 76 | */ 77 | function convertDecimalPriceTo128x128(uint256 price) internal pure returns (uint256) { 78 | return price.shiftDivRoundDown(Constants.SCALE_OFFSET, Constants.PRECISION); 79 | } 80 | 81 | /** 82 | * @dev Converts a 128.128-binary fixed-point number to a price with 18 decimals 83 | * @param price128x128 The 128.128-binary fixed-point number 84 | * @return price The price with 18 decimals 85 | */ 86 | function convert128x128PriceToDecimal(uint256 price128x128) internal pure returns (uint256) { 87 | return price128x128.mulShiftRoundDown(Constants.PRECISION, Constants.SCALE_OFFSET); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/libraries/ReentrancyGuardUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol) 3 | 4 | pragma solidity ^0.8.20; 5 | 6 | import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 7 | 8 | /** 9 | * @dev This contract is a fork of the `ReentrancyGuardUpgradeable` contract from OpenZeppelin 10 | * that has been modified to update the `_nonReentrantBefore` and `_nonReentrantAfter` 11 | * functions to `internal` visibility. 12 | * 13 | * Contract module that helps prevent reentrant calls to a function. 14 | * 15 | * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier 16 | * available, which can be applied to functions to make sure there are no nested 17 | * (reentrant) calls to them. 18 | * 19 | * Note that because there is a single `nonReentrant` guard, functions marked as 20 | * `nonReentrant` may not call one another. This can be worked around by making 21 | * those functions `private`, and then adding `external` `nonReentrant` entry 22 | * points to them. 23 | * 24 | * TIP: If you would like to learn more about reentrancy and alternative ways 25 | * to protect against it, check out our blog post 26 | * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. 27 | */ 28 | abstract contract ReentrancyGuardUpgradeable is Initializable { 29 | // Booleans are more expensive than uint256 or any type that takes up a full 30 | // word because each write operation emits an extra SLOAD to first read the 31 | // slot's contents, replace the bits taken up by the boolean, and then write 32 | // back. This is the compiler's defense against contract upgrades and 33 | // pointer aliasing, and it cannot be disabled. 34 | 35 | // The values being non-zero value makes deployment a bit more expensive, 36 | // but in exchange the refund on every call to nonReentrant will be lower in 37 | // amount. Since refunds are capped to a percentage of the total 38 | // transaction's gas, it is best to keep them low in cases like this one, to 39 | // increase the likelihood of the full refund coming into effect. 40 | uint256 private constant NOT_ENTERED = 1; 41 | uint256 private constant ENTERED = 2; 42 | 43 | /// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard 44 | struct ReentrancyGuardStorage { 45 | uint256 _status; 46 | } 47 | 48 | // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff)) 49 | bytes32 private constant ReentrancyGuardStorageLocation = 50 | 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00; 51 | 52 | function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) { 53 | assembly { 54 | $.slot := ReentrancyGuardStorageLocation 55 | } 56 | } 57 | 58 | /** 59 | * @dev Unauthorized reentrant call. 60 | */ 61 | error ReentrancyGuardReentrantCall(); 62 | 63 | function __ReentrancyGuard_init() internal onlyInitializing { 64 | __ReentrancyGuard_init_unchained(); 65 | } 66 | 67 | function __ReentrancyGuard_init_unchained() internal onlyInitializing { 68 | ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); 69 | $._status = NOT_ENTERED; 70 | } 71 | 72 | /** 73 | * @dev Prevents a contract from calling itself, directly or indirectly. 74 | * Calling a `nonReentrant` function from another `nonReentrant` 75 | * function is not supported. It is possible to prevent this from happening 76 | * by making the `nonReentrant` function external, and making it call a 77 | * `private` function that does the actual work. 78 | */ 79 | modifier nonReentrant() { 80 | _nonReentrantBefore(); 81 | _; 82 | _nonReentrantAfter(); 83 | } 84 | 85 | function _nonReentrantBefore() internal { 86 | ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); 87 | // On the first call to nonReentrant, _status will be NOT_ENTERED 88 | if ($._status == ENTERED) { 89 | revert ReentrancyGuardReentrantCall(); 90 | } 91 | 92 | // Any calls to nonReentrant after this point will fail 93 | $._status = ENTERED; 94 | } 95 | 96 | function _nonReentrantAfter() internal { 97 | ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); 98 | // By storing the original value once again, a refund is triggered (see 99 | // https://eips.ethereum.org/EIPS/eip-2200) 100 | $._status = NOT_ENTERED; 101 | } 102 | 103 | /** 104 | * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a 105 | * `nonReentrant` function in the call stack. 106 | */ 107 | function _reentrancyGuardEntered() internal view returns (bool) { 108 | ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage(); 109 | return $._status == ENTERED; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/libraries/TokenHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | /** 8 | * @title Liquidity Book Token Helper Library 9 | * @author Trader Joe 10 | * @notice Wrappers around ERC20 operations that throw on failure (when the token 11 | * contract returns false). Tokens that return no value (and instead revert or 12 | * throw on failure) are also supported, non-reverting calls are assumed to be 13 | * successful. 14 | * To use this library you can add a `using TokenHelper for IERC20;` statement to your contract, 15 | * which allows you to call the safe operation as `token.safeTransfer(...)` 16 | */ 17 | library TokenHelper { 18 | error TokenHelper__TransferFailed(); 19 | 20 | /** 21 | * @notice Transfers token and reverts if the transfer fails 22 | * @param token The address of the token 23 | * @param owner The owner of the tokens 24 | * @param recipient The address of the recipient 25 | * @param amount The amount to send 26 | */ 27 | function safeTransferFrom(IERC20 token, address owner, address recipient, uint256 amount) internal { 28 | bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, owner, recipient, amount); 29 | 30 | _callAndCatch(token, data); 31 | } 32 | 33 | /** 34 | * @notice Transfers token and reverts if the transfer fails 35 | * @param token The address of the token 36 | * @param recipient The address of the recipient 37 | * @param amount The amount to send 38 | */ 39 | function safeTransfer(IERC20 token, address recipient, uint256 amount) internal { 40 | bytes memory data = abi.encodeWithSelector(token.transfer.selector, recipient, amount); 41 | 42 | _callAndCatch(token, data); 43 | } 44 | 45 | function _callAndCatch(IERC20 token, bytes memory data) internal { 46 | bool success; 47 | 48 | assembly { 49 | mstore(0x00, 0) 50 | 51 | success := call(gas(), token, 0, add(data, 0x20), mload(data), 0x00, 0x20) 52 | 53 | switch success 54 | case 0 { 55 | if returndatasize() { 56 | returndatacopy(0x00, 0x00, returndatasize()) 57 | revert(0x00, returndatasize()) 58 | } 59 | } 60 | default { 61 | switch returndatasize() 62 | case 0 { success := iszero(iszero(extcodesize(token))) } 63 | default { success := and(success, eq(mload(0x00), 1)) } 64 | } 65 | } 66 | 67 | if (!success) revert TokenHelper__TransferFailed(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/libraries/math/BitMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | /** 6 | * @title Liquidity Book Bit Math Library 7 | * @author Trader Joe 8 | * @notice Helper contract used for bit calculations 9 | */ 10 | library BitMath { 11 | /** 12 | * @dev Returns the index of the closest bit on the right of x that is non null 13 | * @param x The value as a uint256 14 | * @param bit The index of the bit to start searching at 15 | * @return id The index of the closest non null bit on the right of x. 16 | * If there is no closest bit, it returns max(uint256) 17 | */ 18 | function closestBitRight(uint256 x, uint8 bit) internal pure returns (uint256 id) { 19 | unchecked { 20 | uint256 shift = 255 - bit; 21 | x <<= shift; 22 | 23 | // can't overflow as it's non-zero and we shifted it by `_shift` 24 | return (x == 0) ? type(uint256).max : mostSignificantBit(x) - shift; 25 | } 26 | } 27 | 28 | /** 29 | * @dev Returns the index of the closest bit on the left of x that is non null 30 | * @param x The value as a uint256 31 | * @param bit The index of the bit to start searching at 32 | * @return id The index of the closest non null bit on the left of x. 33 | * If there is no closest bit, it returns max(uint256) 34 | */ 35 | function closestBitLeft(uint256 x, uint8 bit) internal pure returns (uint256 id) { 36 | unchecked { 37 | x >>= bit; 38 | 39 | return (x == 0) ? type(uint256).max : leastSignificantBit(x) + bit; 40 | } 41 | } 42 | 43 | /** 44 | * @dev Returns the index of the most significant bit of x 45 | * This function returns 0 if x is 0 46 | * @param x The value as a uint256 47 | * @return msb The index of the most significant bit of x 48 | */ 49 | function mostSignificantBit(uint256 x) internal pure returns (uint8 msb) { 50 | assembly { 51 | if gt(x, 0xffffffffffffffffffffffffffffffff) { 52 | x := shr(128, x) 53 | msb := 128 54 | } 55 | if gt(x, 0xffffffffffffffff) { 56 | x := shr(64, x) 57 | msb := add(msb, 64) 58 | } 59 | if gt(x, 0xffffffff) { 60 | x := shr(32, x) 61 | msb := add(msb, 32) 62 | } 63 | if gt(x, 0xffff) { 64 | x := shr(16, x) 65 | msb := add(msb, 16) 66 | } 67 | if gt(x, 0xff) { 68 | x := shr(8, x) 69 | msb := add(msb, 8) 70 | } 71 | if gt(x, 0xf) { 72 | x := shr(4, x) 73 | msb := add(msb, 4) 74 | } 75 | if gt(x, 0x3) { 76 | x := shr(2, x) 77 | msb := add(msb, 2) 78 | } 79 | if gt(x, 0x1) { msb := add(msb, 1) } 80 | } 81 | } 82 | 83 | /** 84 | * @dev Returns the index of the least significant bit of x 85 | * This function returns 255 if x is 0 86 | * @param x The value as a uint256 87 | * @return lsb The index of the least significant bit of x 88 | */ 89 | function leastSignificantBit(uint256 x) internal pure returns (uint8 lsb) { 90 | assembly { 91 | let sx := shl(128, x) 92 | if iszero(iszero(sx)) { 93 | lsb := 128 94 | x := sx 95 | } 96 | sx := shl(64, x) 97 | if iszero(iszero(sx)) { 98 | x := sx 99 | lsb := add(lsb, 64) 100 | } 101 | sx := shl(32, x) 102 | if iszero(iszero(sx)) { 103 | x := sx 104 | lsb := add(lsb, 32) 105 | } 106 | sx := shl(16, x) 107 | if iszero(iszero(sx)) { 108 | x := sx 109 | lsb := add(lsb, 16) 110 | } 111 | sx := shl(8, x) 112 | if iszero(iszero(sx)) { 113 | x := sx 114 | lsb := add(lsb, 8) 115 | } 116 | sx := shl(4, x) 117 | if iszero(iszero(sx)) { 118 | x := sx 119 | lsb := add(lsb, 4) 120 | } 121 | sx := shl(2, x) 122 | if iszero(iszero(sx)) { 123 | x := sx 124 | lsb := add(lsb, 2) 125 | } 126 | if iszero(iszero(shl(1, x))) { lsb := add(lsb, 1) } 127 | 128 | lsb := sub(255, lsb) 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/libraries/math/Encoded.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | /** 6 | * @title Liquidity Book Encoded Library 7 | * @author Trader Joe 8 | * @notice Helper contract used for decoding bytes32 sample 9 | */ 10 | library Encoded { 11 | uint256 internal constant MASK_UINT1 = 0x1; 12 | uint256 internal constant MASK_UINT8 = 0xff; 13 | uint256 internal constant MASK_UINT12 = 0xfff; 14 | uint256 internal constant MASK_UINT14 = 0x3fff; 15 | uint256 internal constant MASK_UINT16 = 0xffff; 16 | uint256 internal constant MASK_UINT20 = 0xfffff; 17 | uint256 internal constant MASK_UINT24 = 0xffffff; 18 | uint256 internal constant MASK_UINT40 = 0xffffffffff; 19 | uint256 internal constant MASK_UINT64 = 0xffffffffffffffff; 20 | uint256 internal constant MASK_UINT128 = 0xffffffffffffffffffffffffffffffff; 21 | 22 | /** 23 | * @notice Internal function to set a value in an encoded bytes32 using a mask and offset 24 | * @dev This function can overflow 25 | * @param encoded The previous encoded value 26 | * @param value The value to encode 27 | * @param mask The mask 28 | * @param offset The offset 29 | * @return newEncoded The new encoded value 30 | */ 31 | function set(bytes32 encoded, uint256 value, uint256 mask, uint256 offset) 32 | internal 33 | pure 34 | returns (bytes32 newEncoded) 35 | { 36 | assembly { 37 | newEncoded := and(encoded, not(shl(offset, mask))) 38 | newEncoded := or(newEncoded, shl(offset, and(value, mask))) 39 | } 40 | } 41 | 42 | /** 43 | * @notice Internal function to set a bool in an encoded bytes32 using an offset 44 | * @dev This function can overflow 45 | * @param encoded The previous encoded value 46 | * @param boolean The bool to encode 47 | * @param offset The offset 48 | * @return newEncoded The new encoded value 49 | */ 50 | function setBool(bytes32 encoded, bool boolean, uint256 offset) internal pure returns (bytes32 newEncoded) { 51 | return set(encoded, boolean ? 1 : 0, MASK_UINT1, offset); 52 | } 53 | 54 | /** 55 | * @notice Internal function to decode a bytes32 sample using a mask and offset 56 | * @dev This function can overflow 57 | * @param encoded The encoded value 58 | * @param mask The mask 59 | * @param offset The offset 60 | * @return value The decoded value 61 | */ 62 | function decode(bytes32 encoded, uint256 mask, uint256 offset) internal pure returns (uint256 value) { 63 | assembly { 64 | value := and(shr(offset, encoded), mask) 65 | } 66 | } 67 | 68 | /** 69 | * @notice Internal function to decode a bytes32 sample into a bool using an offset 70 | * @dev This function can overflow 71 | * @param encoded The encoded value 72 | * @param offset The offset 73 | * @return boolean The decoded value as a bool 74 | */ 75 | function decodeBool(bytes32 encoded, uint256 offset) internal pure returns (bool boolean) { 76 | assembly { 77 | boolean := and(shr(offset, encoded), MASK_UINT1) 78 | } 79 | } 80 | 81 | /** 82 | * @notice Internal function to decode a bytes32 sample into a uint8 using an offset 83 | * @dev This function can overflow 84 | * @param encoded The encoded value 85 | * @param offset The offset 86 | * @return value The decoded value 87 | */ 88 | function decodeUint8(bytes32 encoded, uint256 offset) internal pure returns (uint8 value) { 89 | assembly { 90 | value := and(shr(offset, encoded), MASK_UINT8) 91 | } 92 | } 93 | 94 | /** 95 | * @notice Internal function to decode a bytes32 sample into a uint12 using an offset 96 | * @dev This function can overflow 97 | * @param encoded The encoded value 98 | * @param offset The offset 99 | * @return value The decoded value as a uint16, since uint12 is not supported 100 | */ 101 | function decodeUint12(bytes32 encoded, uint256 offset) internal pure returns (uint16 value) { 102 | assembly { 103 | value := and(shr(offset, encoded), MASK_UINT12) 104 | } 105 | } 106 | 107 | /** 108 | * @notice Internal function to decode a bytes32 sample into a uint14 using an offset 109 | * @dev This function can overflow 110 | * @param encoded The encoded value 111 | * @param offset The offset 112 | * @return value The decoded value as a uint16, since uint14 is not supported 113 | */ 114 | function decodeUint14(bytes32 encoded, uint256 offset) internal pure returns (uint16 value) { 115 | assembly { 116 | value := and(shr(offset, encoded), MASK_UINT14) 117 | } 118 | } 119 | 120 | /** 121 | * @notice Internal function to decode a bytes32 sample into a uint16 using an offset 122 | * @dev This function can overflow 123 | * @param encoded The encoded value 124 | * @param offset The offset 125 | * @return value The decoded value 126 | */ 127 | function decodeUint16(bytes32 encoded, uint256 offset) internal pure returns (uint16 value) { 128 | assembly { 129 | value := and(shr(offset, encoded), MASK_UINT16) 130 | } 131 | } 132 | 133 | /** 134 | * @notice Internal function to decode a bytes32 sample into a uint20 using an offset 135 | * @dev This function can overflow 136 | * @param encoded The encoded value 137 | * @param offset The offset 138 | * @return value The decoded value as a uint24, since uint20 is not supported 139 | */ 140 | function decodeUint20(bytes32 encoded, uint256 offset) internal pure returns (uint24 value) { 141 | assembly { 142 | value := and(shr(offset, encoded), MASK_UINT20) 143 | } 144 | } 145 | 146 | /** 147 | * @notice Internal function to decode a bytes32 sample into a uint24 using an offset 148 | * @dev This function can overflow 149 | * @param encoded The encoded value 150 | * @param offset The offset 151 | * @return value The decoded value 152 | */ 153 | function decodeUint24(bytes32 encoded, uint256 offset) internal pure returns (uint24 value) { 154 | assembly { 155 | value := and(shr(offset, encoded), MASK_UINT24) 156 | } 157 | } 158 | 159 | /** 160 | * @notice Internal function to decode a bytes32 sample into a uint40 using an offset 161 | * @dev This function can overflow 162 | * @param encoded The encoded value 163 | * @param offset The offset 164 | * @return value The decoded value 165 | */ 166 | function decodeUint40(bytes32 encoded, uint256 offset) internal pure returns (uint40 value) { 167 | assembly { 168 | value := and(shr(offset, encoded), MASK_UINT40) 169 | } 170 | } 171 | 172 | /** 173 | * @notice Internal function to decode a bytes32 sample into a uint64 using an offset 174 | * @dev This function can overflow 175 | * @param encoded The encoded value 176 | * @param offset The offset 177 | * @return value The decoded value 178 | */ 179 | function decodeUint64(bytes32 encoded, uint256 offset) internal pure returns (uint64 value) { 180 | assembly { 181 | value := and(shr(offset, encoded), MASK_UINT64) 182 | } 183 | } 184 | 185 | /** 186 | * @notice Internal function to decode a bytes32 sample into a uint128 using an offset 187 | * @dev This function can overflow 188 | * @param encoded The encoded value 189 | * @param offset The offset 190 | * @return value The decoded value 191 | */ 192 | function decodeUint128(bytes32 encoded, uint256 offset) internal pure returns (uint128 value) { 193 | assembly { 194 | value := and(shr(offset, encoded), MASK_UINT128) 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/libraries/math/LiquidityConfigurations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import {PackedUint128Math} from "./PackedUint128Math.sol"; 6 | import {Encoded} from "./Encoded.sol"; 7 | 8 | /** 9 | * @title Liquidity Book Liquidity Configurations Library 10 | * @author Trader Joe 11 | * @notice This library contains functions to encode and decode the config of a pool and interact with the encoded bytes32. 12 | */ 13 | library LiquidityConfigurations { 14 | using PackedUint128Math for bytes32; 15 | using PackedUint128Math for uint128; 16 | using Encoded for bytes32; 17 | 18 | error LiquidityConfigurations__InvalidConfig(); 19 | 20 | uint256 private constant OFFSET_ID = 0; 21 | uint256 private constant OFFSET_DISTRIBUTION_Y = 24; 22 | uint256 private constant OFFSET_DISTRIBUTION_X = 88; 23 | 24 | uint256 private constant PRECISION = 1e18; 25 | 26 | /** 27 | * @dev Encode the distributionX, distributionY and id into a single bytes32 28 | * @param distributionX The distribution of the first token 29 | * @param distributionY The distribution of the second token 30 | * @param id The id of the pool 31 | * @return config The encoded config as follows: 32 | * [0 - 24[: id 33 | * [24 - 88[: distributionY 34 | * [88 - 152[: distributionX 35 | * [152 - 256[: empty 36 | */ 37 | function encodeParams(uint64 distributionX, uint64 distributionY, uint24 id) 38 | internal 39 | pure 40 | returns (bytes32 config) 41 | { 42 | config = config.set(distributionX, Encoded.MASK_UINT64, OFFSET_DISTRIBUTION_X); 43 | config = config.set(distributionY, Encoded.MASK_UINT64, OFFSET_DISTRIBUTION_Y); 44 | config = config.set(id, Encoded.MASK_UINT24, OFFSET_ID); 45 | } 46 | 47 | /** 48 | * @dev Decode the distributionX, distributionY and id from a single bytes32 49 | * @param config The encoded config as follows: 50 | * [0 - 24[: id 51 | * [24 - 88[: distributionY 52 | * [88 - 152[: distributionX 53 | * [152 - 256[: empty 54 | * @return distributionX The distribution of the first token 55 | * @return distributionY The distribution of the second token 56 | * @return id The id of the bin to add the liquidity to 57 | */ 58 | function decodeParams(bytes32 config) 59 | internal 60 | pure 61 | returns (uint64 distributionX, uint64 distributionY, uint24 id) 62 | { 63 | distributionX = config.decodeUint64(OFFSET_DISTRIBUTION_X); 64 | distributionY = config.decodeUint64(OFFSET_DISTRIBUTION_Y); 65 | id = config.decodeUint24(OFFSET_ID); 66 | 67 | if (uint256(config) > type(uint152).max || distributionX > PRECISION || distributionY > PRECISION) { 68 | revert LiquidityConfigurations__InvalidConfig(); 69 | } 70 | } 71 | 72 | /** 73 | * @dev Get the amounts and id from a config and amountsIn 74 | * @param config The encoded config as follows: 75 | * [0 - 24[: id 76 | * [24 - 88[: distributionY 77 | * [88 - 152[: distributionX 78 | * [152 - 256[: empty 79 | * @param amountsIn The amounts to distribute as follows: 80 | * [0 - 128[: x1 81 | * [128 - 256[: x2 82 | * @return amounts The distributed amounts as follows: 83 | * [0 - 128[: x1 84 | * [128 - 256[: x2 85 | * @return id The id of the bin to add the liquidity to 86 | */ 87 | function getAmountsAndId(bytes32 config, bytes32 amountsIn) internal pure returns (bytes32, uint24) { 88 | (uint64 distributionX, uint64 distributionY, uint24 id) = decodeParams(config); 89 | 90 | (uint128 x1, uint128 x2) = amountsIn.decode(); 91 | 92 | assembly { 93 | x1 := div(mul(x1, distributionX), PRECISION) 94 | x2 := div(mul(x2, distributionY), PRECISION) 95 | } 96 | 97 | return (x1.encode(x2), id); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/libraries/math/SampleMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import {Encoded} from "./Encoded.sol"; 6 | 7 | /** 8 | * @title Liquidity Book Sample Math Library 9 | * @author Trader Joe 10 | * @notice This library contains functions to encode and decode a sample into a single bytes32 11 | * and interact with the encoded bytes32 12 | * The sample is encoded as follows: 13 | * 0 - 16: oracle length (16 bits) 14 | * 16 - 80: cumulative id (64 bits) 15 | * 80 - 144: cumulative volatility accumulator (64 bits) 16 | * 144 - 208: cumulative bin crossed (64 bits) 17 | * 208 - 216: sample lifetime (8 bits) 18 | * 216 - 256: sample creation timestamp (40 bits) 19 | */ 20 | library SampleMath { 21 | using Encoded for bytes32; 22 | 23 | uint256 internal constant OFFSET_ORACLE_LENGTH = 0; 24 | uint256 internal constant OFFSET_CUMULATIVE_ID = 16; 25 | uint256 internal constant OFFSET_CUMULATIVE_VOLATILITY = 80; 26 | uint256 internal constant OFFSET_CUMULATIVE_BIN_CROSSED = 144; 27 | uint256 internal constant OFFSET_SAMPLE_LIFETIME = 208; 28 | uint256 internal constant OFFSET_SAMPLE_CREATION = 216; 29 | 30 | /** 31 | * @dev Encodes a sample 32 | * @param oracleLength The oracle length 33 | * @param cumulativeId The cumulative id 34 | * @param cumulativeVolatility The cumulative volatility 35 | * @param cumulativeBinCrossed The cumulative bin crossed 36 | * @param sampleLifetime The sample lifetime 37 | * @param createdAt The sample creation timestamp 38 | * @return sample The encoded sample 39 | */ 40 | function encode( 41 | uint16 oracleLength, 42 | uint64 cumulativeId, 43 | uint64 cumulativeVolatility, 44 | uint64 cumulativeBinCrossed, 45 | uint8 sampleLifetime, 46 | uint40 createdAt 47 | ) internal pure returns (bytes32 sample) { 48 | sample = sample.set(oracleLength, Encoded.MASK_UINT16, OFFSET_ORACLE_LENGTH); 49 | sample = sample.set(cumulativeId, Encoded.MASK_UINT64, OFFSET_CUMULATIVE_ID); 50 | sample = sample.set(cumulativeVolatility, Encoded.MASK_UINT64, OFFSET_CUMULATIVE_VOLATILITY); 51 | sample = sample.set(cumulativeBinCrossed, Encoded.MASK_UINT64, OFFSET_CUMULATIVE_BIN_CROSSED); 52 | sample = sample.set(sampleLifetime, Encoded.MASK_UINT8, OFFSET_SAMPLE_LIFETIME); 53 | sample = sample.set(createdAt, Encoded.MASK_UINT40, OFFSET_SAMPLE_CREATION); 54 | } 55 | 56 | /** 57 | * @dev Gets the oracle length from an encoded sample 58 | * @param sample The encoded sample as follows: 59 | * [0 - 16[: oracle length (16 bits) 60 | * [16 - 256[: any (240 bits) 61 | * @return length The oracle length 62 | */ 63 | function getOracleLength(bytes32 sample) internal pure returns (uint16 length) { 64 | return sample.decodeUint16(0); 65 | } 66 | 67 | /** 68 | * @dev Gets the cumulative id from an encoded sample 69 | * @param sample The encoded sample as follows: 70 | * [0 - 16[: any (16 bits) 71 | * [16 - 80[: cumulative id (64 bits) 72 | * [80 - 256[: any (176 bits) 73 | * @return id The cumulative id 74 | */ 75 | function getCumulativeId(bytes32 sample) internal pure returns (uint64 id) { 76 | return sample.decodeUint64(OFFSET_CUMULATIVE_ID); 77 | } 78 | 79 | /** 80 | * @dev Gets the cumulative volatility accumulator from an encoded sample 81 | * @param sample The encoded sample as follows: 82 | * [0 - 80[: any (80 bits) 83 | * [80 - 144[: cumulative volatility accumulator (64 bits) 84 | * [144 - 256[: any (112 bits) 85 | * @return volatilityAccumulator The cumulative volatility 86 | */ 87 | function getCumulativeVolatility(bytes32 sample) internal pure returns (uint64 volatilityAccumulator) { 88 | return sample.decodeUint64(OFFSET_CUMULATIVE_VOLATILITY); 89 | } 90 | 91 | /** 92 | * @dev Gets the cumulative bin crossed from an encoded sample 93 | * @param sample The encoded sample as follows: 94 | * [0 - 144[: any (144 bits) 95 | * [144 - 208[: cumulative bin crossed (64 bits) 96 | * [208 - 256[: any (48 bits) 97 | * @return binCrossed The cumulative bin crossed 98 | */ 99 | function getCumulativeBinCrossed(bytes32 sample) internal pure returns (uint64 binCrossed) { 100 | return sample.decodeUint64(OFFSET_CUMULATIVE_BIN_CROSSED); 101 | } 102 | 103 | /** 104 | * @dev Gets the sample lifetime from an encoded sample 105 | * @param sample The encoded sample as follows: 106 | * [0 - 208[: any (208 bits) 107 | * [208 - 216[: sample lifetime (8 bits) 108 | * [216 - 256[: any (40 bits) 109 | * @return lifetime The sample lifetime 110 | */ 111 | function getSampleLifetime(bytes32 sample) internal pure returns (uint8 lifetime) { 112 | return sample.decodeUint8(OFFSET_SAMPLE_LIFETIME); 113 | } 114 | 115 | /** 116 | * @dev Gets the sample creation timestamp from an encoded sample 117 | * @param sample The encoded sample as follows: 118 | * [0 - 216[: any (216 bits) 119 | * [216 - 256[: sample creation timestamp (40 bits) 120 | * @return creation The sample creation timestamp 121 | */ 122 | function getSampleCreation(bytes32 sample) internal pure returns (uint40 creation) { 123 | return sample.decodeUint40(OFFSET_SAMPLE_CREATION); 124 | } 125 | 126 | /** 127 | * @dev Gets the sample last update timestamp from an encoded sample 128 | * @param sample The encoded sample as follows: 129 | * [0 - 216[: any (216 bits) 130 | * [216 - 256[: sample creation timestamp (40 bits) 131 | * @return lastUpdate The sample last update timestamp 132 | */ 133 | function getSampleLastUpdate(bytes32 sample) internal pure returns (uint40 lastUpdate) { 134 | lastUpdate = getSampleCreation(sample) + getSampleLifetime(sample); 135 | } 136 | 137 | /** 138 | * @dev Gets the weighted average of two samples and their respective weights 139 | * @param sample1 The first encoded sample 140 | * @param sample2 The second encoded sample 141 | * @param weight1 The weight of the first sample 142 | * @param weight2 The weight of the second sample 143 | * @return weightedAverageId The weighted average id 144 | * @return weightedAverageVolatility The weighted average volatility 145 | * @return weightedAverageBinCrossed The weighted average bin crossed 146 | */ 147 | function getWeightedAverage(bytes32 sample1, bytes32 sample2, uint40 weight1, uint40 weight2) 148 | internal 149 | pure 150 | returns (uint64 weightedAverageId, uint64 weightedAverageVolatility, uint64 weightedAverageBinCrossed) 151 | { 152 | uint256 cId1 = getCumulativeId(sample1); 153 | uint256 cVolatility1 = getCumulativeVolatility(sample1); 154 | uint256 cBinCrossed1 = getCumulativeBinCrossed(sample1); 155 | 156 | if (weight2 == 0) return (uint64(cId1), uint64(cVolatility1), uint64(cBinCrossed1)); 157 | 158 | uint256 cId2 = getCumulativeId(sample2); 159 | uint256 cVolatility2 = getCumulativeVolatility(sample2); 160 | uint256 cBinCrossed2 = getCumulativeBinCrossed(sample2); 161 | 162 | if (weight1 == 0) return (uint64(cId2), uint64(cVolatility2), uint64(cBinCrossed2)); 163 | 164 | uint256 totalWeight = uint256(weight1) + weight2; 165 | 166 | unchecked { 167 | weightedAverageId = uint64((cId1 * weight1 + cId2 * weight2) / totalWeight); 168 | weightedAverageVolatility = uint64((cVolatility1 * weight1 + cVolatility2 * weight2) / totalWeight); 169 | weightedAverageBinCrossed = uint64((cBinCrossed1 * weight1 + cBinCrossed2 * weight2) / totalWeight); 170 | } 171 | } 172 | 173 | /** 174 | * @dev Updates a sample with the given values 175 | * @param sample The encoded sample 176 | * @param deltaTime The time elapsed since the last update 177 | * @param activeId The active id 178 | * @param volatilityAccumulator The volatility accumulator 179 | * @param binCrossed The bin crossed 180 | * @return cumulativeId The cumulative id 181 | * @return cumulativeVolatility The cumulative volatility 182 | * @return cumulativeBinCrossed The cumulative bin crossed 183 | */ 184 | function update(bytes32 sample, uint40 deltaTime, uint24 activeId, uint24 volatilityAccumulator, uint24 binCrossed) 185 | internal 186 | pure 187 | returns (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) 188 | { 189 | unchecked { 190 | cumulativeId = uint64(activeId) * deltaTime; 191 | cumulativeVolatility = uint64(volatilityAccumulator) * deltaTime; 192 | cumulativeBinCrossed = uint64(binCrossed) * deltaTime; 193 | } 194 | 195 | cumulativeId += getCumulativeId(sample); 196 | cumulativeVolatility += getCumulativeVolatility(sample); 197 | cumulativeBinCrossed += getCumulativeBinCrossed(sample); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/libraries/math/TreeMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import {BitMath} from "./BitMath.sol"; 6 | 7 | /** 8 | * @title Liquidity Book Tree Math Library 9 | * @author Trader Joe 10 | * @notice This library contains functions to interact with a tree of TreeUint24. 11 | */ 12 | library TreeMath { 13 | using BitMath for uint256; 14 | 15 | struct TreeUint24 { 16 | bytes32 level0; 17 | mapping(bytes32 => bytes32) level1; 18 | mapping(bytes32 => bytes32) level2; 19 | } 20 | 21 | /** 22 | * @dev Returns true if the tree contains the id 23 | * @param tree The tree 24 | * @param id The id 25 | * @return True if the tree contains the id 26 | */ 27 | function contains(TreeUint24 storage tree, uint24 id) internal view returns (bool) { 28 | bytes32 leaf2 = bytes32(uint256(id) >> 8); 29 | 30 | return tree.level2[leaf2] & bytes32(1 << (id & type(uint8).max)) != 0; 31 | } 32 | 33 | /** 34 | * @dev Adds the id to the tree and returns true if the id was not already in the tree 35 | * It will also propagate the change to the parent levels. 36 | * @param tree The tree 37 | * @param id The id 38 | * @return True if the id was not already in the tree 39 | */ 40 | function add(TreeUint24 storage tree, uint24 id) internal returns (bool) { 41 | bytes32 key2 = bytes32(uint256(id) >> 8); 42 | 43 | bytes32 leaves = tree.level2[key2]; 44 | bytes32 newLeaves = leaves | bytes32(1 << (id & type(uint8).max)); 45 | 46 | if (leaves != newLeaves) { 47 | tree.level2[key2] = newLeaves; 48 | 49 | if (leaves == 0) { 50 | bytes32 key1 = key2 >> 8; 51 | leaves = tree.level1[key1]; 52 | 53 | tree.level1[key1] = leaves | bytes32(1 << (uint256(key2) & type(uint8).max)); 54 | 55 | if (leaves == 0) tree.level0 |= bytes32(1 << (uint256(key1) & type(uint8).max)); 56 | } 57 | 58 | return true; 59 | } 60 | 61 | return false; 62 | } 63 | 64 | /** 65 | * @dev Removes the id from the tree and returns true if the id was in the tree. 66 | * It will also propagate the change to the parent levels. 67 | * @param tree The tree 68 | * @param id The id 69 | * @return True if the id was in the tree 70 | */ 71 | function remove(TreeUint24 storage tree, uint24 id) internal returns (bool) { 72 | bytes32 key2 = bytes32(uint256(id) >> 8); 73 | 74 | bytes32 leaves = tree.level2[key2]; 75 | bytes32 newLeaves = leaves & ~bytes32(1 << (id & type(uint8).max)); 76 | 77 | if (leaves != newLeaves) { 78 | tree.level2[key2] = newLeaves; 79 | 80 | if (newLeaves == 0) { 81 | bytes32 key1 = key2 >> 8; 82 | newLeaves = tree.level1[key1] & ~bytes32(1 << (uint256(key2) & type(uint8).max)); 83 | 84 | tree.level1[key1] = newLeaves; 85 | 86 | if (newLeaves == 0) tree.level0 &= ~bytes32(1 << (uint256(key1) & type(uint8).max)); 87 | } 88 | 89 | return true; 90 | } 91 | 92 | return false; 93 | } 94 | 95 | /** 96 | * @dev Returns the first id in the tree that is lower than or equal to the given id. 97 | * It will return type(uint24).max if there is no such id. 98 | * @param tree The tree 99 | * @param id The id 100 | * @return The first id in the tree that is lower than the given id 101 | */ 102 | function findFirstRight(TreeUint24 storage tree, uint24 id) internal view returns (uint24) { 103 | bytes32 leaves; 104 | 105 | bytes32 key2 = bytes32(uint256(id) >> 8); 106 | uint8 bit = uint8(id & type(uint8).max); 107 | 108 | if (bit != 0) { 109 | leaves = tree.level2[key2]; 110 | uint256 closestBit = _closestBitRight(leaves, bit); 111 | 112 | if (closestBit != type(uint256).max) return uint24(uint256(key2) << 8 | closestBit); 113 | } 114 | 115 | bytes32 key1 = key2 >> 8; 116 | bit = uint8(uint256(key2) & type(uint8).max); 117 | 118 | if (bit != 0) { 119 | leaves = tree.level1[key1]; 120 | uint256 closestBit = _closestBitRight(leaves, bit); 121 | 122 | if (closestBit != type(uint256).max) { 123 | key2 = bytes32(uint256(key1) << 8 | closestBit); 124 | leaves = tree.level2[key2]; 125 | 126 | return uint24(uint256(key2) << 8 | uint256(leaves).mostSignificantBit()); 127 | } 128 | } 129 | 130 | bit = uint8(uint256(key1) & type(uint8).max); 131 | 132 | if (bit != 0) { 133 | leaves = tree.level0; 134 | uint256 closestBit = _closestBitRight(leaves, bit); 135 | 136 | if (closestBit != type(uint256).max) { 137 | key1 = bytes32(closestBit); 138 | leaves = tree.level1[key1]; 139 | 140 | key2 = bytes32(uint256(key1) << 8 | uint256(leaves).mostSignificantBit()); 141 | leaves = tree.level2[key2]; 142 | 143 | return uint24(uint256(key2) << 8 | uint256(leaves).mostSignificantBit()); 144 | } 145 | } 146 | 147 | return type(uint24).max; 148 | } 149 | 150 | /** 151 | * @dev Returns the first id in the tree that is higher than or equal to the given id. 152 | * It will return 0 if there is no such id. 153 | * @param tree The tree 154 | * @param id The id 155 | * @return The first id in the tree that is higher than the given id 156 | */ 157 | function findFirstLeft(TreeUint24 storage tree, uint24 id) internal view returns (uint24) { 158 | bytes32 leaves; 159 | 160 | bytes32 key2 = bytes32(uint256(id) >> 8); 161 | uint8 bit = uint8(id & type(uint8).max); 162 | 163 | if (bit != type(uint8).max) { 164 | leaves = tree.level2[key2]; 165 | uint256 closestBit = _closestBitLeft(leaves, bit); 166 | 167 | if (closestBit != type(uint256).max) return uint24(uint256(key2) << 8 | closestBit); 168 | } 169 | 170 | bytes32 key1 = key2 >> 8; 171 | bit = uint8(uint256(key2) & type(uint8).max); 172 | 173 | if (bit != type(uint8).max) { 174 | leaves = tree.level1[key1]; 175 | uint256 closestBit = _closestBitLeft(leaves, bit); 176 | 177 | if (closestBit != type(uint256).max) { 178 | key2 = bytes32(uint256(key1) << 8 | closestBit); 179 | leaves = tree.level2[key2]; 180 | 181 | return uint24(uint256(key2) << 8 | uint256(leaves).leastSignificantBit()); 182 | } 183 | } 184 | 185 | bit = uint8(uint256(key1) & type(uint8).max); 186 | 187 | if (bit != type(uint8).max) { 188 | leaves = tree.level0; 189 | uint256 closestBit = _closestBitLeft(leaves, bit); 190 | 191 | if (closestBit != type(uint256).max) { 192 | key1 = bytes32(closestBit); 193 | leaves = tree.level1[key1]; 194 | 195 | key2 = bytes32(uint256(key1) << 8 | uint256(leaves).leastSignificantBit()); 196 | leaves = tree.level2[key2]; 197 | 198 | return uint24(uint256(key2) << 8 | uint256(leaves).leastSignificantBit()); 199 | } 200 | } 201 | 202 | return 0; 203 | } 204 | 205 | /** 206 | * @dev Returns the first bit in the given leaves that is strictly lower than the given bit. 207 | * It will return type(uint256).max if there is no such bit. 208 | * @param leaves The leaves 209 | * @param bit The bit 210 | * @return The first bit in the given leaves that is strictly lower than the given bit 211 | */ 212 | function _closestBitRight(bytes32 leaves, uint8 bit) private pure returns (uint256) { 213 | unchecked { 214 | return uint256(leaves).closestBitRight(bit - 1); 215 | } 216 | } 217 | 218 | /** 219 | * @dev Returns the first bit in the given leaves that is strictly higher than the given bit. 220 | * It will return type(uint256).max if there is no such bit. 221 | * @param leaves The leaves 222 | * @param bit The bit 223 | * @return The first bit in the given leaves that is strictly higher than the given bit 224 | */ 225 | function _closestBitLeft(bytes32 leaves, uint8 bit) private pure returns (uint256) { 226 | unchecked { 227 | return uint256(leaves).closestBitLeft(bit + 1); 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/libraries/math/Uint128x128Math.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import {Constants} from "../Constants.sol"; 6 | import {BitMath} from "./BitMath.sol"; 7 | 8 | /** 9 | * @title Liquidity Book Uint128x128 Math Library 10 | * @author Trader Joe 11 | * @notice Helper contract used for power and log calculations 12 | */ 13 | library Uint128x128Math { 14 | using BitMath for uint256; 15 | 16 | error Uint128x128Math__LogUnderflow(); 17 | error Uint128x128Math__PowUnderflow(uint256 x, int256 y); 18 | 19 | uint256 constant LOG_SCALE_OFFSET = 127; 20 | uint256 constant LOG_SCALE = 1 << LOG_SCALE_OFFSET; 21 | uint256 constant LOG_SCALE_SQUARED = LOG_SCALE * LOG_SCALE; 22 | 23 | /** 24 | * @notice Calculates the binary logarithm of x. 25 | * @dev Based on the iterative approximation algorithm. 26 | * https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation 27 | * Requirements: 28 | * - x must be greater than zero. 29 | * Caveats: 30 | * - The results are not perfectly accurate to the last decimal, due to the lossy precision of the iterative approximation 31 | * Also because x is converted to an unsigned 129.127-binary fixed-point number during the operation to optimize the multiplication 32 | * @param x The unsigned 128.128-binary fixed-point number for which to calculate the binary logarithm. 33 | * @return result The binary logarithm as a signed 128.128-binary fixed-point number. 34 | */ 35 | function log2(uint256 x) internal pure returns (int256 result) { 36 | // Convert x to a unsigned 129.127-binary fixed-point number to optimize the multiplication. 37 | // If we use an offset of 128 bits, y would need 129 bits and y**2 would would overflow and we would have to 38 | // use mulDiv, by reducing x to 129.127-binary fixed-point number we assert that y will use 128 bits, and we 39 | // can use the regular multiplication 40 | 41 | if (x == 1) return -128; 42 | if (x == 0) revert Uint128x128Math__LogUnderflow(); 43 | 44 | x >>= 1; 45 | 46 | unchecked { 47 | // This works because log2(x) = -log2(1/x). 48 | int256 sign; 49 | if (x >= LOG_SCALE) { 50 | sign = 1; 51 | } else { 52 | sign = -1; 53 | // Do the fixed-point inversion inline to save gas 54 | x = LOG_SCALE_SQUARED / x; 55 | } 56 | 57 | // Calculate the integer part of the logarithm and add it to the result and finally calculate y = x * 2^(-n). 58 | uint256 n = (x >> LOG_SCALE_OFFSET).mostSignificantBit(); 59 | 60 | // The integer part of the logarithm as a signed 129.127-binary fixed-point number. The operation can't overflow 61 | // because n is maximum 255, LOG_SCALE_OFFSET is 127 bits and sign is either 1 or -1. 62 | result = int256(n) << LOG_SCALE_OFFSET; 63 | 64 | // This is y = x * 2^(-n). 65 | uint256 y = x >> n; 66 | 67 | // If y = 1, the fractional part is zero. 68 | if (y != LOG_SCALE) { 69 | // Calculate the fractional part via the iterative approximation. 70 | // The "delta >>= 1" part is equivalent to "delta /= 2", but shifting bits is faster. 71 | for (int256 delta = int256(1 << (LOG_SCALE_OFFSET - 1)); delta > 0; delta >>= 1) { 72 | y = (y * y) >> LOG_SCALE_OFFSET; 73 | 74 | // Is y^2 > 2 and so in the range [2,4)? 75 | if (y >= 1 << (LOG_SCALE_OFFSET + 1)) { 76 | // Add the 2^(-m) factor to the logarithm. 77 | result += delta; 78 | 79 | // Corresponds to z/2 on Wikipedia. 80 | y >>= 1; 81 | } 82 | } 83 | } 84 | // Convert x back to unsigned 128.128-binary fixed-point number 85 | result = (result * sign) << 1; 86 | } 87 | } 88 | 89 | /** 90 | * @notice Returns the value of x^y. It calculates `1 / x^abs(y)` if x is bigger than 2^128. 91 | * At the end of the operations, we invert the result if needed. 92 | * @param x The unsigned 128.128-binary fixed-point number for which to calculate the power 93 | * @param y A relative number without any decimals, needs to be between ]-2^21; 2^21[ 94 | */ 95 | function pow(uint256 x, int256 y) internal pure returns (uint256 result) { 96 | bool invert; 97 | uint256 absY; 98 | 99 | if (y == 0) return Constants.SCALE; 100 | 101 | assembly { 102 | absY := y 103 | if slt(absY, 0) { 104 | absY := sub(0, absY) 105 | invert := iszero(invert) 106 | } 107 | } 108 | 109 | if (absY < 0x100000) { 110 | result = Constants.SCALE; 111 | assembly { 112 | let squared := x 113 | if gt(x, 0xffffffffffffffffffffffffffffffff) { 114 | squared := div(not(0), squared) 115 | invert := iszero(invert) 116 | } 117 | 118 | if and(absY, 0x1) { result := shr(128, mul(result, squared)) } 119 | squared := shr(128, mul(squared, squared)) 120 | if and(absY, 0x2) { result := shr(128, mul(result, squared)) } 121 | squared := shr(128, mul(squared, squared)) 122 | if and(absY, 0x4) { result := shr(128, mul(result, squared)) } 123 | squared := shr(128, mul(squared, squared)) 124 | if and(absY, 0x8) { result := shr(128, mul(result, squared)) } 125 | squared := shr(128, mul(squared, squared)) 126 | if and(absY, 0x10) { result := shr(128, mul(result, squared)) } 127 | squared := shr(128, mul(squared, squared)) 128 | if and(absY, 0x20) { result := shr(128, mul(result, squared)) } 129 | squared := shr(128, mul(squared, squared)) 130 | if and(absY, 0x40) { result := shr(128, mul(result, squared)) } 131 | squared := shr(128, mul(squared, squared)) 132 | if and(absY, 0x80) { result := shr(128, mul(result, squared)) } 133 | squared := shr(128, mul(squared, squared)) 134 | if and(absY, 0x100) { result := shr(128, mul(result, squared)) } 135 | squared := shr(128, mul(squared, squared)) 136 | if and(absY, 0x200) { result := shr(128, mul(result, squared)) } 137 | squared := shr(128, mul(squared, squared)) 138 | if and(absY, 0x400) { result := shr(128, mul(result, squared)) } 139 | squared := shr(128, mul(squared, squared)) 140 | if and(absY, 0x800) { result := shr(128, mul(result, squared)) } 141 | squared := shr(128, mul(squared, squared)) 142 | if and(absY, 0x1000) { result := shr(128, mul(result, squared)) } 143 | squared := shr(128, mul(squared, squared)) 144 | if and(absY, 0x2000) { result := shr(128, mul(result, squared)) } 145 | squared := shr(128, mul(squared, squared)) 146 | if and(absY, 0x4000) { result := shr(128, mul(result, squared)) } 147 | squared := shr(128, mul(squared, squared)) 148 | if and(absY, 0x8000) { result := shr(128, mul(result, squared)) } 149 | squared := shr(128, mul(squared, squared)) 150 | if and(absY, 0x10000) { result := shr(128, mul(result, squared)) } 151 | squared := shr(128, mul(squared, squared)) 152 | if and(absY, 0x20000) { result := shr(128, mul(result, squared)) } 153 | squared := shr(128, mul(squared, squared)) 154 | if and(absY, 0x40000) { result := shr(128, mul(result, squared)) } 155 | squared := shr(128, mul(squared, squared)) 156 | if and(absY, 0x80000) { result := shr(128, mul(result, squared)) } 157 | } 158 | } 159 | 160 | // revert if y is too big or if x^y underflowed 161 | if (result == 0) revert Uint128x128Math__PowUnderflow(x, y); 162 | 163 | return invert ? type(uint256).max / result : result; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /test/GetOracleLength.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "./helpers/TestHelper.sol"; 6 | 7 | import {LBFactory, LBFactory} from "src/LBFactory.sol"; 8 | import {ILBRouter, IJoeFactory, ILBLegacyFactory, ILBLegacyRouter, IWNATIVE, LBRouter} from "src/LBRouter.sol"; 9 | import {IERC20, LBPair} from "src/LBPair.sol"; 10 | import {LBQuoter} from "src/LBQuoter.sol"; 11 | 12 | contract GetOracleLengthTest is Test { 13 | LBFactory public constant factoryAvax = LBFactory(0x8e42f2F4101563bF679975178e880FD87d3eFd4e); 14 | LBFactory public constant factoryArbitrum = LBFactory(0x8e42f2F4101563bF679975178e880FD87d3eFd4e); 15 | LBFactory public constant factoryBsc = LBFactory(0x8e42f2F4101563bF679975178e880FD87d3eFd4e); 16 | LBFactory public constant factoryEth = LBFactory(0xDC8d77b69155c7E68A95a4fb0f06a71FF90B943a); 17 | 18 | string[] chains = ["avalanche", "arbitrum_one", "bnb_smart_chain", "mainnet"]; 19 | mapping(string => uint256) public forks; 20 | 21 | function setUp() public { 22 | for (uint256 i = 0; i < chains.length; i++) { 23 | string memory chain = chains[i]; 24 | 25 | forks[chain] = vm.createFork(_getRPC(chain)); 26 | } 27 | } 28 | 29 | // function test_size_avalanche() public { 30 | // get_size("avalanche"); 31 | // } 32 | 33 | // function test_size_arbitrum() public { 34 | // get_size("arbitrum_one"); 35 | // } 36 | 37 | // function test_size_bsc() public { 38 | // get_size("bnb_smart_chain"); 39 | // } 40 | 41 | // function test_size_eth() public { 42 | // get_size("mainnet"); 43 | // } 44 | 45 | function get_size(string memory chain) public { 46 | vm.selectFork(forks[chain]); 47 | 48 | LBFactory factory = _getLBFactories(chain); 49 | 50 | GetAllSizeAndAddress s = new GetAllSizeAndAddress(); 51 | 52 | (address[] memory pairs, uint256[] memory sizes) = s.getAllSizeAndAddress(factory); 53 | 54 | uint256 atRisk = 0; 55 | 56 | for (uint256 j = 0; j < pairs.length; j++) { 57 | address pair = pairs[j]; 58 | uint256 size = sizes[j]; 59 | 60 | if (size > 0) { 61 | atRisk++; 62 | console.log("Pair %s has size %s", pair, size); 63 | } 64 | } 65 | 66 | console.log("There are %s/%s pairs at risk on %s", atRisk, pairs.length, chain); 67 | } 68 | 69 | function _getRPC(string memory chain) internal returns (string memory) { 70 | // if (keccak256(abi.encodePacked(chain)) == keccak256(abi.encodePacked("avalanche"))) { 71 | // return "https://rpc.ankr.com/avalanche"; 72 | // } else if (keccak256(abi.encodePacked(chain)) == keccak256(abi.encodePacked("arbitrum_one"))) { 73 | // return "https://rpc.ankr.com/arbitrum"; 74 | // } else if (keccak256(abi.encodePacked(chain)) == keccak256(abi.encodePacked("bnb_smart_chain"))) { 75 | // return "https://rpc.ankr.com/bsc"; 76 | // } else if (keccak256(abi.encodePacked(chain)) == keccak256(abi.encodePacked("mainnet"))) { 77 | // return "https://rpc.ankr.com/eth"; 78 | // } 79 | return StdChains.getChain(chain).rpcUrl; 80 | } 81 | 82 | function _getLBFactories(string memory chain) internal pure returns (LBFactory) { 83 | if (keccak256(abi.encodePacked(chain)) == keccak256(abi.encodePacked("avalanche"))) { 84 | return factoryAvax; 85 | } else if (keccak256(abi.encodePacked(chain)) == keccak256(abi.encodePacked("arbitrum_one"))) { 86 | return factoryArbitrum; 87 | } else if (keccak256(abi.encodePacked(chain)) == keccak256(abi.encodePacked("bnb_smart_chain"))) { 88 | return factoryBsc; 89 | } else if (keccak256(abi.encodePacked(chain)) == keccak256(abi.encodePacked("mainnet"))) { 90 | return factoryEth; 91 | } else { 92 | revert("Invalid chain"); 93 | } 94 | } 95 | } 96 | 97 | contract GetAllSizeAndAddress { 98 | function getAllSizeAndAddress(LBFactory factory) 99 | external 100 | view 101 | returns (address[] memory pairs, uint256[] memory size) 102 | { 103 | uint256 nbPairs = factory.getNumberOfLBPairs(); 104 | 105 | pairs = new address[](nbPairs); 106 | size = new uint256[](nbPairs); 107 | 108 | for (uint256 i = 0; i < nbPairs; i++) { 109 | LBPair pair = LBPair(address(factory.getLBPairAtIndex(i))); 110 | 111 | (, uint16 _size,,,) = pair.getOracleParameters(); 112 | 113 | pairs[i] = address(pair); 114 | size[i] = _size; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /test/LBPairFlashloan.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "./helpers/TestHelper.sol"; 6 | 7 | import "./mocks/FlashBorrower.sol"; 8 | 9 | contract LBPairFlashloanTest is TestHelper { 10 | using SafeCast for uint256; 11 | using PackedUint128Math for bytes32; 12 | using PackedUint128Math for uint128; 13 | 14 | FlashBorrower borrower; 15 | 16 | function setUp() public override { 17 | super.setUp(); 18 | 19 | pairWnative = createLBPair(wnative, usdc); 20 | 21 | addLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 1e18, 50, 50); 22 | 23 | borrower = new FlashBorrower(pairWnative); 24 | 25 | // Make sure the borrower can pay back the flash loan 26 | deal(address(wnative), address(borrower), 1e18); 27 | deal(address(usdc), address(borrower), 1e18); 28 | } 29 | 30 | function testFuzz_FlashLoan(uint128 amountX, uint128 amountY) external { 31 | vm.assume(amountX <= 1e18 && amountY <= 1e18 && (amountX > 0 || amountY > 0)); 32 | 33 | bytes32 amountsBorrowed = amountX.encode(amountY); 34 | bytes memory data = abi.encode(type(uint128).max, type(uint128).max, Constants.CALLBACK_SUCCESS, 0); 35 | 36 | uint256 balanceX = wnative.balanceOf(address(pairWnative)); 37 | uint256 balanceY = usdc.balanceOf(address(pairWnative)); 38 | 39 | uint256 flashLoanFee = factory.getFlashLoanFee(); 40 | 41 | uint256 feeX = (amountX * flashLoanFee + 1e18 - 1) / 1e18; 42 | uint256 feeY = (amountY * flashLoanFee + 1e18 - 1) / 1e18; 43 | 44 | pairWnative.flashLoan(borrower, amountsBorrowed, data); 45 | 46 | assertEq(wnative.balanceOf(address(pairWnative)), balanceX + feeX, "testFuzz_FlashLoan::1"); 47 | assertEq(usdc.balanceOf(address(pairWnative)), balanceY + feeY, "testFuzz_FlashLoan::2"); 48 | 49 | (uint256 reserveX, uint256 reserveY) = pairWnative.getReserves(); 50 | (uint256 protocolFeeX, uint256 protocolFeeY) = pairWnative.getProtocolFees(); 51 | 52 | assertEq(reserveX + protocolFeeX, balanceX + feeX, "testFuzz_FlashLoan::3"); 53 | assertEq(reserveY + protocolFeeY, balanceY + feeY, "testFuzz_FlashLoan::4"); 54 | } 55 | 56 | function testFuzz_revert_FlashLoanInsufficientAmount(uint128 amountX, uint128 amountY) external { 57 | vm.assume(amountX > 0 && amountY > 0 && amountX <= 1e18 && amountY <= 1e18); 58 | 59 | uint256 flashLoanFee = factory.getFlashLoanFee(); 60 | 61 | uint256 feeX = (amountX * flashLoanFee + 1e18 - 1) / 1e18; 62 | uint256 feeY = (amountY * flashLoanFee + 1e18 - 1) / 1e18; 63 | 64 | bytes32 amountsBorrowed = amountX.encode(amountY); 65 | bytes memory data = abi.encode(amountX + feeX - 1, amountY + feeY, Constants.CALLBACK_SUCCESS, 0); 66 | 67 | vm.expectRevert(ILBPair.LBPair__FlashLoanInsufficientAmount.selector); 68 | pairWnative.flashLoan(borrower, amountsBorrowed, data); 69 | 70 | data = abi.encode(amountX + feeX, amountY + feeY - 1, Constants.CALLBACK_SUCCESS, 0); 71 | 72 | vm.expectRevert(ILBPair.LBPair__FlashLoanInsufficientAmount.selector); 73 | pairWnative.flashLoan(borrower, amountsBorrowed, data); 74 | 75 | data = abi.encode(amountX + feeX - 1, amountY + feeY - 1, Constants.CALLBACK_SUCCESS, 0); 76 | 77 | vm.expectRevert(ILBPair.LBPair__FlashLoanInsufficientAmount.selector); 78 | pairWnative.flashLoan(borrower, amountsBorrowed, data); 79 | } 80 | 81 | function testFuzz_revert_FlashLoanCallbackFailed(bytes32 callback) external { 82 | vm.assume(callback != Constants.CALLBACK_SUCCESS); 83 | 84 | bytes32 amountsBorrowed = bytes32(uint256(1)); 85 | bytes memory data = abi.encode(0, 0, callback, 0); 86 | 87 | vm.expectRevert(ILBPair.LBPair__FlashLoanCallbackFailed.selector); 88 | pairWnative.flashLoan(borrower, amountsBorrowed, data); 89 | } 90 | 91 | function testFuzz_revert_FlashLoanReentrant(bytes32 callback) external { 92 | bytes32 amountsBorrowed = bytes32(uint256(1)); 93 | bytes memory data = abi.encode(0, 0, callback, 1); 94 | 95 | vm.expectRevert(ILBPair.LBPair__FlashLoanCallbackFailed.selector); 96 | pairWnative.flashLoan(borrower, amountsBorrowed, data); 97 | } 98 | 99 | function test_revert_FlashLoan0Amounts() external { 100 | vm.expectRevert(ILBPair.LBPair__ZeroBorrowAmount.selector); 101 | pairWnative.flashLoan(borrower, 0, ""); 102 | } 103 | 104 | function test_EdgeCaseFlashLoanBinDOS() external { 105 | // remove the active liquidity 106 | removeLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 1, 1); 107 | 108 | bytes32 amountsBorrowed = uint128(1e17).encode(uint128(1e17)); 109 | bytes memory data = abi.encode(type(uint128).max, type(uint128).max, Constants.CALLBACK_SUCCESS, 0); 110 | 111 | pairWnative.flashLoan(borrower, amountsBorrowed, data); 112 | 113 | (uint256 x, uint256 y) = pairWnative.getBin(ID_ONE); 114 | 115 | assertEq(x, 0, "test_EdgeCaseFlashLoanBinDOS::1"); 116 | assertEq(y, 0, "test_EdgeCaseFlashLoanBinDOS::2"); 117 | 118 | addLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 1e18, 1, 1); 119 | 120 | assertEq(pairWnative.getNextNonEmptyBin(true, ID_ONE + 1), ID_ONE, "test_EdgeCaseFlashLoanBinDOS::3"); 121 | assertEq(pairWnative.getNextNonEmptyBin(false, ID_ONE - 1), ID_ONE, "test_EdgeCaseFlashLoanBinDOS::4"); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /test/LBPairImplementation.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; 8 | 9 | import "../src/LBPair.sol"; 10 | import "../src/libraries/ImmutableClone.sol"; 11 | 12 | contract LBPairImplementationTest is Test { 13 | address factory; 14 | address implementation; 15 | 16 | function setUp() public { 17 | factory = makeAddr("factory"); 18 | implementation = address(new LBPair(ILBFactory(factory))); 19 | } 20 | 21 | function testFuzz_Getters(address tokenX, address tokenY, uint16 binStep) public { 22 | bytes32 salt = keccak256(abi.encodePacked(tokenX, tokenY, binStep)); 23 | bytes memory data = abi.encodePacked(tokenX, tokenY, binStep); 24 | 25 | LBPair pair = LBPair(ImmutableClone.cloneDeterministic(implementation, data, salt)); 26 | 27 | assertEq(address(pair.getTokenX()), tokenX, "testFuzz_Getters::1"); 28 | assertEq(address(pair.getTokenY()), tokenY, "testFuzz_Getters::2"); 29 | assertEq(pair.getBinStep(), binStep, "testFuzz_Getters::3"); 30 | } 31 | 32 | function testFuzz_revert_InitializeImplementation() public { 33 | vm.expectRevert(ILBPair.LBPair__OnlyFactory.selector); 34 | LBPair(implementation).initialize(1, 1, 1, 1, 1, 1, 1, 1); 35 | 36 | vm.expectRevert(Initializable.InvalidInitialization.selector); 37 | vm.prank(address(factory)); 38 | LBPair(implementation).initialize(1, 1, 1, 1, 1, 1, 1, 1); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/LBPairSwap.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "./helpers/TestHelper.sol"; 6 | 7 | contract LBPairSwapTest is TestHelper { 8 | using SafeCast for uint256; 9 | 10 | function setUp() public override { 11 | super.setUp(); 12 | 13 | pairWnative = createLBPair(wnative, usdc); 14 | 15 | addLiquidity(DEV, DEV, pairWnative, ID_ONE, 1e18, 1e18, 50, 50); 16 | } 17 | 18 | function testFuzz_SwapInForY(uint128 amountOut) public { 19 | vm.assume(amountOut > 0 && amountOut < 1e18); 20 | 21 | (uint128 amountIn, uint128 amountOutLeft,) = pairWnative.getSwapIn(amountOut, true); 22 | 23 | assertEq(amountOutLeft, 0, "testFuzz_SwapInForY::1"); 24 | 25 | deal(address(wnative), ALICE, amountIn); 26 | 27 | vm.startPrank(ALICE); 28 | wnative.transfer(address(pairWnative), amountIn); 29 | pairWnative.swap(true, ALICE); 30 | vm.stopPrank(); 31 | 32 | assertEq(wnative.balanceOf(ALICE), 0, "testFuzz_SwapInForY::2"); 33 | assertEq(usdc.balanceOf(ALICE), amountOut, "testFuzz_SwapInForY::3"); 34 | } 35 | 36 | function testFuzz_SwapInForX(uint128 amountOut) public { 37 | vm.assume(amountOut > 0 && amountOut < 1e18); 38 | 39 | (uint128 amountIn, uint128 amountOutLeft,) = pairWnative.getSwapIn(amountOut, false); 40 | 41 | assertEq(amountOutLeft, 0, "testFuzz_SwapInForX::1"); 42 | 43 | deal(address(usdc), ALICE, amountIn); 44 | 45 | vm.startPrank(ALICE); 46 | usdc.transfer(address(pairWnative), amountIn); 47 | pairWnative.swap(false, ALICE); 48 | vm.stopPrank(); 49 | 50 | assertEq(usdc.balanceOf(ALICE), 0, "testFuzz_SwapInForX::2"); 51 | assertEq(wnative.balanceOf(ALICE), amountOut, "testFuzz_SwapInForX::3"); 52 | } 53 | 54 | function testFuzz_SwapOutForY(uint128 amountIn) public { 55 | vm.assume(amountIn > 0 && amountIn <= 1e18); 56 | 57 | (uint128 amountInLeft, uint128 amountOut,) = pairWnative.getSwapOut(amountIn, true); 58 | 59 | vm.assume(amountOut > 0); 60 | 61 | assertEq(amountInLeft, 0, "testFuzz_SwapOutForY::1"); 62 | 63 | deal(address(wnative), ALICE, amountIn); 64 | 65 | vm.startPrank(ALICE); 66 | wnative.transfer(address(pairWnative), amountIn); 67 | pairWnative.swap(true, ALICE); 68 | vm.stopPrank(); 69 | 70 | assertEq(wnative.balanceOf(ALICE), 0, "testFuzz_SwapOutForY::2"); 71 | assertEq(usdc.balanceOf(ALICE), amountOut, "testFuzz_SwapOutForY::3"); 72 | } 73 | 74 | function testFuzz_SwapOutForX(uint128 amountIn) public { 75 | vm.assume(amountIn > 0 && amountIn <= 1e18); 76 | 77 | (uint128 amountInLeft, uint128 amountOut,) = pairWnative.getSwapOut(amountIn, false); 78 | 79 | vm.assume(amountOut > 0); 80 | 81 | assertEq(amountInLeft, 0, "testFuzz_SwapOutForX::1"); 82 | 83 | deal(address(usdc), ALICE, amountIn); 84 | 85 | vm.startPrank(ALICE); 86 | usdc.transfer(address(pairWnative), amountIn); 87 | pairWnative.swap(false, ALICE); 88 | vm.stopPrank(); 89 | 90 | assertEq(usdc.balanceOf(ALICE), 0, "testFuzz_SwapOutForX::2"); 91 | assertEq(wnative.balanceOf(ALICE), amountOut, "testFuzz_SwapOutForX::3"); 92 | } 93 | 94 | function test_revert_SwapInsufficientAmountIn() external { 95 | vm.expectRevert(ILBPair.LBPair__InsufficientAmountIn.selector); 96 | pairWnative.swap(true, ALICE); 97 | 98 | vm.expectRevert(ILBPair.LBPair__InsufficientAmountIn.selector); 99 | pairWnative.swap(false, ALICE); 100 | } 101 | 102 | function test_revert_SwapInsufficientAmountOut() external { 103 | deal(address(wnative), ALICE, 1); 104 | deal(address(usdc), ALICE, 1); 105 | 106 | vm.prank(ALICE); 107 | wnative.transfer(address(pairWnative), 1); 108 | 109 | vm.expectRevert(ILBPair.LBPair__InsufficientAmountOut.selector); 110 | pairWnative.swap(true, ALICE); 111 | 112 | vm.prank(ALICE); 113 | usdc.transfer(address(pairWnative), 1); 114 | 115 | vm.expectRevert(ILBPair.LBPair__InsufficientAmountOut.selector); 116 | pairWnative.swap(false, ALICE); 117 | } 118 | 119 | function test_revert_SwapOutOfLiquidity() external { 120 | deal(address(wnative), ALICE, 2e18); 121 | deal(address(usdc), ALICE, 2e18); 122 | 123 | vm.prank(ALICE); 124 | wnative.transfer(address(pairWnative), 2e18); 125 | 126 | vm.expectRevert(ILBPair.LBPair__OutOfLiquidity.selector); 127 | pairWnative.swap(true, ALICE); 128 | 129 | vm.prank(ALICE); 130 | usdc.transfer(address(pairWnative), 2e18); 131 | 132 | vm.expectRevert(ILBPair.LBPair__OutOfLiquidity.selector); 133 | pairWnative.swap(false, ALICE); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /test/helpers/Utils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {ILBRouter} from "../../src/interfaces/ILBRouter.sol"; 6 | import {ILBLegacyRouter} from "../../src/interfaces/ILBLegacyRouter.sol"; 7 | 8 | library Utils { 9 | function convertToAbsolute(int256[] memory relativeIds, uint24 startId) 10 | internal 11 | pure 12 | returns (uint256[] memory absoluteIds) 13 | { 14 | absoluteIds = new uint256[](relativeIds.length); 15 | for (uint256 i = 0; i < relativeIds.length; i++) { 16 | int256 id = int256(uint256(startId)) + relativeIds[i]; 17 | require(id >= 0, "Id conversion: id must be positive"); 18 | absoluteIds[i] = uint256(id); 19 | } 20 | } 21 | 22 | function convertToRelative(uint256[] memory absoluteIds, uint24 startId) 23 | internal 24 | pure 25 | returns (int256[] memory relativeIds) 26 | { 27 | relativeIds = new int256[](absoluteIds.length); 28 | for (uint256 i = 0; i < absoluteIds.length; i++) { 29 | relativeIds[i] = int256(absoluteIds[i]) - int256(uint256(startId)); 30 | } 31 | } 32 | 33 | function toLegacy(ILBRouter.LiquidityParameters memory liquidityParameters) 34 | internal 35 | pure 36 | returns (ILBLegacyRouter.LiquidityParameters memory legacyLiquidityParameters) 37 | { 38 | legacyLiquidityParameters = ILBLegacyRouter.LiquidityParameters({ 39 | tokenX: liquidityParameters.tokenX, 40 | tokenY: liquidityParameters.tokenY, 41 | binStep: liquidityParameters.binStep, 42 | amountX: liquidityParameters.amountX, 43 | amountY: liquidityParameters.amountY, 44 | amountXMin: liquidityParameters.amountXMin, 45 | amountYMin: liquidityParameters.amountYMin, 46 | activeIdDesired: liquidityParameters.activeIdDesired, 47 | idSlippage: liquidityParameters.idSlippage, 48 | deltaIds: liquidityParameters.deltaIds, 49 | distributionX: liquidityParameters.distributionX, 50 | distributionY: liquidityParameters.distributionY, 51 | to: liquidityParameters.to, 52 | deadline: liquidityParameters.deadline 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/integration/Addresses.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | library AvalancheAddresses { 6 | address internal constant V2_FACTORY_OWNER = 0x2fbB61a10B96254900C03F1644E9e1d2f5E76DD2; 7 | address internal constant JOE_V1_FACTORY = 0x9Ad6C38BE94206cA50bb0d90783181662f0Cfa10; 8 | address internal constant JOE_V1_ROUTER = 0x60aE616a2155Ee3d9A68541Ba4544862310933d4; 9 | address internal constant JOE_V2_FACTORY = 0x6E77932A92582f504FF6c4BdbCef7Da6c198aEEf; 10 | address internal constant JOE_V2_ROUTER = 0xE3Ffc583dC176575eEA7FD9dF2A7c65F7E23f4C3; 11 | address internal constant JOE_V2_1_FACTORY = 0x8e42f2F4101563bF679975178e880FD87d3eFd4e; 12 | address internal constant JOE_V2_1_ROUTER = 0xb4315e873dBcf96Ffd0acd8EA43f689D8c20fB30; 13 | address internal constant WNATIVE = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7; 14 | address internal constant USDC = 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E; 15 | address internal constant USDT = 0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7; 16 | address internal constant WETH = 0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB; 17 | address internal constant BNB = 0x264c1383EA520f73dd837F915ef3a732e204a493; 18 | } 19 | -------------------------------------------------------------------------------- /test/integration/LBQuoterPriority.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "../helpers/TestHelper.sol"; 6 | 7 | /** 8 | * Makes sure that the new quoter picks the version 2.1 over the version 2 if both outputs are exactly the same 9 | */ 10 | contract LiquidityBinQuoterPriorityTest is Test { 11 | address internal constant factory = 0x8e42f2F4101563bF679975178e880FD87d3eFd4e; 12 | address internal constant router = 0xb4315e873dBcf96Ffd0acd8EA43f689D8c20fB30; 13 | 14 | address internal constant legacyUsdcUsdtPair = 0x1D7A1a79e2b4Ef88D2323f3845246D24a3c20F1d; 15 | address internal constant newUsdcUsdtPair = 0x9B2Cc8E6a2Bbb56d6bE4682891a91B0e48633c72; 16 | 17 | LBQuoter internal newQuoter; 18 | LBQuoter internal oldQuoter = LBQuoter(0x64b57F4249aA99a812212cee7DAEFEDC40B203cD); 19 | 20 | function setUp() public { 21 | vm.createSelectFork(vm.rpcUrl("avalanche"), 33313442); 22 | 23 | newQuoter = new LBQuoter( 24 | AvalancheAddresses.JOE_V1_FACTORY, 25 | AvalancheAddresses.JOE_V2_FACTORY, 26 | AvalancheAddresses.JOE_V2_1_FACTORY, 27 | factory, 28 | AvalancheAddresses.JOE_V2_ROUTER, 29 | AvalancheAddresses.JOE_V2_1_ROUTER, 30 | router 31 | ); 32 | } 33 | 34 | function test_QuoteFromAmountIn() public view { 35 | address[] memory route = new address[](2); 36 | route[0] = address(AvalancheAddresses.USDT); 37 | route[1] = address(AvalancheAddresses.USDC); 38 | 39 | uint128 amountIn = 1e6; 40 | 41 | LBQuoter.Quote memory newQuote = newQuoter.findBestPathFromAmountIn(route, amountIn); 42 | LBQuoter.Quote memory oldQuote = oldQuoter.findBestPathFromAmountIn(route, amountIn); 43 | 44 | assertEq(newQuote.route[0], oldQuote.route[0], "test_QuoteFromAmountIn::1"); 45 | assertEq(newQuote.route[1], oldQuote.route[1], "test_QuoteFromAmountIn::2"); 46 | 47 | assertEq(newQuote.pairs[0], newUsdcUsdtPair, "test_QuoteFromAmountIn::3"); 48 | assertEq(oldQuote.pairs[0], legacyUsdcUsdtPair, "test_QuoteFromAmountIn::4"); 49 | 50 | assertEq(newQuote.binSteps[0], oldQuote.binSteps[0], "test_QuoteFromAmountIn::5"); 51 | 52 | assertEq(uint8(newQuote.versions[0]), uint8(ILBRouter.Version.V2_2), "test_QuoteFromAmountIn::6"); 53 | assertEq(uint8(oldQuote.versions[0]), uint8(ILBRouter.Version.V2), "test_QuoteFromAmountIn::7"); 54 | 55 | assertEq(newQuote.amounts[0], oldQuote.amounts[0], "test_QuoteFromAmountIn::8"); 56 | assertEq(newQuote.amounts[1], oldQuote.amounts[1], "test_QuoteFromAmountIn::9"); 57 | 58 | assertEq( 59 | newQuote.virtualAmountsWithoutSlippage[0], 60 | oldQuote.virtualAmountsWithoutSlippage[0], 61 | "test_QuoteFromAmountIn::10" 62 | ); 63 | assertEq( 64 | newQuote.virtualAmountsWithoutSlippage[1], 65 | oldQuote.virtualAmountsWithoutSlippage[1], 66 | "test_QuoteFromAmountIn::11" 67 | ); 68 | 69 | assertEq(newQuote.fees[0], oldQuote.fees[0], "test_QuoteFromAmountIn::12"); 70 | 71 | route[0] = address(AvalancheAddresses.USDC); 72 | route[1] = address(AvalancheAddresses.USDT); 73 | 74 | newQuote = newQuoter.findBestPathFromAmountIn(route, amountIn); 75 | oldQuote = oldQuoter.findBestPathFromAmountIn(route, amountIn); 76 | 77 | assertEq(newQuote.route[0], oldQuote.route[0], "test_QuoteFromAmountIn::13"); 78 | assertEq(newQuote.route[1], oldQuote.route[1], "test_QuoteFromAmountIn::14"); 79 | 80 | assertEq(newQuote.pairs[0], newUsdcUsdtPair, "test_QuoteFromAmountIn::15"); 81 | assertEq(oldQuote.pairs[0], legacyUsdcUsdtPair, "test_QuoteFromAmountIn::16"); 82 | 83 | assertEq(newQuote.binSteps[0], oldQuote.binSteps[0], "test_QuoteFromAmountIn::17"); 84 | 85 | assertEq(uint8(newQuote.versions[0]), uint8(ILBRouter.Version.V2_2), "test_QuoteFromAmountIn::18"); 86 | assertEq(uint8(oldQuote.versions[0]), uint8(ILBRouter.Version.V2), "test_QuoteFromAmountIn::19"); 87 | 88 | assertEq(newQuote.amounts[0], oldQuote.amounts[0], "test_QuoteFromAmountIn::20"); 89 | assertEq(newQuote.amounts[1], oldQuote.amounts[1], "test_QuoteFromAmountIn::21"); 90 | 91 | assertEq( 92 | newQuote.virtualAmountsWithoutSlippage[0], 93 | oldQuote.virtualAmountsWithoutSlippage[0], 94 | "test_QuoteFromAmountIn::22" 95 | ); 96 | assertEq( 97 | newQuote.virtualAmountsWithoutSlippage[1], 98 | oldQuote.virtualAmountsWithoutSlippage[1], 99 | "test_QuoteFromAmountIn::23" 100 | ); 101 | 102 | assertEq(newQuote.fees[0], oldQuote.fees[0], "test_QuoteFromAmountIn::24"); 103 | } 104 | 105 | function test_QuoteFromAmounOut() public view { 106 | address[] memory route = new address[](2); 107 | route[0] = address(AvalancheAddresses.USDC); 108 | route[1] = address(AvalancheAddresses.USDT); 109 | 110 | uint128 amountOut = 1e6; 111 | 112 | LBQuoter.Quote memory newQuote = newQuoter.findBestPathFromAmountOut(route, amountOut); 113 | LBQuoter.Quote memory oldQuote = oldQuoter.findBestPathFromAmountOut(route, amountOut); 114 | 115 | assertEq(newQuote.route[0], oldQuote.route[0], "test_QuoteFromAmounOut::1"); 116 | assertEq(newQuote.route[1], oldQuote.route[1], "test_QuoteFromAmounOut::2"); 117 | 118 | assertEq(newQuote.pairs[0], newUsdcUsdtPair, "test_QuoteFromAmounOut::3"); 119 | assertEq(oldQuote.pairs[0], legacyUsdcUsdtPair, "test_QuoteFromAmounOut::4"); 120 | 121 | assertEq(newQuote.binSteps[0], oldQuote.binSteps[0], "test_QuoteFromAmounOut::5"); 122 | 123 | assertEq(uint8(newQuote.versions[0]), uint8(ILBRouter.Version.V2_2), "test_QuoteFromAmounOut::6"); 124 | assertEq(uint8(oldQuote.versions[0]), uint8(ILBRouter.Version.V2), "test_QuoteFromAmounOut::7"); 125 | 126 | assertEq(newQuote.amounts[0], oldQuote.amounts[0], "test_QuoteFromAmounOut::8"); 127 | assertEq(newQuote.amounts[1], oldQuote.amounts[1], "test_QuoteFromAmounOut::9"); 128 | 129 | assertEq( 130 | newQuote.virtualAmountsWithoutSlippage[0], 131 | oldQuote.virtualAmountsWithoutSlippage[0], 132 | "test_QuoteFromAmounOut::10" 133 | ); 134 | assertEq( 135 | newQuote.virtualAmountsWithoutSlippage[1], 136 | oldQuote.virtualAmountsWithoutSlippage[1], 137 | "test_QuoteFromAmounOut::11" 138 | ); 139 | 140 | assertEq(newQuote.fees[0], oldQuote.fees[0], "test_QuoteFromAmounOut::12"); 141 | 142 | route[0] = address(AvalancheAddresses.USDT); 143 | route[1] = address(AvalancheAddresses.USDC); 144 | 145 | newQuote = newQuoter.findBestPathFromAmountOut(route, amountOut); 146 | oldQuote = oldQuoter.findBestPathFromAmountOut(route, amountOut); 147 | 148 | assertEq(newQuote.route[0], oldQuote.route[0], "test_QuoteFromAmounOut::13"); 149 | assertEq(newQuote.route[1], oldQuote.route[1], "test_QuoteFromAmounOut::14"); 150 | 151 | assertEq(newQuote.pairs[0], newUsdcUsdtPair, "test_QuoteFromAmounOut::15"); 152 | assertEq(oldQuote.pairs[0], legacyUsdcUsdtPair, "test_QuoteFromAmounOut::16"); 153 | 154 | assertEq(newQuote.binSteps[0], oldQuote.binSteps[0], "test_QuoteFromAmounOut::17"); 155 | 156 | assertEq(uint8(newQuote.versions[0]), uint8(ILBRouter.Version.V2_2), "test_QuoteFromAmounOut::18"); 157 | assertEq(uint8(oldQuote.versions[0]), uint8(ILBRouter.Version.V2), "test_QuoteFromAmounOut::19"); 158 | 159 | assertEq(newQuote.amounts[0], oldQuote.amounts[0], "test_QuoteFromAmounOut::20"); 160 | assertEq(newQuote.amounts[1], oldQuote.amounts[1], "test_QuoteFromAmounOut::21"); 161 | 162 | assertEq( 163 | newQuote.virtualAmountsWithoutSlippage[0], 164 | oldQuote.virtualAmountsWithoutSlippage[0], 165 | "test_QuoteFromAmounOut::22" 166 | ); 167 | assertEq( 168 | newQuote.virtualAmountsWithoutSlippage[1], 169 | oldQuote.virtualAmountsWithoutSlippage[1], 170 | "test_QuoteFromAmounOut::23" 171 | ); 172 | 173 | assertEq(newQuote.fees[0], oldQuote.fees[0], "test_QuoteFromAmounOut::24"); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /test/integration/LBRouter.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "../helpers/TestHelper.sol"; 6 | 7 | /** 8 | * Pairs created: 9 | * USDT/USDC V1 10 | * NATIVE/USDC V2 11 | * WETH/NATIVE V2.1 12 | * TaxToken/NATIVE V2.1 13 | */ 14 | contract LiquidityBinRouterForkTest is TestHelper { 15 | using Utils for ILBRouter.LiquidityParameters; 16 | 17 | function setUp() public override { 18 | vm.createSelectFork(vm.rpcUrl("avalanche"), 46012280); 19 | super.setUp(); 20 | 21 | uint256 liquidityAmount = 1e24; 22 | 23 | // Get tokens to add liquidity 24 | deal(address(usdc), address(this), 10 * liquidityAmount); 25 | deal(address(usdt), address(this), 10 * liquidityAmount); 26 | deal(address(weth), address(this), 10 * liquidityAmount); 27 | deal(address(taxToken), address(this), 10 * liquidityAmount); 28 | 29 | // Add liquidity to V1 30 | routerV1.addLiquidity( 31 | address(usdt), 32 | address(usdc), 33 | liquidityAmount, // 1 USDT = 1 USDC 34 | liquidityAmount, 35 | 0, 36 | 0, 37 | address(this), 38 | block.timestamp + 1 39 | ); 40 | 41 | vm.startPrank(AvalancheAddresses.V2_FACTORY_OWNER); 42 | legacyFactoryV2.addQuoteAsset(usdc); 43 | legacyFactoryV2.createLBPair(wnative, usdc, ID_ONE, DEFAULT_BIN_STEP); // 1 NATIVE = 1 USDC 44 | vm.stopPrank(); 45 | 46 | factory.createLBPair(weth, wnative, ID_ONE, DEFAULT_BIN_STEP); // 1 WETH = 1 NATIVE 47 | factory.createLBPair(taxToken, wnative, ID_ONE, DEFAULT_BIN_STEP); // 1 TaxToken = 1 NATIVE 48 | 49 | // Add liquidity to V2 50 | ILBRouter.LiquidityParameters memory liquidityParameters = 51 | getLiquidityParameters(wnative, usdc, liquidityAmount, ID_ONE, 7, 0); 52 | legacyRouterV2.addLiquidityAVAX{value: liquidityParameters.amountX}(liquidityParameters.toLegacy()); 53 | 54 | liquidityParameters = getLiquidityParameters(weth, wnative, liquidityAmount, ID_ONE, 7, 0); 55 | router.addLiquidityNATIVE{value: liquidityParameters.amountY}(liquidityParameters); 56 | 57 | liquidityParameters = getLiquidityParameters(taxToken, wnative, liquidityAmount, ID_ONE, 7, 0); 58 | router.addLiquidityNATIVE{value: liquidityParameters.amountY}(liquidityParameters); 59 | } 60 | 61 | function test_SwapExactTokensForTokens() public { 62 | uint256 amountIn = 1e18; 63 | 64 | ILBRouter.Path memory path = _buildPath(usdt, weth); 65 | LBQuoter.Quote memory quote = 66 | quoter.findBestPathFromAmountIn(_convertToAddresses(path.tokenPath), uint128(amountIn)); 67 | 68 | uint256 amountOut = router.swapExactTokensForTokens(amountIn, 0, path, address(this), block.timestamp + 1); 69 | 70 | assertEq(amountOut, quote.amounts[3], "test_SwapExactTokensForTokens::1"); 71 | 72 | // Reverse path 73 | path = _buildPath(weth, usdt); 74 | quote = quoter.findBestPathFromAmountIn(_convertToAddresses(path.tokenPath), uint128(amountIn)); 75 | 76 | amountOut = router.swapExactTokensForTokens(amountIn, 0, path, address(this), block.timestamp + 1); 77 | 78 | assertEq(amountOut, quote.amounts[3], "test_SwapExactTokensForTokens::2"); 79 | } 80 | 81 | function test_SwapTokensForExactTokens() public { 82 | uint256 amountOut = 1e18; 83 | 84 | ILBRouter.Path memory path = _buildPath(weth, usdt); 85 | LBQuoter.Quote memory quote = 86 | quoter.findBestPathFromAmountOut(_convertToAddresses(path.tokenPath), uint128(amountOut)); 87 | 88 | uint256[] memory amountsIn = 89 | router.swapTokensForExactTokens(amountOut, quote.amounts[0], path, address(this), block.timestamp + 1); 90 | 91 | assertEq(amountsIn[0], quote.amounts[0], "test_SwapTokensForExactTokens::1"); 92 | 93 | // Reverse path 94 | path = _buildPath(usdt, weth); 95 | quote = quoter.findBestPathFromAmountOut(_convertToAddresses(path.tokenPath), uint128(amountOut)); 96 | 97 | amountsIn = 98 | router.swapTokensForExactTokens(amountOut, quote.amounts[0], path, address(this), block.timestamp + 1); 99 | 100 | assertEq(amountsIn[0], quote.amounts[0], "test_SwapTokensForExactTokens::2"); 101 | } 102 | 103 | function test_SwapExactTokensForTokensSupportingFeeOnTransferTokens() public { 104 | uint256 amountIn = 1e18; 105 | 106 | ILBRouter.Path memory path = _buildPath(taxToken, usdt); 107 | LBQuoter.Quote memory quote = 108 | quoter.findBestPathFromAmountIn(_convertToAddresses(path.tokenPath), uint128(amountIn)); 109 | 110 | uint256 amountOut = router.swapExactTokensForTokensSupportingFeeOnTransferTokens( 111 | amountIn, 0, path, address(this), block.timestamp + 1 112 | ); 113 | 114 | assertApproxEqRel( 115 | amountOut, quote.amounts[3] / 2, 1e12, "test_SwapExactTokensForTokensSupportingFeeOnTransferTokens::1" 116 | ); 117 | 118 | // Reverse path 119 | path = _buildPath(usdt, taxToken); 120 | quote = quoter.findBestPathFromAmountIn(_convertToAddresses(path.tokenPath), uint128(amountIn)); 121 | 122 | amountOut = router.swapExactTokensForTokensSupportingFeeOnTransferTokens( 123 | amountIn, 0, path, address(this), block.timestamp + 1 124 | ); 125 | 126 | assertApproxEqRel( 127 | amountOut, quote.amounts[3] / 2, 1e12, "test_SwapExactTokensForTokensSupportingFeeOnTransferTokens::2" 128 | ); 129 | } 130 | 131 | function _buildPath(IERC20 tokenIn, IERC20 tokenOut) private view returns (ILBRouter.Path memory path) { 132 | path.pairBinSteps = new uint256[](3); 133 | path.versions = new ILBRouter.Version[](3); 134 | path.tokenPath = new IERC20[](4); 135 | 136 | if (tokenIn == usdt) { 137 | path.tokenPath[0] = tokenIn; 138 | path.tokenPath[1] = usdc; 139 | path.tokenPath[2] = wnative; 140 | path.tokenPath[3] = tokenOut; 141 | 142 | path.pairBinSteps[0] = 0; 143 | path.pairBinSteps[1] = DEFAULT_BIN_STEP; 144 | path.pairBinSteps[2] = DEFAULT_BIN_STEP; 145 | 146 | path.versions[0] = ILBRouter.Version.V1; 147 | path.versions[1] = ILBRouter.Version.V2; 148 | path.versions[2] = ILBRouter.Version.V2_2; 149 | } else { 150 | path.tokenPath[0] = tokenIn; 151 | path.tokenPath[1] = wnative; 152 | path.tokenPath[2] = usdc; 153 | path.tokenPath[3] = tokenOut; 154 | 155 | path.pairBinSteps[0] = DEFAULT_BIN_STEP; 156 | path.pairBinSteps[1] = DEFAULT_BIN_STEP; 157 | path.pairBinSteps[2] = 0; 158 | 159 | path.versions[0] = ILBRouter.Version.V2_2; 160 | path.versions[1] = ILBRouter.Version.V2; 161 | path.versions[2] = ILBRouter.Version.V1; 162 | } 163 | } 164 | 165 | function _convertToAddresses(IERC20[] memory tokens) private pure returns (address[] memory addresses) { 166 | addresses = new address[](tokens.length); 167 | for (uint256 i = 0; i < tokens.length; i++) { 168 | addresses[i] = address(tokens[i]); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /test/libraries/FeeHelper.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import "../../src/libraries/FeeHelper.sol"; 8 | import "../../src/libraries/math/Uint256x256Math.sol"; 9 | 10 | contract FeeHelperTest is Test { 11 | using FeeHelper for uint128; 12 | using Uint256x256Math for uint256; 13 | 14 | function testFuzz_GetFeeAmountFrom(uint128 amountWithFee, uint128 fee) external { 15 | if (fee > Constants.MAX_FEE) { 16 | vm.expectRevert(FeeHelper.FeeHelper__FeeTooLarge.selector); 17 | amountWithFee.getFeeAmountFrom(fee); 18 | } else { 19 | uint256 expectedFeeAmount = (uint256(amountWithFee) * fee + 1e18 - 1) / 1e18; 20 | uint128 feeAmount = amountWithFee.getFeeAmountFrom(fee); 21 | 22 | assertEq(feeAmount, expectedFeeAmount, "testFuzz_GetFeeAmountFrom::1"); 23 | } 24 | } 25 | 26 | function testFuzz_GetFeeAmount(uint128 amount, uint128 fee) external { 27 | if (fee > Constants.MAX_FEE) { 28 | vm.expectRevert(FeeHelper.FeeHelper__FeeTooLarge.selector); 29 | amount.getFeeAmount(fee); 30 | } else { 31 | uint128 denominator = 1e18 - fee; 32 | uint256 expectedFeeAmount = (uint256(amount) * fee + denominator - 1) / denominator; 33 | 34 | uint128 feeAmount = amount.getFeeAmount(fee); 35 | 36 | assertEq(feeAmount, expectedFeeAmount, "testFuzz_GetFeeAmount::1"); 37 | } 38 | } 39 | 40 | function testFuzz_GetCompositionFee(uint128 amountWithFee, uint128 fee) external { 41 | if (fee > Constants.MAX_FEE) { 42 | vm.expectRevert(FeeHelper.FeeHelper__FeeTooLarge.selector); 43 | amountWithFee.getCompositionFee(fee); 44 | } 45 | 46 | uint256 denominator = 1e36; 47 | uint256 expectedCompositionFee = 48 | (uint256(amountWithFee) * fee).mulDivRoundDown(uint256(fee) + 1e18, denominator); 49 | 50 | uint128 compositionFee = amountWithFee.getCompositionFee(fee); 51 | 52 | assertEq(compositionFee, expectedCompositionFee, "testFuzz_GetCompositionFee::1"); 53 | } 54 | 55 | function testFuzz_GetProtocolFeeAmount(uint128 amount, uint128 fee) external { 56 | if (fee > Constants.MAX_PROTOCOL_SHARE) { 57 | vm.expectRevert(FeeHelper.FeeHelper__ProtocolShareTooLarge.selector); 58 | amount.getProtocolFeeAmount(fee); 59 | } else { 60 | uint256 expectedProtocolFeeAmount = (uint256(amount) * fee) / 1e4; 61 | uint128 protocolFeeAmount = amount.getProtocolFeeAmount(fee); 62 | 63 | assertEq(protocolFeeAmount, expectedProtocolFeeAmount, "testFuzz_GetProtocolFeeAmount::1"); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/libraries/ImmutableClone.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import "../../src/libraries/Clone.sol"; 8 | import "../../src/libraries/ImmutableClone.sol"; 9 | 10 | contract TestImmutableClone is Test { 11 | function cloneDeterministic(address implementation, bytes memory data, bytes32 salt) public returns (address) { 12 | return ImmutableClone.cloneDeterministic(implementation, data, salt); 13 | } 14 | 15 | function testFuzz_CloneDeterministic(bytes32 salt) public { 16 | address clone = address(ImmutableClone.cloneDeterministic(address(1), "", salt)); 17 | 18 | assertEq( 19 | clone, 20 | ImmutableClone.predictDeterministicAddress(address(1), "", salt, address(this)), 21 | "testFuzz_CloneDeterministic::1" 22 | ); 23 | 24 | // Check that cloning twice with the same salt reverts, needs to call the contract for the error to be caught 25 | vm.expectRevert(ImmutableClone.DeploymentFailed.selector); 26 | this.cloneDeterministic(address(1), "", salt); 27 | } 28 | 29 | function testFuzz_Implementation(bytes memory data) public { 30 | vm.assume(data.length <= 0xffca); 31 | 32 | address implementation = address(new Implementation()); 33 | bytes32 salt = keccak256("salt"); 34 | 35 | address clone = address(ImmutableClone.cloneDeterministic(implementation, data, salt)); 36 | 37 | Implementation implementationClone = Implementation(clone); 38 | 39 | assertEq(implementationClone.getBytes(data.length), data, "testFuzz_Implementation::1"); 40 | } 41 | 42 | function testFuzz_Pair(address tokenX, address tokenY, uint16 binStep) public { 43 | address implementation = address(new Pair()); 44 | bytes32 salt = keccak256("salt"); 45 | 46 | address clone = 47 | address(ImmutableClone.cloneDeterministic(implementation, abi.encodePacked(tokenX, tokenY, binStep), salt)); 48 | 49 | Pair pair = Pair(clone); 50 | 51 | assertEq(pair.getTokenX(), tokenX, "testFuzz_Pair::1"); 52 | assertEq(pair.getTokenY(), tokenY, "testFuzz_Pair::2"); 53 | assertEq(pair.getBinStep(), binStep, "testFuzz_Pair::3"); 54 | } 55 | 56 | function test_CloneDeterministicMaxLength() public { 57 | bytes memory b = new bytes(0xffc8); 58 | 59 | assembly { 60 | mstore8(add(b, 0x20), 0xff) 61 | mstore8(add(b, mload(b)), 0xca) 62 | } 63 | 64 | address implementation = address(new Implementation()); 65 | address clone = ImmutableClone.cloneDeterministic(implementation, b, bytes32(0)); 66 | 67 | assertEq(Implementation(clone).getBytes(b.length), b, "test_CloneDeterministicMaxLength::1"); 68 | } 69 | 70 | function test_CloneDeterministicTooBig() public { 71 | bytes memory b = new bytes(0xffc8 + 1); 72 | vm.expectRevert(ImmutableClone.PackedDataTooBig.selector); 73 | ImmutableClone.cloneDeterministic(address(1), b, bytes32(0)); 74 | } 75 | } 76 | 77 | contract Pair is Clone { 78 | function getTokenX() public pure returns (address) { 79 | return _getArgAddress(0); 80 | } 81 | 82 | function getTokenY() public pure returns (address) { 83 | return _getArgAddress(20); 84 | } 85 | 86 | function getBinStep() public pure returns (uint16) { 87 | return _getArgUint16(40); 88 | } 89 | } 90 | 91 | contract Implementation is Clone { 92 | function getBytes(uint256 length) public pure returns (bytes memory) { 93 | return _getArgBytes(0, length); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /test/libraries/PriceHelper.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import "../../src/libraries/PriceHelper.sol"; 8 | 9 | contract PriceHelperTest is Test { 10 | using Uint256x256Math for uint256; 11 | 12 | Math immutable math = new Math(); 13 | 14 | function testFuzz_GetBase(uint16 binStep) external pure { 15 | uint256 base128x128 = PriceHelper.getBase(binStep); 16 | uint256 expectedBase128x128 = (1 << 128) + (uint256(binStep) << 128) / 10_000; 17 | 18 | assertEq(base128x128, expectedBase128x128, "testFuzz_GetBase::1"); 19 | } 20 | 21 | function testFuzz_GetExponent(uint24 id) external pure { 22 | int256 exponent128x128 = PriceHelper.getExponent(id); 23 | int256 expectedExponent128x128 = int256(uint256(id)) - (1 << 23); 24 | 25 | assertEq(exponent128x128, expectedExponent128x128, "testFuzz_GetExponent::1"); 26 | } 27 | 28 | function testFuzz_ConvertDecimalPriceTo128x128(uint256 price) external pure { 29 | // result of `type(uint256).max * 1e18 >> 128`, this is the largest number before the result overflows 30 | vm.assume(price <= 340282366920938463463374607431768211455999999999999999999); 31 | uint256 price128x128 = PriceHelper.convertDecimalPriceTo128x128(price); 32 | uint256 expectedPrice128x128 = price.shiftDivRoundDown(128, 1e18); 33 | 34 | assertEq(price128x128, expectedPrice128x128, "testFuzz_ConvertDecimalPriceTo128x128::1"); 35 | } 36 | 37 | function testFuzz_revert_ConvertDecimalPriceTo128x128(uint256 price) external { 38 | // result of `type(uint256).max * 1e18 >> 128`, this is the largest number before the result overflows 39 | vm.assume(price > 340282366920938463463374607431768211455999999999999999999); 40 | 41 | vm.expectRevert(Uint256x256Math.Uint256x256Math__MulDivOverflow.selector); 42 | PriceHelper.convertDecimalPriceTo128x128(price); 43 | } 44 | 45 | function testFuzz_Convert128x128PriceToDecimal(uint256 price128x128) external pure { 46 | uint256 priceDecimal = PriceHelper.convert128x128PriceToDecimal(price128x128); 47 | uint256 expectedPriceDecimal = price128x128.mulShiftRoundDown(1e18, 128); 48 | 49 | assertEq(priceDecimal, expectedPriceDecimal, "testFuzz_Convert128x128PriceToDecimal::1"); 50 | } 51 | 52 | function testFuzz_Price(uint256 price, uint16 binStep) external view { 53 | // test that all prices from 1e64 to 1e192 are valid, includes 1e-18 to 1e18 in decimal. 54 | vm.assume(price > 1 << 64 && price < 1 << 192 && binStep > 0 && binStep < 200); 55 | 56 | (bool s, bytes memory data) = 57 | address(math).staticcall(abi.encodeWithSelector(math.idFromPrice.selector, price, binStep)); 58 | 59 | if (s) { 60 | uint24 id = abi.decode(data, (uint24)); 61 | uint256 calculatedPrice = PriceHelper.getPriceFromId(id, binStep); 62 | 63 | // Can't use `assertApproxEqRel` as it overflow when multiplying by 1e18 64 | // Assert that price is at most `binStep`% away from the calculated price 65 | assertLe( 66 | price * (Constants.BASIS_POINT_MAX - binStep) / Constants.BASIS_POINT_MAX, 67 | calculatedPrice, 68 | "testFuzz_Price::1" 69 | ); 70 | assertGe( 71 | price * (Constants.BASIS_POINT_MAX + binStep) / Constants.BASIS_POINT_MAX, 72 | calculatedPrice, 73 | "testFuzz_Price::2" 74 | ); 75 | } 76 | } 77 | 78 | function test_Price() external pure { 79 | uint24 id = 8574931; // result of `log2(123456789) / log2(1.0001) + 2**23` 80 | uint256 expectedPrice = PriceHelper.convertDecimalPriceTo128x128(123456789e18); 81 | assertLe(PriceHelper.getPriceFromId(id - 1, 1), expectedPrice, "test_Price::1"); 82 | assertGe(PriceHelper.getPriceFromId(id + 1, 1), expectedPrice, "test_Price::2"); 83 | 84 | id = 8252553; // result of `log2(0.00000123456789) / log2(1.0001) + 2**23` 85 | expectedPrice = PriceHelper.convertDecimalPriceTo128x128(0.00000123456789e18); 86 | assertLe(PriceHelper.getPriceFromId(id - 1, 1), expectedPrice, "test_Price::3"); 87 | assertGe(PriceHelper.getPriceFromId(id + 1, 1), expectedPrice, "test_Price::4"); 88 | 89 | id = 8392773; // result of `log2(10**18)/log2(1.01) + 2**23` 90 | expectedPrice = PriceHelper.convertDecimalPriceTo128x128(1e36); 91 | assertLe(PriceHelper.getPriceFromId(id - 1, 100), expectedPrice, "test_Price::5"); 92 | assertGe(PriceHelper.getPriceFromId(id + 1, 100), expectedPrice, "test_Price::6"); 93 | 94 | id = 8389042; // result of `log2(10**18)/log2(1.1) + 2**23` 95 | expectedPrice = PriceHelper.convertDecimalPriceTo128x128(1e36); 96 | assertLe(PriceHelper.getPriceFromId(id - 1, 1000), expectedPrice, "test_Price::7"); 97 | assertGe(PriceHelper.getPriceFromId(id + 1, 1000), expectedPrice, "test_Price::8"); 98 | } 99 | } 100 | 101 | contract Math { 102 | function priceFromId(uint24 id, uint16 binStep) external pure returns (uint256) { 103 | return PriceHelper.getPriceFromId(id, binStep); 104 | } 105 | 106 | function idFromPrice(uint256 price, uint16 binStep) external pure returns (uint24) { 107 | return PriceHelper.getIdFromPrice(price, binStep); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /test/libraries/ReentrancyGuardUpgradeable.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import "@openzeppelin/contracts/utils/Address.sol"; 8 | 9 | import "../../src/libraries/ReentrancyGuardUpgradeable.sol"; 10 | 11 | contract ReentrancyGuardUpgradeableTest is Test { 12 | Foo foo; 13 | 14 | function setUp() public { 15 | foo = new Foo(); 16 | } 17 | 18 | function testReentrancyGuard() public { 19 | vm.expectRevert(ReentrancyGuardUpgradeable.ReentrancyGuardReentrantCall.selector); 20 | foo.callSelf(abi.encodeWithSignature("reentrancyGuarded()")); 21 | } 22 | 23 | function testNoReentrancyGuard() public { 24 | foo.callSelf(abi.encodeWithSignature("noReentrancyGuard()")); 25 | } 26 | } 27 | 28 | contract Foo is ReentrancyGuardUpgradeable { 29 | using Address for address; 30 | 31 | constructor() initializer { 32 | __ReentrancyGuard_init(); 33 | } 34 | 35 | function callSelf(bytes memory data) external nonReentrant { 36 | address(this).functionCall(data); 37 | } 38 | 39 | function reentrancyGuarded() external nonReentrant {} 40 | 41 | function noReentrancyGuard() external pure {} 42 | } 43 | -------------------------------------------------------------------------------- /test/libraries/TokenHelper.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import "../../src/libraries/TokenHelper.sol"; 8 | 9 | contract TokenHelperTest is Test { 10 | using TokenHelper for IERC20; 11 | 12 | Vault vault; 13 | 14 | function setUp() public { 15 | vault = new Vault(); 16 | } 17 | 18 | function test_transferFrom() public { 19 | IERC20 goodToken = IERC20(address(new ERC20ReturnTrue())); 20 | 21 | vault.deposit(goodToken, 1); 22 | 23 | vault.withdraw(goodToken, 1); 24 | } 25 | 26 | function test_transferFromEmpty() public { 27 | IERC20 goodToken = IERC20(address(new ERC20ReturnsEmpty())); 28 | 29 | vault.deposit(goodToken, 1); 30 | 31 | vault.withdraw(goodToken, 1); 32 | } 33 | 34 | function test_revert_TransferOnFalse() public { 35 | IERC20 badToken = IERC20(address(new ERC20ReturnsFalse())); 36 | 37 | vm.expectRevert(TokenHelper.TokenHelper__TransferFailed.selector); 38 | vault.deposit(badToken, 1); 39 | 40 | vm.expectRevert(TokenHelper.TokenHelper__TransferFailed.selector); 41 | vault.withdraw(badToken, 1); 42 | } 43 | 44 | function test_revert_TransferOnCustom() public { 45 | IERC20 badToken = IERC20(address(new ERC20ReturnsCustom())); 46 | 47 | vm.expectRevert(TokenHelper.TokenHelper__TransferFailed.selector); 48 | vault.deposit(badToken, 1); 49 | 50 | vm.expectRevert(TokenHelper.TokenHelper__TransferFailed.selector); 51 | vault.withdraw(badToken, 1); 52 | } 53 | 54 | function test_revert_TransferOnRevertWithMessage() public { 55 | IERC20 badToken = IERC20(address(new ERC20RevertsWithMessage())); 56 | 57 | vm.expectRevert(bytes("fail")); 58 | vault.deposit(badToken, 1); 59 | 60 | vm.expectRevert(bytes("fail")); 61 | vault.withdraw(badToken, 1); 62 | } 63 | 64 | function test_revert_TransferOnRevertWithoutMessage() public { 65 | IERC20 badToken = IERC20(address(new ERC20RevertsWithoutMessage())); 66 | 67 | vm.expectRevert(TokenHelper.TokenHelper__TransferFailed.selector); 68 | vault.deposit(badToken, 1); 69 | 70 | vm.expectRevert(TokenHelper.TokenHelper__TransferFailed.selector); 71 | vault.withdraw(badToken, 1); 72 | } 73 | 74 | function test_revert_TransferOnNoCode() public { 75 | IERC20 badToken = IERC20(makeAddr("Token")); 76 | 77 | vm.expectRevert(TokenHelper.TokenHelper__TransferFailed.selector); 78 | vault.deposit(badToken, 1); 79 | 80 | vm.expectRevert(TokenHelper.TokenHelper__TransferFailed.selector); 81 | vault.withdraw(badToken, 1); 82 | } 83 | } 84 | 85 | contract Vault { 86 | using TokenHelper for IERC20; 87 | 88 | function deposit(IERC20 token, uint256 amount) external { 89 | token.safeTransferFrom(msg.sender, address(this), amount); 90 | } 91 | 92 | function withdraw(IERC20 token, uint256 amount) external { 93 | token.safeTransfer(msg.sender, amount); 94 | } 95 | } 96 | 97 | contract ERC20ReturnTrue { 98 | function transfer(address, uint256) external pure returns (bool) { 99 | return true; 100 | } 101 | 102 | function transferFrom(address, address, uint256) external pure returns (bool) { 103 | return true; 104 | } 105 | } 106 | 107 | contract ERC20ReturnsEmpty { 108 | function transfer(address, uint256) external pure {} 109 | 110 | function transferFrom(address, address, uint256) external pure {} 111 | } 112 | 113 | contract ERC20ReturnsFalse { 114 | function transfer(address, uint256) external pure returns (bool) { 115 | return false; 116 | } 117 | 118 | function transferFrom(address, address, uint256) external pure returns (bool) { 119 | return false; 120 | } 121 | } 122 | 123 | contract ERC20ReturnsCustom { 124 | function transfer(address, uint256) external pure returns (bytes32) { 125 | return keccak256("fail"); 126 | } 127 | 128 | function transferFrom(address, address, uint256) external pure returns (bytes32) { 129 | return keccak256("fail"); 130 | } 131 | } 132 | 133 | contract ERC20RevertsWithMessage { 134 | function transfer(address, uint256) external pure { 135 | revert("fail"); 136 | } 137 | 138 | function transferFrom(address, address, uint256) external pure { 139 | revert("fail"); 140 | } 141 | } 142 | 143 | contract ERC20RevertsWithoutMessage { 144 | function transfer(address, uint256) external pure { 145 | revert(); 146 | } 147 | 148 | function transferFrom(address, address, uint256) external pure { 149 | revert(); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /test/libraries/math/BitMath.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import "../../../src/libraries/math/BitMath.sol"; 8 | 9 | contract BitMathTest is Test { 10 | using BitMath for uint256; 11 | 12 | function test_MostSignificantBit() external pure { 13 | for (uint256 i = 0; i < 256; i++) { 14 | assertEq(uint256(1 << i).mostSignificantBit(), i, "test_MostSignificantBit::1"); 15 | } 16 | } 17 | 18 | function testFuzz_MostSignificantBit(uint256 x) external pure { 19 | uint256 msb = x.mostSignificantBit(); 20 | 21 | if (x == 0) { 22 | assertEq(msb, 0, "testFuzz_MostSignificantBit::1"); 23 | } else { 24 | assertEq(x >> msb, 1, "testFuzz_MostSignificantBit::2"); 25 | } 26 | } 27 | 28 | function test_LeastSignificantBit() external pure { 29 | for (uint256 i = 0; i < 256; i++) { 30 | assertEq(uint256(1 << i).leastSignificantBit(), i, "test_LeastSignificantBit::1"); 31 | } 32 | } 33 | 34 | function testFuzz_LeastSignificantBit(uint256 x) external pure { 35 | uint256 lsb = x.leastSignificantBit(); 36 | 37 | if (x == 0) { 38 | assertEq(lsb, 255, "testFuzz_LeastSignificantBit::1"); 39 | } else { 40 | assertEq(x << (255 - lsb), 1 << 255, "testFuzz_LeastSignificantBit::2"); 41 | } 42 | } 43 | 44 | function test_ClosestBitRight() external pure { 45 | for (uint256 i = 0; i < 256; i++) { 46 | assertEq(uint256(1 << i).closestBitRight(255), i, "test_ClosestBitRight::1"); 47 | } 48 | } 49 | 50 | function testFuzz_ClosestBitRight(uint256 x, uint8 bit) external pure { 51 | uint256 cbr = x.closestBitRight(bit); 52 | 53 | if (cbr == type(uint256).max) { 54 | assertEq(x << (255 - bit), 0, "testFuzz_ClosestBitRight::1"); 55 | } else { 56 | assertLe(cbr, bit, "testFuzz_ClosestBitRight::2"); 57 | assertGe(x << (255 - cbr), 1 << 255, "testFuzz_ClosestBitRight::3"); 58 | } 59 | } 60 | 61 | function test_ClosestBitLeft() external pure { 62 | for (uint256 i = 0; i < 256; i++) { 63 | assertEq(uint256(1 << i).closestBitLeft(0), i, "test_ClosestBitLeft::1"); 64 | } 65 | } 66 | 67 | function testFuzz_ClosestBitLeft(uint256 x, uint8 bit) external pure { 68 | uint256 cbl = x.closestBitLeft(bit); 69 | 70 | if (cbl == type(uint256).max) { 71 | assertEq(x >> bit, 0, "testFuzz_ClosestBitLeft::1"); 72 | } else { 73 | assertGe(cbl, bit, "testFuzz_ClosestBitLeft::2"); 74 | assertGe(x >> cbl, 1, "testFuzz_ClosestBitLeft::3"); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/libraries/math/Encoded.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import "../../../src/libraries/math/Encoded.sol"; 8 | 9 | contract EncodedTest is Test { 10 | using Encoded for bytes32; 11 | 12 | function testFuzz_Set(bytes32 x, uint256 v, uint256 mask, uint256 offset) external pure { 13 | bytes32 y = x.set(v, mask, offset); 14 | 15 | bytes32 expected = x; 16 | expected &= bytes32(~(mask << offset)); 17 | expected |= bytes32((v & mask) << offset); 18 | 19 | assertEq(y, expected, "testFuzz_Set::1"); 20 | } 21 | 22 | function testFuzz_Decode(bytes32 x, uint256 mask, uint256 offset) external pure { 23 | uint256 v = x.decode(mask, offset); 24 | assertEq(v, (uint256(x) >> offset) & mask, "testFuzz_Decode::1"); 25 | } 26 | 27 | function testFuzz_SetAndDecode(bytes32 x, uint256 v, uint256 mask, uint256 offset) external pure { 28 | bytes32 y = x.set(v, mask, offset); 29 | uint256 v2 = y.decode(mask, offset); 30 | 31 | assertEq(v2, ((v << offset) >> offset) & mask, "testFuzz_SetAndDecode::1"); 32 | } 33 | 34 | function testFuzz_decodeBool(bytes32 x, uint256 offset) external pure { 35 | bool v = x.decodeBool(offset); 36 | assertEq(v ? 1 : 0, (uint256(x) >> offset) & 1, "testFuzz_decodeBool::1"); 37 | } 38 | 39 | function testFuzz_decodeUint8(bytes32 x, uint256 offset) external pure { 40 | uint256 v = x.decodeUint8(offset); 41 | assertEq(v, (uint256(x) >> offset) & 0xff, "testFuzz_decodeUint8::1"); 42 | } 43 | 44 | function testFuzz_decodeUint12(bytes32 x, uint256 offset) external pure { 45 | uint256 v = x.decodeUint12(offset); 46 | assertEq(v, (uint256(x) >> offset) & 0xfff, "testFuzz_decodeUint12::1"); 47 | } 48 | 49 | function testFuzz_decodeUint14(bytes32 x, uint256 offset) external pure { 50 | uint256 v = x.decodeUint14(offset); 51 | assertEq(v, (uint256(x) >> offset) & 0x3fff, "testFuzz_decodeUint14::1"); 52 | } 53 | 54 | function testFuzz_decodeUint16(bytes32 x, uint256 offset) external pure { 55 | uint256 v = x.decodeUint16(offset); 56 | assertEq(v, (uint256(x) >> offset) & 0xffff, "testFuzz_decodeUint16::1"); 57 | } 58 | 59 | function testFuzz_decodeUint20(bytes32 x, uint256 offset) external pure { 60 | uint256 v = x.decodeUint20(offset); 61 | assertEq(v, (uint256(x) >> offset) & 0xfffff, "testFuzz_decodeUint20::1"); 62 | } 63 | 64 | function testFuzz_decodeUint24(bytes32 x, uint256 offset) external pure { 65 | uint256 v = x.decodeUint24(offset); 66 | assertEq(v, (uint256(x) >> offset) & 0xffffff, "testFuzz_decodeUint24::1"); 67 | } 68 | 69 | function testFuzz_decodeUint40(bytes32 x, uint256 offset) external pure { 70 | uint256 v = x.decodeUint40(offset); 71 | assertEq(v, (uint256(x) >> offset) & 0xffffffffff, "testFuzz_decodeUint40::1"); 72 | } 73 | 74 | function testFuzz_decodeUint64(bytes32 x, uint256 offset) external pure { 75 | uint256 v = x.decodeUint64(offset); 76 | assertEq(v, (uint256(x) >> offset) & 0xffffffffffffffff, "testFuzz_decodeUint64::1"); 77 | } 78 | 79 | function testFuzz_decodeUint128(bytes32 x, uint256 offset) external pure { 80 | uint256 v = x.decodeUint128(offset); 81 | assertEq(v, (uint256(x) >> offset) & 0xffffffffffffffffffffffffffffffff, "testFuzz_decodeUint128::1"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/libraries/math/LiquidityConfigurations.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import "../../../src/libraries/math/LiquidityConfigurations.sol"; 8 | 9 | contract LiquidityConfigurationsTest is Test { 10 | using PackedUint128Math for bytes32; 11 | using PackedUint128Math for uint128; 12 | using LiquidityConfigurations for bytes32; 13 | 14 | function testFuzz_EncodeParams(uint64 distributionX, uint64 distributionY, uint24 id) external pure { 15 | bytes32 config = LiquidityConfigurations.encodeParams(distributionX, distributionY, id); 16 | 17 | assertEq( 18 | uint256(config), 19 | uint256(distributionX) << 88 | uint256(distributionY) << 24 | id, 20 | "testFuzz_EncodeParams::1" 21 | ); 22 | } 23 | 24 | function testFuzz_DecodeParams(bytes32 config) external { 25 | uint64 distributionX = uint64(uint256(config) >> 88); 26 | uint64 distributionY = uint64(uint256(config) >> 24); 27 | uint24 id = uint24(uint256(config)); 28 | 29 | if (uint256(config) > type(uint152).max || distributionX > 1e18 || distributionY > 1e18) { 30 | vm.expectRevert(LiquidityConfigurations.LiquidityConfigurations__InvalidConfig.selector); 31 | } 32 | 33 | (uint64 _distributionX, uint64 _distributionY, uint24 _id) = config.decodeParams(); 34 | 35 | assertEq(_distributionX, distributionX, "testFuzz_DecodeParams::1"); 36 | assertEq(_distributionY, distributionY, "testFuzz_DecodeParams::2"); 37 | assertEq(_id, id, "testFuzz_DecodeParams::3"); 38 | } 39 | 40 | function testFuzz_GetAmountsAndId(bytes32 config, bytes32 amounts) external { 41 | uint64 distributionX = uint64(uint256(config) >> 88); 42 | uint64 distributionY = uint64(uint256(config) >> 24); 43 | uint24 id = uint24(uint256(config)); 44 | 45 | if (uint256(config) > type(uint152).max || distributionX > 1e18 || distributionY > 1e18) { 46 | vm.expectRevert(LiquidityConfigurations.LiquidityConfigurations__InvalidConfig.selector); 47 | } 48 | 49 | (distributionX, distributionY, id) = config.decodeParams(); 50 | 51 | (uint128 x1, uint128 x2) = amounts.decode(); 52 | 53 | uint128 y1 = uint128(uint256(x1) * distributionX / 1e18); 54 | uint128 y2 = uint128(uint256(x2) * distributionY / 1e18); 55 | 56 | bytes32 amountsInToBin = y1.encode(y2); 57 | 58 | (bytes32 _amountsInToBin, uint24 _id) = config.getAmountsAndId(amounts); 59 | 60 | assertEq(_amountsInToBin, amountsInToBin, "testFuzz_GetAmountsAndId::1"); 61 | assertEq(_id, id, "testFuzz_GetAmountsAndId::2"); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/libraries/math/PackedUint128Math.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import "../../../src/libraries/math/PackedUint128Math.sol"; 8 | 9 | contract PackedUint128MathTest is Test { 10 | using PackedUint128Math for bytes32; 11 | using PackedUint128Math for uint128; 12 | 13 | function testFuzz_Encode(uint128 x1, uint128 x2) external pure { 14 | assertEq(bytes32(x1 | uint256(x2) << 128), x1.encode(x2), "testFuzz_Encode::1"); 15 | } 16 | 17 | function testFuzz_EncodeFirst(uint128 x1) external pure { 18 | assertEq(bytes32(uint256(x1)), x1.encodeFirst(), "testFuzz_EncodeFirst::1"); 19 | } 20 | 21 | function testFuzz_EncodeSecond(uint128 x2) external pure { 22 | assertEq(bytes32(uint256(x2) << 128), x2.encodeSecond(), "testFuzz_EncodeSecond::1"); 23 | } 24 | 25 | function testFuzz_EncodeBool(uint128 x, bool first) external pure { 26 | assertEq(bytes32(uint256(x) << (first ? 0 : 128)), x.encode(first), "testFuzz_EncodeBool::1"); 27 | } 28 | 29 | function testFuzz_Decode(bytes32 x) external pure { 30 | (uint128 x1, uint128 x2) = x.decode(); 31 | 32 | assertEq(x1, uint128(uint256(x)), "testFuzz_Decode::1"); 33 | assertEq(x2, uint128(uint256(x) >> 128), "testFuzz_Decode::2"); 34 | } 35 | 36 | function testFuzz_decodeX(bytes32 x) external pure { 37 | assertEq(uint128(uint256(x)), x.decodeX(), "testFuzz_decodeX::1"); 38 | } 39 | 40 | function testFuzz_decodeY(bytes32 x) external pure { 41 | assertEq(uint128(uint256(x) >> 128), x.decodeY(), "testFuzz_decodeY::1"); 42 | } 43 | 44 | function testFuzz_DecodeBool(bytes32 x, bool first) external pure { 45 | assertEq(uint128(uint256(x) >> (first ? 0 : 128)), x.decode(first), "testFuzz_DecodeBool::1"); 46 | } 47 | 48 | function test_AddSelf() external pure { 49 | bytes32 x = bytes32(uint256(1 << 128 | 1)); 50 | 51 | assertEq(x.add(x), bytes32(uint256(2 << 128 | 2)), "test_AddSelf::1"); 52 | } 53 | 54 | function test_AddOverflow() external { 55 | bytes32 x = bytes32(type(uint256).max); 56 | 57 | bytes32 y1 = bytes32(uint256(1)); 58 | bytes32 y2 = bytes32(uint256(1 << 128)); 59 | bytes32 y3 = y1 | y2; 60 | 61 | vm.expectRevert(PackedUint128Math.PackedUint128Math__AddOverflow.selector); 62 | x.add(y1); 63 | 64 | vm.expectRevert(PackedUint128Math.PackedUint128Math__AddOverflow.selector); 65 | x.add(y2); 66 | 67 | vm.expectRevert(PackedUint128Math.PackedUint128Math__AddOverflow.selector); 68 | x.add(y3); 69 | 70 | vm.expectRevert(PackedUint128Math.PackedUint128Math__AddOverflow.selector); 71 | y1.add(x); 72 | 73 | vm.expectRevert(PackedUint128Math.PackedUint128Math__AddOverflow.selector); 74 | y2.add(x); 75 | 76 | vm.expectRevert(PackedUint128Math.PackedUint128Math__AddOverflow.selector); 77 | y3.add(x); 78 | } 79 | 80 | function testFuzz_Add(bytes32 x, bytes32 y) external { 81 | uint128 x1 = uint128(uint256(x)); 82 | uint128 x2 = uint128(uint256(x >> 128)); 83 | 84 | uint128 y1 = uint128(uint256(y)); 85 | uint128 y2 = uint128(uint256(y >> 128)); 86 | 87 | if (x1 <= type(uint128).max - y1 && x2 <= type(uint128).max - y2) { 88 | assertEq(x.add(y), bytes32(uint256(x1 + y1) | uint256(x2 + y2) << 128), "testFuzz_Add::1"); 89 | } else { 90 | vm.expectRevert(PackedUint128Math.PackedUint128Math__AddOverflow.selector); 91 | x.add(y); 92 | } 93 | } 94 | 95 | function test_SubSelf() external pure { 96 | bytes32 x = bytes32(uint256(1 << 128 | 1)); 97 | 98 | assertEq(x.sub(x), bytes32(0), "test_SubSelf::1"); 99 | } 100 | 101 | function test_SubUnderflow() external { 102 | bytes32 x = bytes32(0); 103 | 104 | bytes32 y1 = bytes32(uint256(1)); 105 | bytes32 y2 = bytes32(uint256(1 << 128)); 106 | bytes32 y3 = y1 | y2; 107 | 108 | assertEq(y1.sub(x), y1, "test_SubUnderflow::1"); 109 | assertEq(y2.sub(x), y2, "test_SubUnderflow::2"); 110 | assertEq(y3.sub(x), y3, "test_SubUnderflow::3"); 111 | 112 | vm.expectRevert(PackedUint128Math.PackedUint128Math__SubUnderflow.selector); 113 | x.sub(y1); 114 | 115 | vm.expectRevert(PackedUint128Math.PackedUint128Math__SubUnderflow.selector); 116 | x.sub(y2); 117 | 118 | vm.expectRevert(PackedUint128Math.PackedUint128Math__SubUnderflow.selector); 119 | x.sub(y3); 120 | } 121 | 122 | function testFuzz_Sub(bytes32 x, bytes32 y) external { 123 | uint128 x1 = uint128(uint256(x)); 124 | uint128 x2 = uint128(uint256(x >> 128)); 125 | 126 | uint128 y1 = uint128(uint256(y)); 127 | uint128 y2 = uint128(uint256(y >> 128)); 128 | 129 | if (x1 >= y1 && x2 >= y2) { 130 | assertEq(x.sub(y), bytes32(uint256(x1 - y1) | uint256(x2 - y2) << 128), "testFuzz_Sub::1"); 131 | } else { 132 | vm.expectRevert(PackedUint128Math.PackedUint128Math__SubUnderflow.selector); 133 | x.sub(y); 134 | } 135 | } 136 | 137 | function testFuzz_LessThan(bytes32 x, bytes32 y) external pure { 138 | (uint128 x1, uint128 x2) = x.decode(); 139 | (uint128 y1, uint128 y2) = y.decode(); 140 | 141 | assertEq(x.lt(y), x1 < y1 || x2 < y2, "testFuzz_LessThan::1"); 142 | } 143 | 144 | function testFuzz_GreaterThan(bytes32 x, bytes32 y) external pure { 145 | (uint128 x1, uint128 x2) = x.decode(); 146 | (uint128 y1, uint128 y2) = y.decode(); 147 | 148 | assertEq(x.gt(y), x1 > y1 || x2 > y2, "testFuzz_GreaterThan::1"); 149 | } 150 | 151 | function testFuzz_ScalarMulDivBasisPointRoundDown(bytes32 x, uint128 multipilier) external { 152 | (uint128 x1, uint128 x2) = x.decode(); 153 | 154 | uint256 y1 = uint256(x1) * multipilier; 155 | uint256 y2 = uint256(x2) * multipilier; 156 | 157 | uint256 z1 = y1 / Constants.BASIS_POINT_MAX; 158 | uint256 z2 = y2 / Constants.BASIS_POINT_MAX; 159 | 160 | if (multipilier > Constants.BASIS_POINT_MAX) { 161 | vm.expectRevert(PackedUint128Math.PackedUint128Math__MultiplierTooLarge.selector); 162 | x.scalarMulDivBasisPointRoundDown(multipilier); 163 | } else { 164 | assertLe(z1, type(uint128).max, "testFuzz_ScalarMulDivBasisPointRoundDown::1"); 165 | assertLe(z2, type(uint128).max, "testFuzz_ScalarMulDivBasisPointRoundDown::2"); 166 | 167 | assertEq( 168 | x.scalarMulDivBasisPointRoundDown(multipilier), 169 | uint128(z1).encode(uint128(z2)), 170 | "testFuzz_ScalarMulDivBasisPointRoundDown::3" 171 | ); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /test/libraries/math/SampleMath.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import "../../../src/libraries/math/SampleMath.sol"; 8 | 9 | contract SampleMathTest is Test { 10 | using SampleMath for bytes32; 11 | 12 | function testFuzz_GetOracleLength(bytes32 sample) external pure { 13 | uint256 oracleLength = sample.getOracleLength(); 14 | assertLe(oracleLength, type(uint16).max, "testFuzz_GetOracleLength::1"); 15 | assertEq(uint16(uint256(sample)), oracleLength, "testFuzz_GetOracleLength::2"); 16 | } 17 | 18 | function testFuzz_GetCumulativeId(bytes32 sample) external pure { 19 | uint256 cumulativeId = sample.getCumulativeId(); 20 | assertLe(cumulativeId, type(uint64).max, "testFuzz_GetCumulativeId::1"); 21 | assertEq(uint64(uint256(sample) >> 16), cumulativeId, "testFuzz_GetCumulativeId::2"); 22 | } 23 | 24 | function testFuzz_GetCumulativeVolatility(bytes32 sample) external pure { 25 | uint256 cumulativeVolatility = sample.getCumulativeVolatility(); 26 | assertLe(cumulativeVolatility, type(uint64).max, "testFuzz_GetCumulativeVolatility::1"); 27 | assertEq(uint64(uint256(sample) >> 80), cumulativeVolatility, "testFuzz_GetCumulativeVolatility::2"); 28 | } 29 | 30 | function testFuzz_GetCumulativeBinCrossed(bytes32 sample) external pure { 31 | uint256 cumulativeBinCrossed = sample.getCumulativeBinCrossed(); 32 | assertLe(cumulativeBinCrossed, type(uint64).max, "testFuzz_GetCumulativeBinCrossed::1"); 33 | assertEq(uint64(uint256(sample) >> 144), cumulativeBinCrossed, "testFuzz_GetCumulativeBinCrossed::2"); 34 | } 35 | 36 | function testFuzz_GetSampleLifetime(bytes32 sample) external pure { 37 | uint256 sampleLifetime = sample.getSampleLifetime(); 38 | assertLe(sampleLifetime, type(uint8).max, "testFuzz_GetSampleLifetime::1"); 39 | assertEq(uint8(uint256(sample) >> 208), sampleLifetime, "testFuzz_GetSampleLifetime::2"); 40 | } 41 | 42 | function testFuzz_GetSampleCreation(bytes32 sample) external pure { 43 | uint256 sampleCreation = sample.getSampleCreation(); 44 | assertLe(sampleCreation, type(uint40).max, "testFuzz_GetSampleCreation::1"); 45 | assertEq(uint40(uint256(sample) >> 216), sampleCreation, "testFuzz_GetSampleCreation::2"); 46 | } 47 | 48 | function testFuzz_GetSampleLastUpdate(bytes32 sample) external { 49 | uint40 sampleCreation = sample.getSampleCreation(); 50 | uint8 sampleLifetime = sample.getSampleLifetime(); 51 | 52 | if (sampleCreation > type(uint40).max - sampleLifetime) { 53 | vm.expectRevert(); 54 | sample.getSampleLastUpdate(); 55 | } else { 56 | uint40 sampleLastUpdate = sample.getSampleLastUpdate(); 57 | assertEq(sampleLastUpdate, sampleCreation + sampleLifetime, "testFuzz_GetSampleLastUpdate::1"); 58 | } 59 | } 60 | 61 | function testFuzz_encode( 62 | uint16 oracleLength, 63 | uint64 cumulativeId, 64 | uint64 cumulativeVolatility, 65 | uint64 cumulativeBinCrossed, 66 | uint8 sampleLifetime, 67 | uint40 createdAt 68 | ) external pure { 69 | bytes32 sample = SampleMath.encode( 70 | oracleLength, cumulativeId, cumulativeVolatility, cumulativeBinCrossed, sampleLifetime, createdAt 71 | ); 72 | 73 | assertEq(sample.getOracleLength(), oracleLength, "testFuzz_encode::1"); 74 | assertEq(sample.getCumulativeId(), cumulativeId, "testFuzz_encode::2"); 75 | assertEq(sample.getCumulativeVolatility(), cumulativeVolatility, "testFuzz_encode::3"); 76 | assertEq(sample.getCumulativeBinCrossed(), cumulativeBinCrossed, "testFuzz_encode::4"); 77 | assertEq(sample.getSampleLifetime(), sampleLifetime, "testFuzz_encode::5"); 78 | assertEq(sample.getSampleCreation(), createdAt, "testFuzz_encode::6"); 79 | } 80 | 81 | function testFuzz_GetWeightedAverage(bytes32 sample1, bytes32 sample2, uint40 weight1, uint40 weight2) external { 82 | uint256 totalWeight = uint256(weight1) + weight2; 83 | 84 | if (totalWeight == 0) { 85 | vm.expectRevert(); 86 | sample1.getWeightedAverage(sample2, weight1, weight2); 87 | } 88 | 89 | (uint256 wAverageId, uint256 wAverageVolatility, uint256 wAverageBinCrossed) = (0, 0, 0); 90 | 91 | { 92 | uint256 cId1 = sample1.getCumulativeId(); 93 | uint256 cVol1 = sample1.getCumulativeVolatility(); 94 | uint256 cBin1 = sample1.getCumulativeBinCrossed(); 95 | 96 | uint256 cId2 = sample2.getCumulativeId(); 97 | uint256 cVol2 = sample2.getCumulativeVolatility(); 98 | uint256 cBin2 = sample2.getCumulativeBinCrossed(); 99 | 100 | wAverageId = (cId1 * weight1 + cId2 * weight2) / totalWeight; 101 | wAverageVolatility = (cVol1 * weight1 + cVol2 * weight2) / totalWeight; 102 | wAverageBinCrossed = (cBin1 * weight1 + cBin2 * weight2) / totalWeight; 103 | } 104 | 105 | if ( 106 | wAverageId > type(uint64).max || wAverageVolatility > type(uint64).max 107 | || wAverageBinCrossed > type(uint64).max 108 | ) { 109 | vm.expectRevert(); 110 | sample1.getWeightedAverage(sample2, weight1, weight2); 111 | } else { 112 | (uint64 weightedAverageId, uint64 weightedAverageVolatility, uint64 weightedAverageBinCrossed) = 113 | sample1.getWeightedAverage(sample2, weight1, weight2); 114 | 115 | assertEq(weightedAverageId, wAverageId, "testFuzz_GetWeightedAverage::1"); 116 | assertEq(weightedAverageVolatility, wAverageVolatility, "testFuzz_GetWeightedAverage::2"); 117 | assertEq(weightedAverageBinCrossed, wAverageBinCrossed, "testFuzz_GetWeightedAverage::3"); 118 | } 119 | } 120 | 121 | function testFuzz_update(uint40 deltaTime, uint24 activeId, uint24 volatilityAccumulator, uint24 binCrossed) 122 | external 123 | pure 124 | { 125 | (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = 126 | bytes32(0).update(deltaTime, activeId, volatilityAccumulator, binCrossed); 127 | 128 | assertEq(cumulativeId, uint64(activeId) * deltaTime, "testFuzz_update::1"); 129 | assertEq(cumulativeVolatility, uint64(volatilityAccumulator) * deltaTime, "testFuzz_update::2"); 130 | assertEq(cumulativeBinCrossed, uint64(binCrossed) * deltaTime, "testFuzz_update::3"); 131 | } 132 | 133 | function testFuzz_updateWithSample( 134 | bytes32 sample, 135 | uint40 deltaTime, 136 | uint24 activeId, 137 | uint24 volatilityAccumulator, 138 | uint24 binCrossed 139 | ) external { 140 | uint64 currentCumulativeId = sample.getCumulativeId(); 141 | uint64 currentCumulativeVolatility = sample.getCumulativeVolatility(); 142 | uint64 currentCumulativeBinCrossed = sample.getCumulativeBinCrossed(); 143 | 144 | uint64(deltaTime) * activeId; 145 | uint64(deltaTime) * volatilityAccumulator; 146 | uint64(deltaTime) * binCrossed; 147 | 148 | if ( 149 | uint64(deltaTime) * activeId > type(uint64).max - currentCumulativeId 150 | || uint64(deltaTime) * volatilityAccumulator > type(uint64).max - currentCumulativeVolatility 151 | || uint64(deltaTime) * binCrossed > type(uint64).max - currentCumulativeBinCrossed 152 | ) { 153 | vm.expectRevert(); 154 | sample.update(deltaTime, activeId, volatilityAccumulator, binCrossed); 155 | } else { 156 | (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed) = 157 | sample.update(deltaTime, activeId, volatilityAccumulator, binCrossed); 158 | 159 | assertEq( 160 | uint64(cumulativeId), currentCumulativeId + uint64(activeId) * deltaTime, "testFuzz_updateWithSample::1" 161 | ); 162 | assertEq( 163 | uint64(cumulativeVolatility), 164 | currentCumulativeVolatility + uint64(volatilityAccumulator) * deltaTime, 165 | "testFuzz_updateWithSample::2" 166 | ); 167 | assertEq( 168 | uint64(cumulativeBinCrossed), 169 | currentCumulativeBinCrossed + uint64(binCrossed) * deltaTime, 170 | "testFuzz_updateWithSample::3" 171 | ); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /test/libraries/math/TreeMath.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import "../../../src/libraries/math/TreeMath.sol"; 8 | 9 | contract TreeMathTest is Test { 10 | using TreeMath for TreeMath.TreeUint24; 11 | 12 | TreeMath.TreeUint24 private _tree; 13 | 14 | function testFuzz_AddToTree(uint24[] calldata ids) external { 15 | for (uint256 i = 0; i < ids.length; i++) { 16 | bool contains = _tree.contains(ids[i]); 17 | assertEq(_tree.add(ids[i]), !contains, "testFuzz_AddToTree::1"); 18 | assertEq(_tree.contains(ids[i]), true, "testFuzz_AddToTree::2"); 19 | } 20 | } 21 | 22 | function testFuzz_RemoveFromTree(uint24[] calldata ids) external { 23 | for (uint256 i = 0; i < ids.length; i++) { 24 | _tree.add(ids[i]); 25 | } 26 | 27 | for (uint256 i = 0; i < ids.length; i++) { 28 | bool contains = _tree.contains(ids[i]); 29 | assertEq(_tree.remove(ids[i]), contains, "testFuzz_RemoveFromTree::1"); 30 | assertEq(_tree.contains(ids[i]), false, "testFuzz_RemoveFromTree::2"); 31 | } 32 | } 33 | 34 | function testFuzz_AddAndRemove(uint24 id) external { 35 | _tree.add(id); 36 | 37 | assertEq(_tree.contains(id), true, "testFuzz_AddAndRemove::1"); 38 | 39 | assertGt(uint256(_tree.level0), 0, "testFuzz_AddAndRemove::2"); 40 | assertGt(uint256(_tree.level1[bytes32(uint256(id >> 16))]), 0, "testFuzz_AddAndRemove::3"); 41 | assertGt(uint256(_tree.level2[bytes32(uint256(id >> 8))]), 0, "testFuzz_AddAndRemove::4"); 42 | 43 | _tree.remove(id); 44 | 45 | assertEq(_tree.contains(id), false, "testFuzz_AddAndRemove::5"); 46 | 47 | assertEq(uint256(_tree.level0), 0, "testFuzz_AddAndRemove::6"); 48 | assertEq(uint256(_tree.level1[bytes32(uint256(id >> 16))]), 0, "testFuzz_AddAndRemove::7"); 49 | assertEq(uint256(_tree.level2[bytes32(uint256(id >> 8))]), 0, "testFuzz_AddAndRemove::8"); 50 | } 51 | 52 | function testFuzz_RemoveLogicAndSearchRight(uint24 id) external { 53 | vm.assume(id > 0); 54 | 55 | _tree.add(id); 56 | _tree.add(id - 1); 57 | 58 | assertEq(_tree.findFirstRight(id), id - 1, "testFuzz_RemoveLogicAndSearchRight::1"); 59 | 60 | _tree.remove(id - 1); 61 | assertEq(_tree.findFirstRight(id), type(uint24).max, "testFuzz_RemoveLogicAndSearchRight::2"); 62 | } 63 | 64 | function testFuzz_RemoveLogicAndSearchLeft(uint24 id) external { 65 | vm.assume(id < type(uint24).max); 66 | 67 | _tree.add(id); 68 | _tree.add(id + 1); 69 | assertEq(_tree.findFirstLeft(id), id + 1, "testFuzz_RemoveLogicAndSearchLeft::1"); 70 | 71 | _tree.remove(id + 1); 72 | assertEq(_tree.findFirstLeft(id), 0, "testFuzz_RemoveLogicAndSearchLeft::2"); 73 | } 74 | 75 | function test_FindFirst() external { 76 | _tree.add(0); 77 | _tree.add(1); 78 | _tree.add(2); 79 | 80 | assertEq(_tree.findFirstRight(2), 1, "test_FindFirst::1"); 81 | assertEq(_tree.findFirstRight(1), 0, "test_FindFirst::2"); 82 | 83 | assertEq(_tree.findFirstLeft(0), 1, "test_FindFirst::3"); 84 | assertEq(_tree.findFirstLeft(1), 2, "test_FindFirst::4"); 85 | 86 | assertEq(_tree.findFirstRight(0), type(uint24).max, "test_FindFirst::5"); 87 | assertEq(_tree.findFirstLeft(2), 0, "test_FindFirst::6"); 88 | } 89 | 90 | function test_FindFirstFar() external { 91 | _tree.add(0); 92 | _tree.add(type(uint24).max); 93 | 94 | assertEq(_tree.findFirstRight(type(uint24).max), 0, "test_FindFirstFar::1"); 95 | 96 | assertEq(_tree.findFirstLeft(0), type(uint24).max, "test_FindFirstFar::2"); 97 | } 98 | 99 | function testFuzz_FindFirst(uint24[] calldata ids) external { 100 | vm.assume(ids.length > 0); 101 | 102 | for (uint256 i = 0; i < ids.length; i++) { 103 | _tree.add(ids[i]); 104 | } 105 | 106 | for (uint256 i = 0; i < ids.length; i++) { 107 | uint24 id = ids[i]; 108 | 109 | uint24 firstRight = _tree.findFirstRight(id); 110 | uint24 firstLeft = _tree.findFirstLeft(id); 111 | 112 | if (firstRight != type(uint24).max) { 113 | assertEq(_tree.contains(firstRight), true, "testFuzz_FindFirst::1"); 114 | assertEq(firstRight < id, true, "testFuzz_FindFirst::2"); 115 | } 116 | 117 | if (firstLeft != 0) { 118 | assertEq(_tree.contains(firstLeft), true, "testFuzz_FindFirst::3"); 119 | assertEq(firstLeft > id, true, "testFuzz_FindFirst::4"); 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /test/libraries/math/Uint128x128Math.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import "../../../src/libraries/math/Uint128x128Math.sol"; 8 | 9 | contract Uint128x128MathTest is Test { 10 | using Uint128x128Math for uint256; 11 | 12 | function test_Pow() external pure { 13 | uint256 res = _toUint128x128(1.0001e18).pow(100_000); 14 | 15 | assertApproxEqRel(_toUint256(res), 22015.456048527954e18, 1e12, "test_Pow::1"); 16 | } 17 | 18 | function test_PowAndLog() external pure { 19 | uint256 base = _toUint128x128(1.0001e18); 20 | uint256 res = base.pow(100_000); 21 | 22 | int256 baselog2 = base.log2(); 23 | 24 | assertGt(baselog2, 0, "test_PowAndLog::1"); 25 | assertApproxEqRel(res.log2() / baselog2, 100_000, 1e12, "test_PowAndLog::2"); 26 | } 27 | 28 | function _toUint128x128(uint256 x) internal pure returns (uint256) { 29 | return (x << 128) / 1e18; 30 | } 31 | 32 | function _toUint256(uint256 x) internal pure returns (uint256) { 33 | return (x * 1e18) >> 128; 34 | } 35 | 36 | function _abs(int256 x) internal pure returns (uint256) { 37 | return x < 0 ? uint256(-x) : uint256(x); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/mocks/ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 7 | 8 | /// @title ERC20Mock 9 | /// @author Trader Joe 10 | /// @dev ONLY FOR TESTS 11 | contract ERC20Mock is ERC20, Ownable { 12 | uint8 private immutable decimalsOverride; 13 | 14 | /// @dev Constructor 15 | /// @param _decimals The number of decimals for this token 16 | constructor(uint8 _decimals) ERC20("ERC20 Mock", "ERC20Mock") Ownable(msg.sender) { 17 | decimalsOverride = _decimals; 18 | } 19 | 20 | /// @dev Define the number of decimals 21 | /// @return The number of decimals 22 | function decimals() public view override returns (uint8) { 23 | return decimalsOverride; 24 | } 25 | 26 | /// @dev Mint _amount to _to, only callable by the owner 27 | /// @param _to The address that will receive the mint 28 | /// @param _amount The amount to be minted 29 | function mint(address _to, uint256 _amount) external onlyOwner { 30 | _mint(_to, _amount); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/mocks/ERC20TransferTax.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {ERC20Mock} from "./ERC20.sol"; 6 | 7 | /// @title ERC20MockTransferTax 8 | /// @author Trader Joe 9 | /// @dev ONLY FOR TESTS 10 | contract ERC20TransferTaxMock is ERC20Mock { 11 | /// @dev Constructor 12 | constructor() ERC20Mock(18) {} 13 | 14 | function _update(address from, address to, uint256 amount) internal override { 15 | uint256 tax = amount / 2; 16 | 17 | super._update(from, address(0), tax); 18 | super._update(from, to, amount - tax); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/mocks/FlashBorrower.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | import {ILBPair} from "src/LBPair.sol"; 8 | import {ILBFlashLoanCallback} from "src/interfaces/ILBFlashLoanCallback.sol"; 9 | import {Constants} from "src/libraries/Constants.sol"; 10 | import {PackedUint128Math} from "src/libraries/math/PackedUint128Math.sol"; 11 | 12 | contract FlashBorrower is ILBFlashLoanCallback { 13 | using PackedUint128Math for bytes32; 14 | using SafeERC20 for IERC20; 15 | 16 | enum Action { 17 | NORMAL, 18 | REENTRANT 19 | } 20 | 21 | error FlashBorrower__UntrustedLender(); 22 | error FlashBorrower__UntrustedLoanInitiator(); 23 | 24 | ILBPair private immutable _lender; 25 | 26 | constructor(ILBPair lender_) { 27 | _lender = lender_; 28 | } 29 | 30 | function LBFlashLoanCallback( 31 | address, 32 | IERC20 tokenX, 33 | IERC20 tokenY, 34 | bytes32 amounts, 35 | bytes32 totalFees, 36 | bytes calldata data 37 | ) external override returns (bytes32) { 38 | (uint128 paybackX, uint128 paybackY, bytes32 callback, Action a) = 39 | abi.decode(data, (uint128, uint128, bytes32, Action)); 40 | 41 | if (a == Action.REENTRANT) { 42 | _lender.flashLoan(this, amounts, ""); 43 | } 44 | 45 | if (paybackX == type(uint128).max) { 46 | paybackX = amounts.decodeX() + totalFees.decodeX(); 47 | } 48 | 49 | if (paybackY == type(uint128).max) { 50 | paybackY = amounts.decodeY() + totalFees.decodeY(); 51 | } 52 | 53 | if (paybackX > 0) { 54 | tokenX.safeTransfer(msg.sender, paybackX); 55 | } 56 | 57 | if (paybackY > 0) { 58 | tokenY.safeTransfer(msg.sender, paybackY); 59 | } 60 | 61 | return callback; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/mocks/MockHooks.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | import "../../src/LBBaseHooks.sol"; 6 | 7 | contract MockHooks is LBBaseHooks { 8 | bytes public beforeData; 9 | bytes public afterData; 10 | 11 | address public pair; 12 | 13 | function setPair(address _pair) public virtual { 14 | pair = _pair; 15 | } 16 | 17 | function reset() public virtual { 18 | delete beforeData; 19 | delete afterData; 20 | } 21 | 22 | function _getLBPair() internal view virtual override returns (ILBPair) { 23 | return ILBPair(pair); 24 | } 25 | 26 | function _onHooksSet(bytes32, bytes calldata) internal virtual override { 27 | beforeData = msg.data; 28 | } 29 | 30 | function _beforeSwap(address, address, bool, bytes32) internal virtual override { 31 | beforeData = msg.data; 32 | } 33 | 34 | function _afterSwap(address, address, bool, bytes32) internal virtual override { 35 | afterData = msg.data; 36 | } 37 | 38 | function _beforeFlashLoan(address, address, bytes32) internal virtual override { 39 | beforeData = msg.data; 40 | } 41 | 42 | function _afterFlashLoan(address, address, bytes32, bytes32) internal virtual override { 43 | afterData = msg.data; 44 | } 45 | 46 | function _beforeMint(address, address, bytes32[] calldata, bytes32) internal virtual override { 47 | beforeData = msg.data; 48 | } 49 | 50 | function _afterMint(address, address, bytes32[] calldata, bytes32) internal virtual override { 51 | afterData = msg.data; 52 | } 53 | 54 | function _beforeBurn(address, address, address, uint256[] calldata, uint256[] calldata) internal virtual override { 55 | beforeData = msg.data; 56 | } 57 | 58 | function _afterBurn(address, address, address, uint256[] calldata, uint256[] calldata) internal virtual override { 59 | afterData = msg.data; 60 | } 61 | 62 | function _beforeBatchTransferFrom(address, address, address, uint256[] calldata, uint256[] calldata) 63 | internal 64 | virtual 65 | override 66 | { 67 | beforeData = msg.data; 68 | } 69 | 70 | function _afterBatchTransferFrom(address, address, address, uint256[] calldata, uint256[] calldata) 71 | internal 72 | virtual 73 | override 74 | { 75 | afterData = msg.data; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/mocks/WNATIVE.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract WNATIVE is ERC20 { 8 | constructor() ERC20("Wrapped Native", "WNATIVE") {} 9 | 10 | function deposit() external payable { 11 | _mint(msg.sender, msg.value); 12 | } 13 | 14 | function withdraw(uint256 _amount) external { 15 | _burn(msg.sender, _amount); 16 | (bool success,) = msg.sender.call{value: _amount}(""); 17 | 18 | if (!success) { 19 | revert("Withdraw failed"); 20 | } 21 | } 22 | } 23 | --------------------------------------------------------------------------------